Commit Graph

25415 Commits

Author SHA1 Message Date
Teknium ad7b7255ab fix(state): renaming a bot's canonical Bot Chat is refused — the title IS the identity (#92473)
Bot Mode resolves the forever-chat by exact-title lookup on
(profile, 'Bot Chat'); no session-id pointer exists. A user rename
therefore orphaned the whole conversation: resolution missed, the next
click minted an empty replacement, and UNIQUE(title) then blocked ever
renaming back. Refuse the rename at SessionDB._set_session_title — the
single write path every surface funnels through (gateway session.title,
/title, CLI rename, REST). Hidden discriminates the registry row, so a
normal visible session a user happens to call 'Bot Chat' stays freely
renameable; re-asserting the same canonical title stays a no-op.
2026-08-26 03:21:42 -07:00
Teknium a74ffb9ee8 chore: map contributor email for projetsjsl 2026-08-26 03:21:37 -07:00
Teknium 45db70a80a fix(computer-use): fail closed on unverified CuaDriver.app + background launch
Hardening on top of the TCC daemon-identity salvage:
- _validate_cua_driver_app_signature: codesign -dv gate requiring EXACT
  Identifier=com.trycua.driver and the official team (4YEC26S9KF) before
  /usr/bin/open hands the bundle to LaunchServices — the identity fix must
  not double as a launcher for arbitrary/impostor bundles (suffixed
  identifiers and wrong teams rejected; unsigned dev builds only via
  computer_use.allow_unsigned_driver: true in config.yaml).
- _resolve_cua_driver_app_path: derive the bundle ONLY from the resolved
  driver binary — the /Applications fallback could launch a DIFFERENT
  install than the manifest resolved.
- open -n -g: don't activate/steal focus when launching the daemon.
- 7 new tests incl. sabotage-verified exact-match assertions.

Grafted from #76433's review direction (@Chadmc9889's original fail-closed
validation requirement).

Co-authored-by: Chadmc9889 <Chadmc9889@users.noreply.github.com>
2026-08-26 03:21:37 -07:00
projetsjsl 4746f614be fix(computer-use): preserve macOS TCC daemon identity
Launch private computer-use daemons through CuaDriver.app so Screen
Recording authorization remains attached to its stable bundle identity
instead of Hermes' ad-hoc signature.

Co-Authored-By: GPT-5.6 Codex <noreply@openai.com>
2026-08-26 03:21:37 -07:00
Teknium 1fe0f2f3ac feat(cron): import-error cron failures now name gateway code skew and the one-command fix (#95294 part 3)
When an agent cron job dies with an import-class error (cannot import
name / ModuleNotFoundError / ImportError), the failure summarizer — which
runs inside the gateway process — now consults gateway.code_skew: if the
process booted on a different revision than disk HEAD, the delivered
message appends 'gateway is running stale code (booted on X, disk is at
Y) — run hermes gateway restart'. Turns the reported two-day mystery
(15 missed jobs, identical ImportError, no explanation) into a one-line
fix instruction on the first failure.

Fail-safe by construction: skew detection returns None on non-git
installs and processes without a boot fingerprint, the probe seam
swallows every exception, and no_agent script jobs (fresh subprocess,
consistent imports) fall through to the generic cleaner — their
ImportErrors are the script's own problem, and blaming gateway skew
there would send the reader to the wrong place (same mode-gating as the
provider branches).

