Commit graph

4317 commits

Author SHA1 Message Date
nesquena-hermes
d85eb85c25
Merge branch 'master' into fix/interim-anchor-final-character 2026-07-31 11:52:58 -07:00
nesquena-hermes
07b33505fd
Merge branch 'master' into franksong2702/fix-gateway-terminal-persistence 2026-07-31 11:47:43 -07:00
nesquena-hermes
d482a12345
Merge branch 'master' into franksong2702/viewport-scroll-anchor-6414 2026-07-31 11:40:47 -07:00
Nathan Esquenazi
94a2091435
Merge branch 'master' into fix/interim-anchor-final-character 2026-07-31 11:11:50 -07:00
Nathan Esquenazi
15e3e89c3c
Merge branch 'master' into franksong2702/fix-gateway-terminal-persistence 2026-07-31 11:09:50 -07:00
Nathan Esquenazi
eb1484bede
Merge branch 'master' into franksong2702/viewport-scroll-anchor-6414 2026-07-31 11:09:39 -07:00
Nathan Esquenazi
46623de2bd
Merge branch 'master' into fix/preflight-compression-false-positive 2026-07-31 11:09:09 -07:00
Frank Song
89023eb191
Live-to-Final: prove historical hydration and fence terminal retry ownership (#6451)
* test: add historical hydration lifecycle proof

* fix: retry anchor persistence after terminal app errors

* fix: fence deferred anchor settlement ownership

* test: cover deferred anchor retry ownership

* fix: preserve terminal anchor retry across refresh

* fix: bind terminal anchor retry to turn owner

---------

Co-authored-by: Frank Song <franksong2702@gmail.com>
2026-07-31 11:04:40 -07:00
happy5318
f52be26610 docs: update comments to reflect preflight exclusion reasoning
Clarify that preflight compression is intentionally excluded because
the later authoritative 'Compacting context' marker from
conversation_compression is the real signal that compression
proceeded. The preflight status can fire even when compression
exits before compaction (e.g. durable guard refresh, Codex-Hermes
mode with no active thread).
2026-08-01 01:47:51 +08:00
happy5318
9c1c1d8063 fix: stop false Compressing-context card for preflight compression
PR #6184 narrowed the agent-status-to-SSE bridge to real compression
start notices, but the 'preflight compression:' pattern matches the
Hermes agent's preflight check message ("Preflight compression: ~X
tokens >= Y threshold"). This fires on every new session where the
system prompt alone exceeds the threshold, showing a "Compressing
context" card before any model output — a false positive.

Remove 'preflight compression:' from the pattern. The preflight check
is a one-time pre-model-call estimate, not a mid-conversation
compression event that the user needs to see.
2026-08-01 01:07:12 +08:00
Frank Song
c69f1a73dc test: exercise renderer scroll restore ownership 2026-07-31 22:24:25 +08:00
Frank Song
fd1879418d fix: queue pinned live scroll ownership check 2026-07-31 22:24:25 +08:00
Frank Song
c32b10ee8b test: update live scroll fixture contracts 2026-07-31 22:24:25 +08:00
Frank Song
cc5cf19710 test: exercise reachable live scroll restore boundary 2026-07-31 22:24:25 +08:00
Frank Song
713418dec0 test: cover live render scroll ownership boundary 2026-07-31 22:24:25 +08:00
Frank Song
7a320bea3c fix: preserve live scroll ownership on snapshot mismatch 2026-07-31 22:24:25 +08:00
Frank Song
5efc94f4b2 fix: preserve ownership of live scroll snapshots 2026-07-31 22:24:25 +08:00
Frank Song
5c3ead61a2 Restore unpinned desktop stream scroll 2026-07-31 22:24:25 +08:00
Frank Song
dadffe0e02 fix: bind gateway terminal recovery to run identity 2026-07-31 17:31:12 +08:00
Frank Song
ded12e48c5 fix: recover unsaved gateway terminal errors 2026-07-31 12:05:39 +08:00
Frank Song
2f17d1c74c fix: mark failed gateway terminal saves as unpersisted 2026-07-31 09:57:51 +08:00
Frank Song
1c385e6935 Merge remote-tracking branch 'origin/master' into franksong2702/live-worklog-settle-no-blank 2026-07-31 01:27:57 +08:00
nankingjing
95a1647557 fix(#6419): route every recovery path through one pending_user_message merge helper
The mid-stream reconnect path could render the just-sent user message below
the live streaming assistant response it triggered, because
`refreshSession()` appended `pending_user_message` at the end of
`S.messages` unconditionally while `loadSession()` correctly inserted
it before the first live assistant message. When the client went through
its reconnect/recovery path mid-turn (typical on iOS PWA screen
lock/resume) the live assistant tail rendered above the prompt.

Centralize every recovery/reconnect reinsertion through one
identity-aware helper (`_mergePendingSessionMessage`) that:

- inserts the owning user turn before its live assistant/Worklog tail,
- appends only when no live tail exists,
- deduplicates against the current tail user row,
- remains idempotent across repeated resume probes.

`refreshSession()` now calls the same helper instead of pushing at
the end. `loadSession()` continues to use the helper unchanged.

Adds `tests/test_issue6419_pending_user_reconnect_order.py` pinning:
- the helper is defined exactly once at module top level,
- it inserts before live assistant and falls back to append,
- `refreshSession()` uses the helper and does not push unconditionally,
- `loadSession()` still merges before renderMessages in the INFLIGHT
  reattach branch.

Renames the inner end-marker in
`tests/test_gateway_sse_reconnect_dedupe.py` to a stable inside-loadSession
boundary (`// Phase 2a:`) since the helper is no longer nested.
2026-07-29 22:37:58 +00:00
Anthony Ruiz
127a57e0c5 fix(sessions): fully consume PWA launch action 2026-07-29 21:50:54 +00:00
Anthony Ruiz
cd04f5fcc2 fix(sessions): preserve active chat across hard refresh 2026-07-29 21:50:54 +00:00
Rod Boev
4a7b851c8c fix(#6481): materialize pending turns without borrowed display indexes 2026-07-29 18:36:24 +00:00
Rod Boev
9b0506d31d fix(#6481): keep display ownership out of context indexes 2026-07-29 18:36:24 +00:00
Rod Boev
525edef5f7 Fix silent-result ownership for repeated prompts 2026-07-29 18:36:24 +00:00
Rod Boev
66438f07bc Settle verification turns from exact turn ownership 2026-07-29 18:36:24 +00:00
Rod Boev
920b48d709 fix(#6481): preserve repeated pending turns through verification cleanup 2026-07-29 18:36:24 +00:00
Rod Boev
5e447683f8 fix(#6481): keep verification cleanup from duplicating the current turn 2026-07-29 18:36:24 +00:00
Rod Boev
ad3770fd6e fix(#6481): restore marker-only verification cleanup 2026-07-29 18:36:24 +00:00
Rod Boev
663aec682e fix(#6481): prevent verification follow-up context contamination 2026-07-29 18:36:24 +00:00
pxxD1998
cc6c87903a fix(session): bound delete lock wait 2026-07-29 18:22:22 +00:00
pxxD1998
e2b052a637 fix(workspace): persist recovered session bindings 2026-07-29 18:22:22 +00:00
happy5318
a57d576805 fix: make prefixed Qwen detection positive-only to avoid shadowing hybrid IDs
Gate review found that the broadened token-prefix check caused
deepseek-r1-distill-qwen2.5-bakeneko-32b to enter the Qwen branch
(qwen2 token), return False, and prevent the DeepSeek detector from
running — a silent regression.

Fix: split into two phases:
1. Positive-only scan: any token matching qwen(\d+) with major >= 3
   immediately returns True. No False path — Qwen 2.x in hybrid IDs
   falls through to subsequent detectors.
2. Terminal guard (original behavior): standalone "qwen" token or
   normalized.startswith("qwen") still applies the 3+ gate with
   return False for 2.x, since those ARE pure Qwen models.

Add regression test for deepseek-r1-distill-qwen2.5-bakeneko-32b.
2026-07-29 18:07:55 +00:00
happy5318
90250adfb7 fix: address review — remove redundant guard, add regression tests
- Drop the now-subsumed `normalized.startswith("qwen")` guard
  (token-prefix matching already covers the bare case)
- Add test_qwen_prefixed_alias_reasoning_detection covering:
  prefixed Qwen 3+ (al-/sn-), bare Qwen 3+, and Qwen 2.x exclusion
2026-07-29 18:07:55 +00:00
StefanPieter
8db75f8b78 fix: keep SIGUSR1 stack dumps non-terminating 2026-07-29 17:41:46 +00:00
starship-s
d00c3332e4
ci: constrain MCP SDK to compatible 1.x releases (#6564)
Assisted-by: Hermes Agent:gpt-5.6-sol
2026-07-29 10:35:23 -07:00
nesquena-hermes
b145f59312 fix(titles): reject trivial-echo new session titles (gate-rebase of #6529)
Co-authored-by: Hermes Agent <hermes@nousresearch.com>
2026-07-26 20:22:25 +00:00
nesquena-hermes
c6e9ad79d2 test: update legacy SSE-break regression test for SSE_RELAY_CLOSE_EVENTS (#6527 maintainer fold-in)
The R5b static-pattern test pinned the old inline ('stream_end','error','cancel')
tuple; #6527 replaced it with the named SSE_RELAY_CLOSE_EVENTS frozenset. Accept
either shape and assert behavior (cancel+apperror in the resolved close set)
rather than source syntax. Intent-preserving; non-vacuous (fails on master:
SSE_RELAY_CLOSE_EVENTS absent + apperror not in old tuple).
2026-07-26 19:58:43 +00:00
nesquena-hermes
c49568101c fix(sse): close relays on apperror terminal events (gate-rebase of #6527)
Co-authored-by: Hermes Agent <hermes@nousresearch.com>
2026-07-26 19:45:38 +00:00
Frank Song
c8d9faea29 Merge remote-tracking branch 'origin/master' into franksong2702/live-worklog-settle-no-blank 2026-07-26 11:03:04 +08:00
nesquena-hermes
44877274da Release exp-v0.52.151: repair admin model reload rebinding (#6477, @mspgle-dotcom) 2026-07-26 02:08:30 +00:00
Frank Song
95aa8ece36 Merge remote-tracking branch 'origin/master' into franksong2702/live-worklog-settle-no-blank 2026-07-26 09:22:09 +08:00
ai-ag2026
a31cdb7765 fix(terminal): unsubscribe when the SSE header write fails after attaching
Adversarial review of the previous commit found a leak it introduced.

`attach_terminal()` subscribes and only then are the response headers written.
A client that dropped in that instant makes `end_headers()` raise
BrokenPipeError/ConnectionResetError, and because the `try/finally` started
*after* those writes, the exception escaped `_handle_terminal_output` with the
queue still in `_subscribers`. That pins `unwatched_since` at None — only
`unsubscribe()` on an empty subscriber list resets it — so the terminal became
permanently unreapable: the exact fd/thread leak (#4633) this reaper exists to
prevent, and strictly worse than the TOCTOU the previous commit fixed, since
that at least still allowed cleanup.

The try now starts immediately after the attach, so every exit path
unsubscribes. Regression test drives a handler whose `end_headers()` raises and
asserts the subscriber list is empty, `unwatched_since` is restored, and the
terminal is reapable again once the grace elapses. It fails against the
previous ordering.
2026-07-25 03:17:30 +02:00
ai-ag2026
07d110a064 fix(terminal): close the reaper's selection→reconnect→close TOCTOU
`_terminals_to_reap()` snapshots victims under `_LOCK` and then releases it,
but `subscribe()` clears `unwatched_since` and appends to `_subscribers` under
the terminal's own `_sub_lock`, which the selection pass never held. A viewer
reconnecting in that gap left object identity — all `close_terminal(expected=…)`
ever checked — still true, so the reaper killed a terminal that now had a live
viewer.

- `_claim_reap_victim()` re-establishes the *whole* selection predicate while
  holding `_LOCK` and the terminal's `_sub_lock`, and removes the registry
  entry in that same critical section: same object, no subscribers, and
  `unwatched_since` still set and still expired. A dead process is still
  reaped unconditionally — it cannot come back, and a viewer watching a corpse
  must not pin the entry forever.
- `attach_terminal()` does the registry lookup and the subscribe atomically
  under `_LOCK`, replacing `get_terminal()` + `term.subscribe()` in the SSE
  route. It takes the same two locks in the same order as the claim, so
  exactly one side wins: the viewer attaches (and the reap is refused), or the
  entry is already gone (and the route answers "terminal not running" before
  committing a 200, instead of streaming from a corpse). Registration is the
  authority — a registered-but-`closed` terminal still attaches so the viewer
  receives `terminal_closed`.
- Teardown moved to `_teardown_terminal()` and runs after both locks are
  released; `killpg`+`wait` can block ~2.5s and must not pin the registry.
- Dropped the duplicated `self.last_activity = time.time()` the rebase left in
  `put_output()`.

Tests: a barrier regression that pauses the reaper after selection, attaches a
viewer through the production path, resumes, and asserts the terminal stays
registered/live and still receives output; the inverse where the reap claims
first and the attach reports not-running; refreshed-grace and replaced-entry
claims; dead-with-viewer; and a probe that `_LOCK` is free during teardown.
Three of them fail against an identity-only claim.
2026-07-25 00:33:41 +02:00
ai-ag2026
932e858994 feat(terminal): reap idle abandoned terminals so #4633 can't accumulate
The fd-leak fix retires a terminal whose shell EXITS, and _MAX_TERMINALS bounds
the worst case, but an interactive shell abandoned by its client (tab closed
without POSTing /api/terminal/close, browser crash, network drop) keeps running
forever — there is deliberately no PDEATHSIG. A background reaper now closes any
terminal that has had zero attached output streams for longer than a 15-minute
idle grace, plus any dead-process terminal as a belt-and-suspenders for the
reader-loop retire.

Attachment is tracked on the subscriber model from the output-broadcast change:
subscribe() clears unwatched_since, the last unsubscribe() sets it. A terminal
is born unwatched, so a spawn nobody ever attaches to is reaped too; the grace
spans a tab refresh / brief drop so a real reconnect keeps the session. Eviction
uses close_terminal(sid, expected=term) so a restart replacement is never torn
down. The reaper thread is a lazily-started daemon singleton like the spawn
supervisor.

Stacked on the terminal fd-leak + output-broadcast changes (same file).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-25 00:30:06 +02:00
starship-s
53f9ab9a00 test(streaming): assert interim prose finalizes once
Pin the exact parser finalization count in the interim-assistant-to-tool regression.

Assisted-by: Hermes Agent:gpt-5.6-sol
Assisted-by: Codex:gpt-5.6-luna
2026-07-24 12:49:02 -06:00
starship-s
69b26c1fa5 fix(streaming): complete interim anchor prose
Treat status-less interim assistant events as complete semantic boundaries so streaming Markdown flushes its final lookahead character before tool activity. Add behavior coverage for the real interim-to-tool Anchor pipeline.

Assisted-by: Hermes Agent:gpt-5.6-sol
Assisted-by: Codex:gpt-5.3-codex-spark
2026-07-24 12:30:46 -06:00