* fix(remote-terminal): calm, honest reconnect status with one notice per server
* fix(remote-terminal): keep the SSH sign-in wait immediate under the calm reconnect banner
* fix(remote-terminal): a surface that loses its turn drops its notice at once
* fix(remote-terminal): resolved action cards and handed-off notices leave at once
* fix(remote-terminal): only the leader at the end of an outage holds its notice
* feat(telemetry): count Windows-drive WSL repos with WSL-made worktrees
The daily wsl_usage_snapshot gains projects_wsl_windows_drive_wsl_worktrees: of the
Windows-drive repos that run Git in WSL, how many have a linked worktree WSL Git made
(a Linux-path link). Git for Windows can't take those over, so the D1c conversion
refuses them; this sizes that class before the bridge is removed. Bucketed count only,
read from the link files with no Git spawned; optional in the schema so older payloads
still validate.
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(telemetry): don't count Windows Git's //server/share worktree links as WSL-made
Review: a UNC link starts with '/' too. Reuse the prune guard's classifier, and note
that a repo whose .git is a file reads as having no worktrees (no Git is spawned).
Co-Authored-By: Claude <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
* fix(editor): enable Lean syntax highlighting
* fix(editor): enable Lean syntax highlighting
Adapt #31930 onto current main and retain its bundled Lean grammar repair. Register the lazy TextMate provider and cover detection, proof syntax, nested comments, and bounded tokenization.
* test(editor): verify Lean highlighting in the full app
---------
Co-authored-by: Neil <neil@stably.ai>
Client control-socket closes and server heartbeat reaps only reached the
console, which a packaged app never shows. Each control-socket close now
writes a remote-runtime.control-socket-close span (cause, close code,
error code, last keepalive RTT, socket and ready uptime). Each server reap
writes remote-runtime.server-socket-reaped. Both land in the main trace
file that Send app diagnostics collects. Keepalive pongs now measure RTT,
exposed as lastRttMs and lastCloseCause on the shared-control diagnostics.
On macOS wake and network online events every remote-runtime socket was
probed with an 8 s deadline and closed on a miss. Relayed or lossy links
still rerouting after a wake lost connections that would have survived.
A miss now re-pings and leaves the socket until the normal 25 s keepalive
window ends; the probe is skipped when data arrived in the last 3 s.
Exiting a paired host tab's original pane moved the host's active leaf to the
surviving split. The client's reconciliation effect followed it with
setActivePane, and that persisted every still-mounted pane, writing the retired
leaf (whose remote PTY had not ended yet) back into the layout. The leaf was then
no longer retired, so its pane stayed forever. Any other persist in that window
(insertions, clicks, title edits, divider drags) could do the same.
persistLayoutSnapshot now drops the leaves the host retired, using the
retireLeavesFromTerminalLayout main already uses, moved to shared. The retired
set it reads is the one the reconciliation effect tracks.
* A Claude chat shows the turns made outside it
When a Claude chat's agent next opens, turns written to the same
conversation elsewhere (claude --resume in a terminal) are appended once,
in order, under the existing "Continued outside this chat." divider.
Streamed reply and thinking rows are now keyed by their source block
(message id and block index), which Claude also saves on each transcript
row, so the chat can tell which saved rows it already shows. The read goes
back from the end of the transcript only to the chat's saved turn-end leaf,
replays the rows through Claude's own translator in an isolated sink, and
commits them in one settlement that writes nothing if the chat moved.
Imported prompts are display-only and never settle a send; any failure
opens the chat normally.
* test(claude): name the transcript row shapes the catch-up test builds
* fix(claude): read a catch-up row's uuid once as a string
* fix(claude): parse transcript tail rows without type assertions
* refactor(native-chat): share the catch-up open runner across agents
The Codex catch-up's agent-neutral parts move to one module: the open-time
envelope (its own deadline and cancellation, never gating the open), the
bounded staged batch, the single settlement commit that re-checks the
journal before writing, and the "Continued outside this chat." divider.
Codex keeps only how it reads its saved turns and what the chat already
holds. No behavior change; the other agents' catch-up builds on this
instead of copying it.
* refactor(claude): catch up through the shared history catch-up runner
* chore: remove unrelated pnpm executable metadata
* refactor(native-chat): the catch-up runner reads the chat's view only when the agent asks
* chore: remove regenerated pnpm executable metadata
* fix(native-chat): the catch-up deadline also bounds waiting for its commit to be written
A journal write barrier that never settled after the import was admitted held
the provider's open past the catch-up deadline. The wait now ends with the
deadline or the open's cancellation, like every other step.
* test: type the history catch-up sink fixture without assertions
* fix(native-chat): bound and revoke history preparation
* test(claude): expect source block identity in stream cadence
* fix(claude): preserve final output and history coverage
* feat(native-chat): validate expected saved items during history catch-up
* fix(claude): integrate conditional history settlement
* fix(claude): use new content and provider prompt identities for catch-up
* fix(claude): retain qualified completed leaves through session exit
* fix(claude): preserve unresolved membership for recovered follow-ups
* test(claude): use fork epoch reason for rewind preservation
* fix(claude): consolidate translator sink type imports
* fix(claude): preserve source message times during catch-up
* fix(claude): narrow staged history mutations to items
* test: bound Claude retained-leaf catch-up work
* fix(claude): settle imported work at source boundaries
* test(claude): keep shared journal harness portable
* test(mobile): await editor listener cleanup before snapshot
* test: isolate retained slice measurements and accept closed socket EPIPE
* fix(keybindings): report conflicts between terminal shortcuts and chords that fire in terminals
Actions that reach a focused terminal now share a conflict bucket, and the
Shortcuts pane judges the bindings as written in the file instead of the
map the loader already stripped.
* fix(keybindings): flag only the ignored binding and say it was ignored
The other claimant keeps its own default and never stopped working, so a
warning on its row blamed an untouched action. Adds the e2e regression that
seeds a conflicting file inside the isolated E2E home.
* test(keybindings): capture the shortcuts pane at each stage of the conflict report
* test(keybindings): assert the untouched action carries no warning
Visibility alone would still pass if a regression warned on both rows.
* fix(keybindings): narrow the ignored-binding guard and tighten its negative test
Review follow-ups:
- Only warn when the file asked for a binding and the loader took it away.
The guard now also requires the action to be absent from the active map,
so an override that survived is never labelled "was ignored".
- The negative case used view.tasks, which has no default binding, so the
conflict assertion passed without exercising the terminal bucket at all.
It now uses worktree.quickOpen and asserts the definition exists, is not
terminal-reachable, and has a non-empty default list.
* fix(keybindings): compare main-process shortcuts against every scope
- List the actions resolveWindowShortcutAction consumes in main and put them in every scope's conflict bucket, so a binding elsewhere on the same chord is reported instead of silently losing it.
- Browser bindings are compared only against the actions a focused guest forwards ahead of browser chords.
- Dictation stays out, since main lets its chord through while voice is off (the default).
- The two digit ranges may still share a modifier.
- Drop the allowInTerminal terminal-bucket rule.
- Explain overrides the loader drops on a later pass.
- Pin the list to the resolver and the guest order with tests.
* fix: report main-intercepted shortcuts in Settings without dropping saved overrides
Main-process interception now only feeds the Settings conflict report.
The loader, write path and web normalization keep main's scope-only check,
so existing keybindings.json overrides load exactly as before.
---------
Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
Co-authored-by: Neil <neil@stably.ai>
* fix(agent-status): drop Claude teammates stopped with TaskStop from the sidebar
Named background agents run as in-process teammates. TaskStop fires no
lifecycle hook, and their background_tasks entries use ids unrelated to the
lifecycle agent_id, so an idle teammate row was kept as long as any teammate
task remained listed, even after that teammate was stopped.
Remember the description of the lead's Agent call that spawned each teammate
(Claude lists the teammate's background_tasks entry under it), and at a lead
Stop with a complete inventory drop a teammate row whose description is no
longer listed. Rows without that evidence keep the previous behavior.
Fixes#31818
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(agent-status): keep a teammate's spawn description when its spawn result lands first
Hook requests are handled unordered, so the lead's Agent PostToolUse can
arrive before the teammate's SubagentStart. Create the row as idle in that
case so the description is kept; SubagentStart then revives it to working,
and an idle row never gates the pane if the teammate's stop came first.
Moves the spawn recorder into claude-teammate-spawn.ts to keep
claude-events.ts within the file length limit.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(agent-status): retain teammates with clipped task descriptions
Claude caps background task descriptions and appends a clipping marker. Match that visible prefix conservatively when reconciling a teammate's full spawn description, so an inventory update cannot hide a live teammate. Keep ambiguous descriptions and unknown inventories safe, with coverage through the production hook normalizer.
* fix(agent-status): escape clipping marker in daemon bundle
* fix(agent-status): retire stopped teammates only when the inventory is fully attributable
Claude registers a teammate task under the whitespace-collapsed spawn description, so
compare normalized descriptions. Infer a stop from absence only when every listed
teammate matches a recorded spawn; unknown or differently formatted entries keep rows.
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Neil <neil@stably.ai>
* Native chat: never mark a message not delivered after a crash; reopening a chat stops reading the transcript
After a crash or restart, attach read a window of the agent's saved log and marked any
unconfirmed message missing from it as rejected "not delivered". Absence from a log Orca
does not own is not proof: the window's anchor moves every turn (STA-7416) and its text
filters drop tagged prompts (STA-7453), so messages the agent received were marked
undelivered and retyping them duplicated the question. The read also ran on every attach
(STA-7434).
Delete the inference: the provider history window and its adapter hooks (Claude window and
branch proof, OpenCode stored-messages reader), the submission matcher, and the attach step.
A send the crash boundary leaves unconfirmed stays an ordinary sent message; the interrupted
turn still ends through the existing settlement. Explicit live refusals are unchanged.
A queued card whose hand-off a restart left unconfirmed no longer counts as able to return
to the queue, so it stops keeping an idle chat loaded.
* test(native-chat): update restart fixture calls after history removal
* Hide rewind targets without a saved native boundary
* Run rewind boundary regression across host and renderer
* Keep incoming main package-manager lock unchanged
* Test recovered chat consumer outcomes without delivery inference
* Assign the complete cursor in recovered phone regression fixture
* Supply presentation capture in rewind boundary fixture
* fix(terminal): hand a host-exit-settled remote pane back when the host binds a new process
After #31968 settles a host-session pane on the host's exit record, a host
restart can bind the new process just after the probe read the old one's exit.
The pane then stayed ended until the tab was reopened. Keep watching the host's
accepted snapshots after that settle; once one publishes a provably different
live process for the pane, remount the tab so it attaches to it.
* fix(terminal): let a new host process reopen recovery after a failed rebind
* fix(terminal): watch for a host rebind only on a leaf-addressed pane
* fix(terminal): catch a host rebind published while the exit probe confirms
* fix(terminal): keep the host-rebind remount under the recovery window cap
* feat(orchestration): a chat worker's turn-ending error alerts its coordinator
A native-chat agent working a Dispatch whose turn ends in an error (usage limit, signed out,
provider refused, agent died mid-turn, failed start) now sends one high-priority escalation to
the Run that dispatched it, carrying the error sentence the chat recorded plus the agent's own
detail. The task stays assigned to the chat. Terminal workers already escalated on exit; both
now share one escalation helper.
On by default; a new switch on the Agent Orchestration settings page turns it off. It only
works for agents in Chat UI, so it is disabled while Chat UI is off.
* fix(native-chat): observe committed worker request failures
* Fix chat error alert fixture types and catalogs
* Keep chat failure alerts scoped to their request and journal lifetime
* fix: capture pending chat failure diagnostics before escalation
* fix: type chat failure observation fixtures and journal reads
* fix(orchestration): use checked database during worker exit
* test: bound chat failure observation and diagnostic work
* test: check ACP acquisition generation before capture
* test: bound native worker end history traversal
* test: type native exit fixture record lookup
* test: follow the journal context clear signature
* test: bound legacy display work for worker failure alerts
* test: type shared request failure rig mocks explicitly
* test: cover paired web chat error preference hiding
* perf(ssh): reuse one proxy connection for system SSH on Windows
Windows OpenSSH cannot multiplex (ControlMaster fails with "getsockname
failed: Not a socket"), so every system-SSH exec for a ProxyCommand or
ProxyJump target re-ran the proxy handshake. Hold one `ssh -N -L` through
the real proxy per session and dial later execs over 127.0.0.1 with
HostName/Port/ProxyCommand=none/ProxyJump=none/HostKeyAlias overrides.
The route starts after connect, is proven with a login before use, falls
back to per-command proxying on any failure, honors
systemSshConnectionReuse: false, and is torn down with its proxy child.
Rebased from #31731 (commits b1cdd46, 3835c2d, 6268f2f) onto current main,
including the pending-route cancellation fix from b301b6a.
Co-authored-by: Neil <neil@stably.ai>
* refactor(ssh): stop the loopback forward through process-host's tree kill
Reuse signalProcessTree instead of a hand-rolled taskkill-then-kill
fallback: it already joins an in-flight tree kill, refuses a reaped PID,
and falls back to killing the root, so the route keeps no parallel copy
of that logic.
* fix(ssh): harden the Windows loopback route against review findings
- Manual targets: take the forward's sshd port and the HostKeyAlias from
the stored endpoint; `ssh -G` ran on the label and named another host.
- Hold the forward with ServerAlive probes so a silently dead proxy ends
it and commands fall back instead of stalling on 127.0.0.1.
- Never route forwards: their argv must stay stable for orphan recovery,
and the route's own forward must take the real proxy.
- Upload sessions read the route per file, so an exited route's port is
not dialled after it closes.
- Skip the route for security-key identities, whose forward and probe
logins would each ask for an unprompted touch.
* fix(ssh): keep the loopback route for manual targets no Host block claims
The unclaimed-alias endpoint rewrite only applies to config-backed targets,
so manual targets with a configHost no block names stay eligible.
* fix(ssh): keep the held loopback forward non-interactive
The forward is an optional background login; with BatchMode=no a password or
passphrase login raised an extra prompt right after connect, which the
non-interactive probe then discarded anyway.
* fix(ssh): admit the loopback route only when it changes nothing but the route
Overriding HostName to 127.0.0.1 changes which ssh_config blocks match, so a
`Match host <real host>` block could silently switch the remote User (or
identities, certificates, known-hosts files) for every routed command. Run
`ssh -G` with and without the route options and refuse the route on any
difference outside HostName/Port/HostKeyAlias/ProxyCommand/ProxyJump.
Also ask the agent with `ssh-add -L` and refuse the route when it may hold a
security key, so the background forward and probe never request a touch.
Both checks run through process-host and fail closed.
* feat(ssh): make Windows proxy connection reuse an opt-in per-target setting
The loopback route rewrites HostName, which ssh_config can key settings on,
so it no longer turns on by itself: a new optional systemSshProxyConnectionReuse
field (default off, Windows only) enables it from the SSH host's Advanced
settings. Old clients and servers ignore or omit the field.
Admission stays as the safety net and now:
- takes the forward's sshd port and HostKeyAlias from the direct `ssh -G`,
so a manual host's own Host block cannot send the route to another sshd;
- refuses any non-routing value with a `%` token, which `ssh -G` can leave
unexpanded while a routed login expands `%h` to 127.0.0.1;
- checks the security-key and agent identities of every ProxyJump hop;
- asserts the routed config really dials 127.0.0.1 with no proxy left.
* fix(ssh): offer proxy reuse only for hosts this desktop dials, refuse ${VAR} paths
- The switch now appears only for this desktop's own SSH hosts. A paired
server's add-host dialog never shows or sends it, so an older server's
strict schema is never handed the new field, and a client's platform no
longer decides a server's setting.
- Admission also refuses `${VAR}` references, which `ssh -G` prints
unexpanded while a login expands them.
- Advanced no longer auto-opens just for the opt-in.
---------
Co-authored-by: Bingxin Xu <xubingxin.xbx@gmail.com>