Reuses gateway/code_skew.py (the /model-switch skew detector) rather
than adding a second fingerprint reader.
2026-08-26 01:23:15 -07:00
Teknium f0c0c986c4 test: pin overlay policy off in embedded-daemon socket/ack contract test
The embedded spawn now consults the overlay policy (capability probe via
subprocess.run) when _cua_no_overlay() is true — which it is on headless
CI since the Linux X11 default flip. The fixed two-entry run side_effect
in this test didn't budget for the probe call; pin the policy off since
this test pins the socket/ack contract, not overlay behavior.
2026-08-26 00:54:36 -07:00
cvillarroel2 1a7f83a73b fix(computer_use): disable embedded daemon overlay 2026-08-26 00:54:36 -07:00
Teknium 4faa721d7d fix(desktop): clicking a bot no longer burns a model turn on a fake user prompt
The intro kickoff ('Hey, tell me about yourself!') now fires ONLY from
genuine New Agent creation. The bot-click canonical resolution path mints
silently: the eager session.title write already persists the lazy row on
modern gateways, so the kickoff's session-persistence job is obsolete
there. A resolution miss (retitled row, hidden-listing gap, post-update
skew) previously re-fired the kickoff on EVERY click — a burned model
turn plus a user-attributed prompt the user never typed (ScottFive
report). Older gateways that reject the eager title keep a narrow compat
kickoff, else the pruner reaps the empty lazy session.
2026-08-26 00:52:54 -07:00
Teknium 3e5c49643c refactor(cron): halve the cronjob tool schema (2,234 → 1,070 tokens/call) without losing guidance (#95287)
* refactor(cron): halve the cronjob schema (2234->1070 tok/call); teaching moves to errors + create-time guidance

* fix(cron): deliver description states one-way posting semantics, drops 'recommended'/'preserves thread context'

* refactor(cron): merge monitor_script/monitor_url into one model-facing 'monitor' field (legacy aliases kept)
2026-08-26 00:51:46 -07:00
hermes-seaeye[bot] 1a19c52dd2 fmt(js): npm run fix on merge (#95365)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-08-26 07:45:59 +00:00
Teknium 25d46c7887 fix(desktop): drop unused registryGatewayWsUrl import left by the header-binding refactor rebase 2026-08-26 00:40:35 -07:00
Victor Nogueira 21f34794be fix(desktop): recover remote sessions after gateway restart 2026-08-26 00:40:35 -07:00
686f6c61 8085614f6a fix(desktop): log when Windows remote SSH skip teardown
POSIX disconnect does not apply to connectWindowsRemote. Quit still
closes the tunnel; log the skipped serve kill so it is not a silent
no-op.
2026-08-26 00:40:35 -07:00
686f6c61 0c69cac48a fix(desktop): terminate owned SSH serve backends on quit
teardownSshConnection closed the tunnel and SSH transport but never
killed the detached serve --isolated process. Spawn uses setsid/nohup,
so the backend reparents to pid 1, keeps state.db open, and accumulates
across Cmd+Q. Reuse cleanupStale via disconnect while SSH can still
exec, sequence remote kill before close, and seal the bootstrap
coordinator so reconnect during a prevented first quit cannot respawn.
The quit race is 6s to cover cleanupStale's 5s wait-for-exit loop.
2026-08-26 00:40:35 -07:00
Jaime Marques 14d16c2578 fix(desktop): clarify primary SSH reuse failures 2026-08-26 00:40:35 -07:00
Jaime Marques 3263ca2af6 fix(desktop): reuse migrated primary SSH backend
Avoid opening a second SSH lifecycle when a migrated registry request targets the same primary/default backend already booted through the legacy route. Compare effective SSH configuration for representation-only drift, treat empty and default as the same root profile, and keep named profiles isolated.
2026-08-26 00:40:35 -07:00
Ahmett101 1b8f3eda38 fix(desktop): scope registered ssh primary gateway 2026-08-26 00:40:35 -07:00
Teknium 4fc4da2531 chore: map contributor email for salvaged PR #88544 2026-08-26 00:40:35 -07:00
Ravi Tharuma 949f5169de fix(desktop): treat ticket 401 as sign-in when native tokens are unreadable 2026-08-26 00:40:35 -07:00
Marco Fernstaedt 024b9c0545 fix(desktop): refresh remote WebSocket header cache recency 2026-08-26 00:40:35 -07:00
Marco Fernstaedt b4162de333 fix(desktop): bind headers to scoped WebSocket URL 2026-08-26 00:40:35 -07:00
kshitijk4poor 4ba2608524 fix(compressor): widen empty-content abort to sibling no-response shapes + snapshot state field
Follow-up to PR #94531 salvage:
- classify the auxiliary boundary's terminal 'None response' /
  'invalid response' errors (#7264) into the same empty-content abort
  carve-out so those shapes also preserve the session (#94459's wider
  classification, sibling shapes from #94448)
- register _last_summary_empty_content_failure in
  _COMPRESSOR_ATTEMPT_STATE_FIELDS so pre-commit hard-cancel rollback
  restores the flag (conversation_compression snapshot allow-list)
- tests: cooldown re-entry keeps aborting; both sibling shapes abort
- attribution: map zhangyswx@163.com -> YusenZhang0601
2026-08-26 13:02:27 +05:30
TonyRainforest fa210e5a96 fix(compressor): abort compression on empty-content provider degradation to prevent context loss (#94448)
When an auxiliary or main summarizer LLM returns an HTTP 200 with an empty or whitespace-only response (e.g., degraded provider/channel), abort compression and preserve the full conversation context rather than falling through to the destructive static-fallback branch that drops the middle window.

- Track _last_summary_empty_content_failure across _generate_summary() and compress()
- Attempt fallback to the main model when an aux model returns empty content
- Abort compression and preserve all messages intact if no valid summary can be generated
- Record summary_empty_content_failure in telemetry and log actionable diagnostic guidance
- Add comprehensive unit tests in tests/agent/test_context_compressor.py

Fixes #94448
2026-08-26 13:02:27 +05:30
kshitijk4poor 5885289c47 refactor(codex): route transports _pair_ids through shared fc_ canonicalization
The reuse reviewer found a third copy of the fc_->call_ synthesis in
agent/transports/codex.py _pair_ids (item_id[3:] spelling, which is why
the len('fc_') grep missed it). All three sites now share
_canonical_call_id_from_fc(), keeping the pairing invariant in one place.
2026-08-26 12:58:35 +05:30
kshitijk4poor e3bb3e7f8c refactor(codex): extract shared fc_->call_ canonicalization helper
/simplify-code reuse+quality reviewers both flagged the byte-identical
fc_->call_ synthesis blocks in the assistant and tool-result branches as
a correctness coupling — the two sites MUST stay in lockstep or pairing
breaks. Extract _canonical_call_id_from_fc() and route both through it.
Mutation check: pairing regression test fails when the tool-branch call
is stubbed out, green after restore.
2026-08-26 12:58:35 +05:30
kshitijk4poor 635232ec4e fix(codex): canonicalize fc_-only tool-result ids to match the call side
The sweeper review on #49224 flagged that the assistant branch synthesizes
call_<suffix> from an fc_-only id while the tool-result branch kept the raw
fc_... string — so an oversized pair hashed to two DIFFERENT clamped
surrogates and the function_call_output arrived unmatched (HTTP 400).

Canonicalize the tool-result side to the same call_<suffix> before
clamping. Also fixes the pre-existing short-fc_ pairing mismatch
(call_short123 vs fc_short123). Regression test covers both lengths.
2026-08-26 12:58:35 +05:30
kshitijk4poor 31485d50ea fix(codex): sanitize replayed function_call.name to Responses API pattern (#31666)
A degenerate tool name stored in conversation history (dots, spaces,
unicode from an earlier model degeneration) bricks every subsequent
Codex Responses turn with a non-retryable HTTP 400:
  Invalid input[N].name: string does not match pattern '^[a-zA-Z0-9_-]+'

The 400 replays forever until the user manually starts a new session.

Add _sanitize_replayed_fn_name() — replaces invalid chars with '_'
(runs collapsed), degrades all-invalid names to 'fn' instead of empty
(an empty name would trade one 400 for a preflight ValueError).  Applied
at both replay sites: the chat-message converter and the preflight
choke-point.  Live tool-definition names are left untouched — they must
match the dispatch registry exactly.  Pairing is by call_id, so
renaming a replayed function_call is safe.

call_id overflow (the sibling half of #49224) was already fixed on main
by #73492 (_clamp_responses_call_id); this commit covers the remaining
invalid-name defect.

Credit: @Morad37 (#31678 — identified the bug, the replay sites, and
the regex contract), @lubosxyz (#49224 — replace-not-strip semantics
and 'fn' fallback to avoid the empty-name trap).

Fixes #31666
2026-08-26 12:58:35 +05:30
ethernet 34c5fcb2a2 fix: update on macos referenced nonexisting variable 2026-08-26 00:24:26 -07:00
Teknium 5a285d3436 fix(desktop): restore stale-branch-reverted main.ts/update files; detach post-switch profile refresh from switch completion
The rebase re-landed pre-#74805 versions of the backend release gate,
venv-blocker rescan, mac entitlements/usage tests, and package.json from
the stale branch base — restored to main's versions (only the salvaged
enumeration/profileMetadata/profile:remember hunks kept in main.ts).
refreshActiveProfile's new bounded retry chain (#70679) is no longer
awaited inside the switch-completion barrier, so a slow/unhealthy backend
cannot hold $gatewaySwitching past the switch-ownership deadline; also
drop an unused $connection import from the earlier conflict compose.
2026-08-26 00:22:27 -07:00
Teknium b722177fcd fix(desktop): drop duplicate knownSessionOwner re-landed by rebase (main's richer variant wins) 2026-08-26 00:22:27 -07:00
tachi 317ae240fb fix(desktop): SSH/reconnect owner continuity, attachment routing, transport-error recovery
Salvage of #94192's unique work (owner-hardening portions that overlap
the class-1 branch — #94824/#93451 seams — and out-of-cluster #94864 are
intentionally excluded):

- use-gateway-request: recognize the full transport-error family
  (ECONNRESET & friends, including error.code and error.cause.code) so a
  reset SSH/remote socket triggers the connection-owned reconnect instead
  of surfacing as a request failure; background profiles keep the
  registry reconnect path for composite remote/SSH sources.
- session-tile-actions: tile attachment uploads and session RPCs follow
  the tile's composite owner (connectionId+profile) even when the active
  gateway moved to a same-named profile on another source.
- knownSessionOwner: sessions expose their complete owner (registry
  connection + profile) instead of a bare profile name that silently
  collapsed the route back to the local path; delegate/wiring resolve
  owners through it.

Fixes the SSH-reconnect share of #91365-adjacent routing gaps.
Salvaged (partial) from #94192.
2026-08-26 00:22:27 -07:00
Tom 2ed39365d6 fix(desktop): thread eager profile metadata through registry enumeration
Never-interacted remote bots painted as bare handles because roster rows
carried only profile names: display_name/title/ui_meta/has_avatar were
fetched lazily on first interaction (#91365). Thread credential-free
profile metadata from the enumeration-time /api/profiles body through
enumerateRegistryAgentSources (main.ts) and buildAgentRoster
(connection-registry.ts), keeping it attached to the connection-qualified
row across the same-install collapse. The plugin.js botRosterMeta half of
the original PR is dropped — superseded by landed #92731.

Fixes #91365
Salvaged (partial) from #92708.
2026-08-26 00:22:27 -07:00
Tilly-YL 2952119bce fix(desktop): remember selected profile across restarts
The profile rail's live workspace switch never persisted the selection,
so the Desktop always booted back into the previous startup profile
(#79886). Route the successful primary-backend activation through a new
persistence-only hermes:profile:remember IPC (validated
writeActiveDesktopProfile) that records the choice WITHOUT tearing down
the backend or reloading the window like hermes:profile:set does.
Registry-source picks name another source's profiles and do not touch
the startup preference. Reapplied semantically over three weeks of
main.ts/preload.ts drift (selectProfile now routes through
activateOnCurrentSource, #91349/#91365 seams).

Fixes #79886
Salvaged from #79888.
2026-08-26 00:22:27 -07:00
David Metcalfe 57043c2bc0 fix(desktop): single-flight refreshProfiles with retry recovery in global remote mode
Global remote mode fires refreshProfiles while the remote HTTP proxy is
still routing: the one-shot fetch failed silently and the rail stayed
empty until a manual refresh. Retry with 500ms/1000ms backoff, surface
terminal failures on the console, and dedupe concurrent callers into a
single retry chain (gateway open fires useBackgroundSync and the
activeGatewayProfile effect at once). Reapplied semantically on top of
the #85731 epoch guard: a stranded epoch stops the retry chain and
invalidation detaches the single-flight slot.

Fixes #70679
Salvaged from #74500.
2026-08-26 00:22:27 -07:00
chelsealong a928596758 fix(desktop): document connect-on-demand origin, add fallback-profiles integration test
Addresses AI-review feedback on #94653: note where the 'connect-on-demand'
sentinel is produced, and cover the interaction between
isLocalEnumerationFailure and localRouteFallbackProfiles directly (not just
the helper in isolation).
2026-08-26 00:22:27 -07:00
chelsealong c475484f63 fix(desktop): do not treat deferred local enumeration as a failure
'connect-on-demand' means local roster enumeration was intentionally
skipped to avoid spawning a local backend on a remote-only workspace,
not that it failed. The plugin-profile-routes IPC handler passed
Boolean(error) straight through, so that deferral was treated as a
genuine failure and Bot Mode re-synthesized cached local profile rows
even though local was never dialed.

Fixes #94648
2026-08-26 00:22:27 -07:00
Jeremy McKeehen 4ca1f532be test(desktop): pass the pin-write fence into the Show-all order assertion
resolvePinnedSessions requires unconfirmedPinWrites; the reconnection-scope test omitted it and would fail strict tsc.
2026-08-26 00:22:27 -07:00
Jeremy McKeehen 3751b04550 test(desktop): lock pin upgrade to server-authoritative pull
Old per-profile pin caches caused the stale unpin resurrection. Prove they are ignored and that sessions.pinned repopulates the gateway-wide key without a migration PATCH.
2026-08-26 00:22:27 -07:00
Jeremy McKeehen ff57f173d8 fix(desktop): keep pin list identity gateway-wide
Pin localStorage was keyed per connection and profile, so an unpin
reloaded a stale copy on switch and re-asserted pinned=true.
Scope pins by connection only so they survive rescope and stay isolated per gateway.
2026-08-26 00:22:27 -07:00
Teknium e0210ab6c9 chore: contributor email mappings for salvage class-4 2026-08-26 00:21:49 -07:00
Kolton Jacobs 9577d66317 fix(desktop): probe a cached pooled remote backend before dispatching to it
A pooled remote backend (Bot Mode, group chat) keeps its descriptor and SSH
forward cached in the backend pool. When the remote Desktop relaunches, the
remote process dies but the local forward stays LISTENing, so
ensureRegistryBackend() keeps returning the dead descriptor and every dispatch
to that machine fails until the app is restarted.

The background sweep cannot cover this: revalidatePooledRemoteBackends() only
runs from the renderer reconnect IPC, which never fires while the primary
connection stays healthy.

Validate the exact cached descriptor at dispatch time with a short /api/status
probe (2.5 s). On failure, retire the pool entry and its SSH forward, then
reconnect on demand. Concurrent dispatches share one retire/reconnect sequence
through a RemoteRevalidationCoordinator keyed on the cached promise, and
identity checks make a late failure from an old descriptor unable to tear
down a replacement another caller already installed.

Verified on a two-Mac setup (MacBook + Mac mini over SSH): after relaunching
the Mac mini's Desktop, a group-chat turn from the MacBook now reaches the
mini's backend and its reply lands, where it previously failed forever.
2026-08-26 00:21:49 -07:00
RayCharlizard 616d6c5432 fix(desktop): retire the composer busy latch on gateway reconnect (#93059)
reconcileBusyStatesOnReconnect downgraded stale busy/awaiting claims by
writing the $sessionStates mirror directly. The claim has four holders —
the wiring cache, that mirror, the focused view's draft $busy /
$awaitingResponse, and busyRef — and only the write path (the delegate's
updateSessionState) keeps them in lockstep. After a reconnect that orphans
a mid-turn runtime (a respawned backend re-mints runtime ids, so the
terminal busy:false never arrives) the mirror cleared but the composer
stayed latched: Send failed isTargetSessionBusy and silently no-oped until
restart, and warm resume could OR the stale cache copy back over the
backend's running:false.

- SessionTileDelegate.retireBusyClaim?: optional twin of
  invalidateRuntimeBindings; writes through updateSessionState, returns
  false (and writes nothing) for a runtime the cache never held.
- reconcileBusyStatesOnReconnect routes each in-scope downgrade through it,
  keeps the mirror publish as the fallback, and on a primary reconcile also
  clears the focused draft latches. Scoped reconciles leave the composer
  alone.

Tests: hook (real useGatewayBoot + fake socket), store (write-path route,
miss fallback, primary vs scoped), cache (real updateSessionState) and
delegate (hit/miss) — RED on main, GREEN here. Full desktop UI suite,
typecheck and lint pass.

Written with LLMs under human direction: initial report and diagnosis by
GPT-5.6 (OpenAI Codex); root-cause refinement, design and review by
Claude Fable 5; implementation and tests by Claude Opus 5.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-26 00:21:49 -07:00
Flownium 2e75f9dc48 fix(desktop): preserve terminal state during reconnect hydration 2026-08-26 00:21:49 -07:00
Flownium 19251ac9e3 fix(desktop): hydrate transcript after reconnect attach 2026-08-26 00:21:49 -07:00
Teknium b52b05c1de test(desktop): profile-door dial failure now rejects (post-#81165 contract) — #92265 invariant unchanged 2026-08-26 00:21:29 -07:00
Teknium 57876f4b71 fix(desktop): typecheck fixes for salvaged tests (afterEach import, routed-request mock typing) 2026-08-26 00:21:29 -07:00
Teknium 9730bc78ff fix(desktop): feature-detect ctx.onDispose in the hide-sweep scheduler
Direct-file plugin hosts don't provide onDispose; every other call site in
plugin.js already guards it. Follow-up to the #94915 salvage.
2026-08-26 00:21:29 -07:00
Teknium 0366eacae3 fix(desktop): re-home the active key when the primary gateway re-homes
Follow-up to the #93892 keep-set salvage (#93916): the new
"remote tile keep-set must not pin a local same-named secondary" test
exposed a real scoping defect — route identity in the prune keep-set must
be full composite scope (connectionId + profile), never a bare profile
name inherited by accident.

setPrimaryGateway() moved g.primaryProfile without moving g.activeKey
when the active route WAS the primary. The stale bare-name activeKey
(e.g. 'default') then matched a later, unrelated LOCAL 'default'
secondary in pruneSecondaryGateways' `key === g.activeKey` spare, so a
keep-set of composite scopes like 'conn:homelab::default' appeared to
pin the local socket forever. Now the active key follows the primary
re-home, keeping the exact-scope identity contract intact.
2026-08-26 00:21:29 -07:00
fangliquanflq ef6532c25c test(desktop): cover bot reconciliation runtime lifecycle 2026-08-26 00:21:29 -07:00
fangliquanflq 66186dc58f fix(desktop): keep bot reconciliation off inactive backends 2026-08-26 00:21:29 -07:00