Commit Graph
10813 Commits
Author SHA1 Message Date
Neil c8996d76a5 Speed up daemon smoke checks and keep compiler slots busy (#28106)
* Remove repeated daemon smoke builds and idle compiler slots

* Test compiler scheduling through its process interface

* Keep preview component below the existing line limit with a type-only split
2026-10-12 17:20:19 -07:00
OrcaWinandOrca Worker 5e83ca0edd fix(lint): move the doc preview guest-focus handoff into its own hook (#28110)
HtmlDocPreview.tsx reached 403 lines on main (#27608 + #27875), over the
400-line limit for .tsx, so lint and the pre-commit hook fail on any
branch that merges main. The focus handoff effect moves unchanged into
use-doc-preview-guest-focus.ts.

Co-authored-by: Orca Worker <orca-worker@localhost>
2026-10-12 16:58:22 -07:00
1104402f5e fix(browser): annotation card and outline follow the picked element (#27875)
* fix(browser): annotation card and outline follow the selected element

The grab overlay keeps re-measuring the picked element after the click
(scroll in any box, resize, element size changes), and the isolated
viewport bridge reports that live box so the pending card follows it,
including page zoom. The card's side pick uses its measured height.

* fix(browser): bind annotation card to selected element

* fix(browser): keep document annotation card on selection

* fix(ci): point the main sync-spawn ratchet at the shared ratchet library (#28054)

#27756's script imported baseline helpers from check-focus-read-ratchet.mjs
after #27788 had moved them into ratchet-baseline.mjs, so it crashed on start
and failed every PR that merges main. Use the library's helpers, and record the
two daemon-process launches from #27769 that the broken check never saw.

Co-authored-by: Orca Worker <orca-worker@localhost>

* fix(browser): follow selected element outer-box size

* refactor(chat): give the start-refusal resumable table its own module (#28085)

#27946 and #26684 together put agent-session-failure-words.ts at 304 lines,
over max-lines, which fails lint and the pre-commit hook on every merge of
main. The refusal-code table is standalone with one reader, so it moves out.

Co-authored-by: Orca Worker <orca-worker@localhost>

---------

Co-authored-by: OrcaWin <alpha-eng@stably.ai>
Co-authored-by: Orca Worker <orca-worker@localhost>
2026-10-12 16:22:05 -07:00
Jinwoo HongandClaude 7154e9045f fix(terminal): keep the tab when a reattach finds its process gone (#28067)
* fix(terminal): keep the tab when a reattach finds its process gone

Co-Authored-By: Claude <noreply@anthropic.com>

* test(e2e): read the paired retention heap after two collections

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(terminal): clear the absent owner through the replaced-pane binding writer

Co-Authored-By: Claude <noreply@anthropic.com>

* test(e2e): fail the paired retention check when the heap cannot be read

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(terminal): refuse the respawn without a Store; tighten the binding fixture and stop docs

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-10-12 19:16:57 -04:00
Brennan Benson acb6299191 docs(native-chat): say why /clear is refused while the agent works instead of queued (#28101) 2026-10-12 16:12:00 -07:00
OrcaWinandOrca Worker 7909732283 perf(claude): run the Windows Claude hook as a signed native claude-hook.exe (#27719)
* perf(claude): run the Windows Claude hook as a native orca-hook.exe

Each Claude hook event on Windows started cmd.exe (plus the user's cmd
AutoRun) and then curl.exe on top of the agent's own shell. A small signed
Rust binary now does the same synchronous POST in one process, under Git
Bash, PowerShell and direct CreateProcess alike.

- native/windows-agent-hook: std-only crate, same contract as the .cmd hook
  (prints {}, env guards before stdin, endpoint re-read, 0.5s/1.5s caps,
  waits for the listener reply, always exit 0). Its installed file name
  selects the agent profile, so the registration stays a bare path.
- Packaged as resources/bin/orca-hook.exe (SignPath signs it with the other
  inner PEs); afterPack fails on a missing or wrong-machine binary.
- Claude install copies it verbatim to ~/.orca/agent-hooks/claude-hook.exe
  (temp + rename, running copy moved aside) and registers it; it falls back
  to the .cmd hook when the bundle is missing, unsigned or cannot be copied.
  Only Claude opts in, via windowsNativeHookExecutable.
- The managed-command matcher sweeps .exe entries alongside .cmd/.ps1/.sh.
- The CLI launcher and hook builds share windows-rust-executable-build.mjs,
  which now also checks the PE machine.

Refs #20913

* fix(build): publish native Windows binaries atomically so parallel builds cannot collide

The Windows CI lane runs every orca-hook.exe suite in one vitest
invocation, each building through build-windows-agent-hook.mjs. The build
copied straight onto native/windows-agent-hook/.build/orca-hook.exe, and
CopyFile opens its destination unshared, so a second build (or a scanner
still holding the fresh image) failed with EBUSY.

- windows-rust-executable-build.mjs skips the publish when the output
  already has the built bytes, otherwise stages a per-process temp file and
  renames it over the output, retrying briefly while another handle holds it.
- The test fixture hands each suite a private copy of the build output, so
  no suite reads or starts the shared file while another build replaces it.

Refs #20913

* refactor(claude): simplify the native Windows hook to one Claude binary

Review follow-up on the native hook (#20913): the same behaviour with less
machinery.

- The binary is built, shipped and installed as claude-hook.exe, so the
  installed name matches its PE OriginalFilename. It is Claude-only, so the
  file-name profile table and the per-agent opt-in flag are gone; the
  registration is gated by the existing Claude-only usesWindowsEntry check.
- Install and refresh reuse writeManagedScript / refreshManagedScriptIfPresent
  (now accepting bytes): temp file, rename, skip when identical. A copy whose
  hook is still running cannot be replaced; install then registers the .cmd
  hook and refresh keeps the old build until a later start, instead of the
  rename-aside and *.retired sweep.
- The install-time Authenticode-presence check is dropped. Release CI now
  requires resources\bin\claude-hook.exe to be signed (release-cut and the
  signing rehearsal), next to resources\bin\orca.exe.
- getStatus counts either Windows form through findManagedEventHookForms in
  hook-settings.ts, replacing managed-event-hook-status.ts.
- Rust: plain Claude constants, one endpoint-file lookup (last assignment
  wins, as under `call`), one request write and a deadline-bounded drain.
  Guard order and form encoding are unchanged.
- Build: PR CI builds the hook once before the Windows tests, so the suites
  only read it; the launcher publish goes back to copyFileSync and the retry
  loop and its test are gone. The PE-machine check lives only in afterPack.
  Cargo.lock joins both crates' build fingerprints, and the launcher's alias
  exports are removed.

Refs #20913

* test(ci): run the managed hook stdin-lifecycle suite in the Windows lane

Its claude-hook.exe case (and the other Windows-only cases) are gated per
test, so the lane-registration guard did not require the file and it ran
only on Linux, where those cases skip. Register it in both Windows lists.

Refs #20913

* feat(agent-hooks): ship the native hook as orca-agent-hook.exe <agent>

The native Windows hook (#20913) becomes one general binary that stays
Claude-only in behaviour.

- Named orca-agent-hook.exe everywhere: build output, PE OriginalFilename
  and InternalName, resources\bin, the afterPack check, the installed copy
  in ~/.orca/agent-hooks, and the release signed-target lists.
- It takes the agent as its first argument and Claude registers
  `<path> claude`. The CLAUDE_JOB_DIR exit and the Grok/Devin drain belong
  to that agent; an unknown or missing agent answers {} without reading
  stdin. The managed-command matcher owns `orca-agent-hook.exe claude` and
  still owns the old claude-hook.exe entry, so installs migrate; install
  deletes a leftover claude-hook.exe when nothing runs it.
- A copy still running from the previous build no longer forces the .cmd
  fallback. Identical bytes are not rewritten; otherwise the temp file is
  renamed over the target, and when Windows refuses because the image is
  mapped, the running copy is renamed to orca-agent-hook.exe.old first.
  Install deletes a stale .old; only when that copy is also still running
  does the install fall back to .cmd. writeManagedScript and
  refreshManagedScriptIfPresent take the aside path, sharing one helper.

Refs #20913

* fix(agent-hooks): roll back a failed aside rename and keep an installed hook exe

Design-review follow-up on the native hook (#20913).

- If the running copy was moved to orca-agent-hook.exe.old but the new
  file then could not take its name, the copy is renamed back before the
  error is rethrown, so settings.json never points at a missing exe. The
  rename helper now owns the `.old` name, next to the comment that
  explains it; a mocked-fs unit test covers both the sync and async paths.
- A failed write, or a build with no bundled exe, keeps registering a copy
  that is already installed (every build knows `claude`), so a dev checkout
  and an installed build no longer flip settings.json between forms.
- The claude-hook.exe migration is gone: no release ever shipped that
  name, so the legacy sweep, the `.exe` matcher needle and their test are
  removed.

Refs #20913

* fix(agent-hooks): derive the native hook's agent with win32 path rules

getWindowsNativeHookAgent took the host's basename of the managed script
path, so on a POSIX host (Linux CI) a Windows path was not split and the
registered command became `orca-agent-hook.exe C:\...\claude`. It now takes
the path and applies path.win32.basename itself. The install destination
and the `.old` aside stay on host paths: they name files the host opens.

Refs #20913

---------

Co-authored-by: Orca Worker <orca-worker@localhost>
2026-10-12 16:09:30 -07:00
Jinwoo HongandClaude a1f8e6482b fix(layout): save terminal rows in the tab groups' order (#28066)
* fix(layout): save terminal rows in the tab groups' order

Co-Authored-By: Claude <noreply@anthropic.com>

* test(e2e): read the paired retention heap after two collections

Co-Authored-By: Claude <noreply@anthropic.com>

* test(e2e): fail the paired retention check when the heap cannot be read

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(layout): name the headless flow behind the row-id group fallback

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-10-12 18:55:25 -04:00
NeilandClaude Opus 5.5 125007b680 chore: upgrade terminal dependencies to matching payload betas (#27935)
* chore: upgrade terminal dependencies to the matching payload betas

* test: harden xterm payload contract, align mobile pins, fix serialize-at-ref across version bumps

- Cover foreign-payload replacement and the shared blank-cell reset in the
  installed image payload contract (both mutants previously survived).
- Bump mobile's xterm, webgl and unicode11 pins to the root's betas.
- Resolve the ref's own serialize version and patch so the differential
  fuzz baseline builds across a version bump.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 15:53:42 -07:00
Brennan Benson 2233fd3669 feat(native-chat): each Claude/Codex chat gets its own permission mode (#26116)
Each native Claude and Codex chat now has its own permission mode (Ask for approval, Accept edits for Claude, Approve for me, Full access), shown and changed from a button right after "+" in the composer. A change takes effect before the next message: the agent restarts quietly and resumes the same conversation, never interrupting a reply or background work. New chats start from Settings → Chat → Permissions, which is seeded once from Agent Permissions. The mode is stored as an ordinary chat option, and the restart reuses the existing "replace the agent before the next message" step.
2026-10-12 15:36:45 -07:00
Brennan BensonandClaude f89d51ba02 Share any HTML file from the editor and the remote preview (#27608)
* Share any HTML file from the editor and the remote preview

HTML could only be shared as an artifact from a local file:// browser tab.
The editor offered Share only for Markdown, and the document preview that
renders HTML on SSH and paired hosts had no Share at all.

The editor now shares HTML files too, and the document preview's toolbar
gets the same Share button, reading the page from its owning host. Both use
one source key per file per host, so either surface finds the link the
other published.

Co-Authored-By: Claude <noreply@anthropic.com>

* Share every file under one key per host, and preview Share from its grant's host

The editor, the rendered preview and a local browser tab each built their own
share key, so one file could publish to several links: a Windows path spelled
C:/ in one place and C:\ in another, and the preview keyed from whichever host
the workspace resolved to at render time, not the host it was reading from.

One key function now serves every surface, with main's formats (local path,
the CLI's SSH key, ['editor', env, host, path] with the 'remote' fallback) and
Windows drive/UNC paths spelled with backslashes.

The preview's grant handle keeps the location it was minted for. Share reads
the page through that location and keys from it, so a later host change can't
move either; an SSH grant keeps its connection id, so its read fails closed
instead of reading a local file.

Publishing prepares the request once per click, before sign-in and the auth
retry, so a retry publishes what the user clicked Share on.

Co-Authored-By: Claude <noreply@anthropic.com>

* Pin preview Share key parity with the editor for server-proxied SSH workspaces

A preview of an SSH workspace a paired server proxies mints its grant on that
SSH target, so it already keys the file the same way the editor does; the new
test locks that in. Also corrects the SSH read comment: the connection id is
kept because dropping it would read the path on this machine.

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-10-12 15:30:48 -07:00
Brennan Benson d8a89f169b fix(sidebar): stop the workspace link icon lighting up on hover (#28078)
* fix(sidebar): stop workspace metadata icons brightening on hover

The link, notes and provenance icons on a sidebar workspace row only open
the details hover card; clicking them does nothing extra. The hover colour
made the link icon look clickable.

* chore: remove unrelated lockfile changes
2026-10-12 15:22:08 -07:00
Brennan Benson 6440d3c520 fix(mobile): keep a not-sent message in place on the phone, using the desktop's logic (#27936)
* fix(native-chat): list not-sent rows at their journal places

The shared projection appended rows the host recorded but never delivered after every other row,
leaving the reader to sort them. A reader that draws the list as given showed an old not-sent
message under newer turns. Merge them in at their journal places, after the stop rows are built,
so the list is already in order. The desktop sorts by the same order, so what it draws is unchanged.

* refactor(native-chat): move the recorded-rejection line into shared words

The desktop's words for a message the host recorded and then rejected now live beside the other
rejection words, returning notice parts each client renders its own way, so the phone can say the
same thing. The desktop calls it with the same inputs; its words are unchanged.

* refactor(native-chat): one projection behaviour for rejected rows

Every reader now draws a message the host recorded and then rejected where it was sent, marked
not sent, so the rejectedInPlace option goes. The phone was its last off caller apart from the
host's conversation outline, which now leaves not-sent rows out itself; a test proves the outline
is the same as if the rejected message had never been sent.

* fix(mobile): show a not-sent message in place with the reason under it

The phone now draws a message the host recorded and then rejected where it was sent, as the desktop
does, with one small line under the bubble in the desktop's words. The line is built from the
journal the view already receives; when a start-failure or sign-in row already gives the reason,
it says only that the message was not sent. The load-earlier header moves to its own file to keep
the view under its line limit.

* fix(mobile): a send the host recorded is never handed back

When the send's own reply said the host recorded the message and then rejected it, the phone put
the text back in the composer with a banner, though the host and the desktop keep it in the chat.
A recorded send now settles like any other: its row draws it in place as not sent (or its kept
card holds it), and the composer stays empty. The banner's failure-fact plumbing, fed only by that
branch, goes with it; the transcript states the sign-in guidance once.

* fix(mobile): keep each structured send on the shared optimistic list

Once a not-sent row stays in the chat, matching a pending bubble by its text breaks: resending the
same words after a not-sent message never reached the count the bubble waited for, so a stray copy
stayed, and an acknowledgement lost on that resend showed a false "Delivery unconfirmed".

A structured send is now one record, kept by the structured session under the id it is sent with.
Three parts match the desktop's sender: the id, the drawing (the shared projection draws it at the
tail until the host's row under that id lands, in the same pass) and the settle rule (the host's own
record of it, a submission, a card, or a card's hand-off, drops it by the one rule the desktop now
shares). The rest stays the phone's own: the bubble shows once the host accepts the send, not from
the tap; a send whose answer was lost is held undrawn and says "Delivery unconfirmed" only if its
deadline passes first, instead of going back to the composer; records are kept whether or not a
chat view is open; and one send in flight is the composer's rule, not the sender's. Its photo is
kept under the row id it shares with the host's row, so it shows on the bubble, the landed row and
a not-sent row. The drafts' text matching, with its older queued-card matching, now serves only
terminal-backed chats, whose transcripts carry no ids.

* fix(native-chat): merge not-sent rows by the transcript order alone

The merge kept a rule for rows with no journal place that no caller reaches and that the desktop's
sort does not have; drop it, and say what merging after the stop rows actually keeps. A test pins
that the list the phone draws as given is already in the desktop's sorted order, with a moved stop
row, a retry run, a cut-turn notice, a held card and two not-sent rows.

* fix(mobile): draw the not-sent line as the desktop does

Red and capped to the bubble's width, like the desktop's line and the phone's own returned-card
caption, and only when there is a notice: the row had a second, unreachable source of words. A
send a Stop took back is no failure, so it no longer rebuilds the notices on every streaming frame.
2026-10-12 15:16:23 -07:00
Brennan Benson e83e40e258 Session History shows OpenCode, Grok, OMP and Pi chats as chats and refuses a second terminal writer (#27183)
* refactor(chat): answer transcript adoption and Session History from the agent registration

Claude and Codex's transcript importers and Session History ownership (row id,
resume-command grammar) move onto their runtime registrations as required-nullable
fields; every other agent writes null. agentSession.agents publishes the derived
transcriptAdoption/sessionHistory flags, and a client reads a host that omits them
(or lists nothing) as the old build's answer. Strict parity: only Claude and Codex
answer yes anywhere.

* refactor(chat): one Session History ownership index, binaries from row agents, fork offer from fork argv

Review round 1: the duplicate-adoption check and Session History ownership read one
index keyed by the registration's row id (an importer now requires sessionHistory);
resume-command binaries derive from rowAgents through the TUI agent config; the
Resume in New CLI offer asks getAgentForkArgv instead of naming Claude and Codex.
Strict parity: only Claude and Codex answer yes.

* feat(chat): Session History owns OpenCode, Grok, OMP and Pi native chats

An OpenCode, Grok, OMP or Pi native chat's conversation listed in Session
History as a plain terminal row, and resuming it ran the agent's CLI as a
second writer to the conversation the chat was still using. Each agent's
registration now fills `sessionHistory`, so those rows show as the chat
(its name, opening the chat) and Orca refuses a terminal resume of an owned
conversation, as it already did for Claude and Codex.

- OpenCode lists under `opencode` and `opencode2` (2.x) rows by its ses_ id;
  `-s/--session`, `-c/--continue` resume, `--fork` does not.
- Grok: `-r/--resume/--load` and `-c`; a non-UUID value names a title, so it
  is read as target-less; `--fork-session` does not resume.
- OMP: `-r/--resume/--session` take an id, any prefix of it, or the session
  file's path; bare `--resume` and `-c` are target-less.
- Pi: rows are listed by the session file name's id while the chat holds the
  file's path; `--session <id|prefix|path>`, `-c`, `-r` (picker).
- An optional `resumeTargetMatches` on the registration covers prefix and path
  targets; it defaults to exact equality.
- Each agent now reads every token naming its own binary, so another agent's
  name earlier in a command (`claude --model pi`, `cd ~/src/codex && claude
  --resume X`) no longer hides a resume. For Claude and Codex this only ever
  refuses more than before.
- A renamed OpenCode chat tab names its `opencode2` row too.

* fix(chat): show owned rows only to clients that open the chat, without a resume command

- The list and search projection now asks, per owning agent, whether the
  requesting client opens that agent's chat. Current mobile renders only Claude
  and Codex chats, so it no longer receives OpenCode/Grok/OMP/Pi rows owned by a
  chat (a tap there opened an empty terminal and showed agent_session_conflict).
  Claude and Codex keep their rule: any client that reads structured sessions.
- An owned search hit opens its chat, so it no longer carries a terminal resume
  command (all agents; `orca` CLI search stopped printing one for a chat).
- OpenCode/Grok/OMP/Pi flags match case-sensitively, as their CLIs read them,
  so `rg foo src/pi -C 3` is not `pi -c`. Claude and Codex still fold case.
- A test feeds every resume command Orca builds for an owned row of each agent
  (Session History, live-tab argv; posix, PowerShell, cmd) into the guard.

* fix(chat): keep the resume guard linear and skip chat-tab lookups with no move

- Each agent binary mention in a command now reads its arguments only up to
  that agent's next mention, which reads the rest, so every token is parsed
  once per agent. A 200 KB text naming agents throughout took 9.4 s on the
  host's main process; it now takes about 90 ms. Every refusal before still
  holds: a later mention still reads its own resume.
- Right-clicking a native chat tab looks up its Session History row only when
  the agent's CLI can fork a conversation, the one move a chat tab offers, so
  OpenCode, Grok, OMP and Pi chat tabs run no list or rescan for nothing.
  Claude and Codex are unchanged.

* fix(chat): read each shell command once in the resume guard

The guard tokenizes the text once and splits it into shell commands at
&&, ||, ;, |, & and newlines (a newline after a line-continuation mark stays
inside its command). In each command, each agent reads its arguments from its
first binary token to the command's end; a later token naming it is an
argument. A `claude` path inside a Claude command no longer ends that command,
so `--session-id <owned>` still cancels a fork after it, and a binary-named
path between a resume and its fork flag keeps the fork a fork. Binary names
are looked up once per token, which keeps a 16 MB send to about 2 s.

Against main's parser, a Claude/Codex command is now allowed only when main
refused it for a flag in a later shell command (`claude ; echo --resume X`).

* fix(chat): end a resume-guard command at a bare carriage return

A PTY's Enter, and many orca terminal send texts, end lines with \r. The guard
now treats \r and \r\n as line breaks like \n, including after a line
continuation mark, so a later line's resume is read as its own command.

* refactor(chat): drop the transcript-adoption and Session History wire flags

agentSession.agents no longer publishes transcriptAdoption or sessionHistory, so
there is no new field for older and newer peers to reconcile. Their only
consumers were renderer gates that could answer from what they already have:
"Resume in New Native Chat" eligibility goes back to main's rule (adopting a CLI
conversation is Claude/Codex-only, and the host still refuses any other agent from
its registration's transcriptImport). Removes the AgentSessionDeclaredCapabilities
split, the older-host decoder shim and the generic host-flag reader.

* refactor(chat): keep main's Claude/Codex rule for the CLI fork offer

"Resume in New CLI" and the chat-tab menu read the agent name again instead of
building a synthetic provider session to ask the fork builder, and the chat-tab
lookup loses its host Session History flag gate. Only Claude and Codex CLIs fork,
so only their chat tabs look up a row, exactly as on main: no new list requests.

* refactor(chat): one registration-aware ownership index, no registration union

- listStructuredProviderSessionOwnership reads each record's registration for its
  Session History row id; the callback parameter and its one-function wrapper
  module are gone (no import cycle: checked value and type imports).
- StructuredAgentCliConversations is a plain type: transcriptImport is nullable
  and sessionHistory is required, since every registered agent now owns its rows.
  The "importer needs history" union and its null checks go.
- A test pins that no two registrations claim the same Session History row agent,
  since the owner lookup takes the first match.

* fix(chat): read resume commands into words as a shell does

Separators glued to a word (`opencode -s id; echo`, `cd /x&&claude -r id`) now end the
command, and quotes are removed inside a word, so `--session="id"` reads as the id. Applies
to every agent, Claude and Codex included. The acp-and-pi allowed() helper now asserts.

* fix(chat): count an agent's name only where a shell would run it

terminal.send checks agent prompts too, and agentPrompt does not keep the text from a shell (it
falls back to a plain PTY write), so prompt text like "Fix the Pi --resume flag" was refused.
The resume check now reads each command's program: its first word past VAR=x prefixes, a path,
and env/exec/command/sudo/npx/bunx/pnpm dlx. Every command Orca's builders produce, Claude and
Codex included and with a user's command override, is still refused.

* refactor(chat): compare resume ids in any case; Claude uses the shared option reader

The default target match now ignores case (ids are UUIDs or ses_ ids), so Grok's override,
which only repeated it, is gone. Claude folds its flags and reads them with
resumeInvocationFromOptions instead of repeating the `=` handling.

* perf(chat): read terminal text in one linear pass

A repeated regex group overflowed the stack on a 16 MB word. A sticky-regex scanner reads words,
quoted runs and separators in one pass; an unclosed quote is searched for once per quote kind.

* refactor(chat): read a command's resume only when its program is an agent

* fix(chat): read an agent run under nohup, time or wsl as the command's program

* chore: restore pnpm-lock.yaml

* fix(chat): keep the CLI's own title on OpenCode, Grok, OMP and Pi chat rows

Session History shows these agents' chat conversations under the title their CLI saved, as it does
for their terminal conversations; the chat tab's name and renames stay on the tab. Claude and Codex
rows keep the chat's name, as on main.

* feat(chat): Resume in New CLI for Grok and Pi chats

A Grok or Pi chat's Session History row and tab menu now offer "Resume in New CLI", like Claude and
Codex: the CLI opens a copy of the conversation (grok --resume <id> --fork-session, pi --fork <file>)
and the chat keeps its own. OpenCode's interactive CLI and OMP have no way to open a copy, so their
chats keep only Resume.

* fix(chat): offer Resume in New CLI only when the copy command can be built

A Pi chat found by search on a remote Orca server has no session file path, so its copy command
can't be built; the row no longer offers the button there.

* fix(chat): refuse pi --session-id of a conversation a chat owns

`pi --session-id <id>` reopens that session when it exists, so it is a second writer like --session.
2026-10-12 15:14:01 -07:00
OrcaWinandOrca Worker 9c1d5d2fb7 perf(pty): check WSL working directories off the main thread (#27705)
* fix(wsl): start host-side wsl.exe probes through process-host so they never open a console

wsl --list, wsl --status, the WSL home and directory probes, and WSL UNC
deletes were started with node:child_process directly. The synchronous ones
omitted windowsHide, so each spawn from the GUI-subsystem main process
flashed a conhost window that could take keyboard focus. Route them through
@orca/process-host, which pins windowsHide, and keep the execFile-shaped exit
errors the availability cache classifies.

Refs #27390

* build(tc): list wsl-exe-process.ts in the web typecheck project

wsl-availability.ts is in the explicit web file list and now imports it.

* refactor(wsl): drop the unused sync availability probe; classify ProcessResult directly

No production code calls the sync isWslAvailable (the IPC and RPC paths
already assert they never do), so delete it and its sync guest-probe twin
instead of porting them. The async probe now reads the --status
ProcessResult itself: an exit code is definitive, a timeout or signal kill
is retryable, ENOENT on spawn is definitive. The WSL2-missing-kernel exit
codes live in one named set. Availability tests now drive the async probe.

* refactor(wsl): shrink wsl-exe-process to a spec builder and stdout reader

With the availability cache classifying ProcessResult itself, nothing reads
the execFile-shaped code/status error any more. wslExeFailure returns a
plain reason string; wslExeStdout throws it; the UNC delete folds it into
its own error. The home probe builds its argv with buildWslExecArgs.

* perf(pty): probe WSL working directories off the main thread

The in-process terminal launch path validated a \wsl.localhost cwd with a
synchronous wsl.exe probe, blocking the main process ~70 ms per launch
(300-530 ms when the distro is stopped, up to the 5 s timeout). The launch
now awaits the existing async probe and the synchronous one is removed.

Refs #20497

* fix(pty): let a canceled spawn abandon its pending WSL cwd probe

The local spawn awaited the WSL directory probe without its cancellation
signal, so a pane closed mid-probe waited up to the 5s wsl.exe timeout
before noticing. Pass the signal through validateWorkingDirectoryAsync and
report an abandoned probe as the spawn's own cancellation, as the daemon
spawn preflight does.

* test(wsl): mock process-host in the empty running-list case from main

* docs(wsl): the availability probe no longer blocks; drop the stale word

* build(ratchet): prune the wsl.exe sync launches this branch removed

* build(ratchet): prune the sync WSL cwd probe this branch moved off the main thread

---------

Co-authored-by: Orca Worker <orca-worker@localhost>
2026-10-12 14:53:09 -07:00
OrcaWinandOrca Worker fb5d016887 fix(wsl): start every host wsl.exe call through process-host so no console window flashes (#27628)
* fix(wsl): start host-side wsl.exe probes through process-host so they never open a console

wsl --list, wsl --status, the WSL home and directory probes, and WSL UNC
deletes were started with node:child_process directly. The synchronous ones
omitted windowsHide, so each spawn from the GUI-subsystem main process
flashed a conhost window that could take keyboard focus. Route them through
@orca/process-host, which pins windowsHide, and keep the execFile-shaped exit
errors the availability cache classifies.

Refs #27390

* build(tc): list wsl-exe-process.ts in the web typecheck project

wsl-availability.ts is in the explicit web file list and now imports it.

* refactor(wsl): drop the unused sync availability probe; classify ProcessResult directly

No production code calls the sync isWslAvailable (the IPC and RPC paths
already assert they never do), so delete it and its sync guest-probe twin
instead of porting them. The async probe now reads the --status
ProcessResult itself: an exit code is definitive, a timeout or signal kill
is retryable, ENOENT on spawn is definitive. The WSL2-missing-kernel exit
codes live in one named set. Availability tests now drive the async probe.

* refactor(wsl): shrink wsl-exe-process to a spec builder and stdout reader

With the availability cache classifying ProcessResult itself, nothing reads
the execFile-shaped code/status error any more. wslExeFailure returns a
plain reason string; wslExeStdout throws it; the UNC delete folds it into
its own error. The home probe builds its argv with buildWslExecArgs.

* test(wsl): mock process-host in the empty running-list case from main

* docs(wsl): the availability probe no longer blocks; drop the stale word

* build(ratchet): prune the wsl.exe sync launches this branch removed

---------

Co-authored-by: Orca Worker <orca-worker@localhost>
2026-10-12 14:51:32 -07:00
OrcaWinandOrca Worker 424437a206 fix(claude): register the Windows statusLine so usage updates without Git Bash (#27710)
* fix(claude): register a statusLine Windows PowerShell can run

Claude runs the statusLine under Git Bash when present and otherwise under
PowerShell, which cannot parse the POSIX runtime-home command Orca registered.
On Windows without Git Bash the statusline never posted rate_limits, so the
live Claude usage feed went dark. Register the same shell-neutral .cmd
invocation the lifecycle hooks use; the old form is still recognised as
Orca-owned and replaced, and a user's own statusLine is never touched.

* fix(claude): keep the statusLine free of a per-tick PowerShell launch

A profile path the bare form cannot carry (a space, non-ASCII, quotes) fell
back to the encoded PowerShell launcher, which would start a hidden PowerShell
on every statusline tick and print "{}" if the script went missing. Register
the bare .cmd path only when it is cmd-safe and keep the runtime-home form
otherwise, so Git Bash users on such paths still run the .cmd directly.
Fold the registration tests into claude-status-line-install.test.ts and add a
real Git Bash run for a profile path with a space.

* test(claude): pair the stubbed win32 platform with win32 basename

The Windows statusLine case fed a Windows path to getManagedCommand on a
Linux runner, whose posix basename does not split backslashes. Production
only takes that branch on real Windows, where node:path is path.win32.

---------

Co-authored-by: Orca Worker <orca-worker@localhost>
2026-10-12 14:51:28 -07:00
OrcaWinandOrca Worker 1df7d7dd7a fix(daemon): verify daemon PID identity from the native Windows process table, not PowerShell (#27703)
* fix(daemon): verify daemon PID identity from the native Windows process table

The Windows daemon PID check forked powershell.exe Get-CimInstance through
node:child_process with no windowsHide, flashing a console and costing
~850ms per check. Read the command line and creation time from the shared
windows-process-table reader instead, and route the POSIX ps queries
through @orca/process-host.

Refs #27390

* refactor(daemon): drop the ps migration and trim the PID identity tests

Keep #27703 to the PowerShell removal: restore the macOS/Linux ps paths,
their tests and the process-host baseline entry. Collapse the mocked
Windows identity cases into one table, keep only the real-addon native
case, and move the creation-time fallback beside queryWindowsProcess.

---------

Co-authored-by: Orca Worker <orca-worker@localhost>
2026-10-12 14:49:08 -07:00
OrcaWinandOrca Worker 50cc50d33e refactor(chat): give the start-refusal resumable table its own module (#28085)
#27946 and #26684 together put agent-session-failure-words.ts at 304 lines,
over max-lines, which fails lint and the pre-commit hook on every merge of
main. The refusal-code table is standalone with one reader, so it moves out.

Co-authored-by: Orca Worker <orca-worker@localhost>
2026-10-12 14:43:03 -07:00
Brennan Benson edd808479b feat(settings): move Chat UI's chat settings from Experimental to the Chat page (#28050)
* feat(settings): move Chat UI's chat settings from Experimental to the Chat page

* chore(i18n): sync catalogs for the moved chat settings

* fix(settings): hide the Behavior group when search matches only a switch the host lacks

* Keep Chat behavior search aligned with translations and host support
2026-10-12 14:40:52 -07:00
OrcaWinandOrca Worker 889ddcc6bd fix(agent-hooks): register forward-slash Windows hook paths for Grok, Devin, ZCode and Antigravity (#27713)
* fix(agent-hooks): register forward-slash Windows hook paths for Grok, Devin, ZCode and Antigravity

These four agents registered their native-Windows status hook as a bare
backslash path. A Git Bash hook host strips the backslashes, the command exits
127, and no status event reaches Orca. Use the same direct forward-slash form
Claude, Codex and Muse already register, keeping the cmd-safe gate and the
encoded PowerShell fallback. Existing backslash entries still match the
managed-command matcher, so the next install replaces them and keeps user hooks.

* perf(hooks): register direct .cmd hooks for Gemini, DSH, Kiro, Droid and Command Code on Windows

On a cmd-safe profile path these agents now register the managed .cmd path
itself instead of a `powershell.exe -EncodedCommand` launcher, so each hook
event no longer pays a second PowerShell start-up on top of the runner's own
shell. Profiles the bare path cannot carry (spaces, non-ASCII, UNC) keep the
encoded launcher. Same pattern as Claude (#18875) and Muse (#26755).

Refs #20913

* test(agent-hooks): expect forward-slash Antigravity hooks after the Unicode upgrade

ASCII profiles now register the direct forward-slash wrapper path, so the
upgrade-delivery test expects that form and also runs it through Git Bash.
Unicode profiles keep the encoded launcher.

* test(agent-hooks): assert the registered hook lives under the mocked home

Pins that the os mock reaches the agents' node:os imports, so the host test
never installs into the real profile.

* refactor(hooks): one Windows batch-hook helper and one payload-delivery table

- Add wrapWindowsBatchHookCommand (bare .cmd path, encoded launcher
  otherwise) and use it for Muse, Gemini, DSH, Kiro, Droid and Command Code.
- Fold the five agents into windows-hook-payload-delivery.test.ts as rows
  run under each vendor's shell, from a plain and a spaced profile, with a
  stale encoded registration seeded per row; drop the separate runner suite
  and its CI list entries.
- Replace the Windows-only per-agent registration tests with ones that stub
  process.platform, so they run on every CI OS.
- Fix stale launcher, helper and DSH comments.

Refs #20913

* refactor(agent-hooks): register Grok, Devin, ZCode and Antigravity through wrapWindowsBatchHookCommand

Use the shared batch-hook helper from #27717 instead of an inline direct-or-cmd
fallback. A UNC home is cmd-safe but has no drive letter; the old fallback
registered it as a bare backslash path that Git Bash cannot start, while the
helper sends it to the encoded launcher.

wrapWindowsCmdHookCommand has no production callers left, so it is removed and
its tests point at the helper. Grok, Devin and ZCode join the Windows payload
delivery table with a legacy backslash entry to sweep, replacing the separate
host test, and the Antigravity migration test now runs on every platform.

---------

Co-authored-by: Orca Worker <orca-worker@localhost>
2026-10-12 14:31:04 -07:00
Brennan Benson c0e2b73395 feat(native-chat): require Codex 0.136.0 for chats (#26684)
* Require Codex 0.136.0 for native chats

* Add host-owned Codex install and update actions

* Bind Codex checks and maintenance to the selected installation

* Resolve Windows updater cleanup through system helpers

* Use one child environment for Codex checks and launch

* Expire Codex readiness and reconcile current host jobs

* Resolve Codex checks against the child command search path

* Restrict global npm repair to the selected Codex install layout

* Pin the host updater directory through Windows supervision

* Synchronize Codex maintenance fixtures on readiness

* Unify Codex notices and recover version-refused chats

* Route Codex recovery to the operation that remains

* Preserve Codex setup execution errors in the job log

* Preserve UTF-8 in Codex setup logs

* Remove unused Codex host installation detector

* Require host administration for Codex maintenance

* Show a missing Codex explanation once

* test(native-chat): give scripted Codex runtimes their installation fact; keep the registry out of the relay

Runtime tests that drive a scripted Codex app-server still ran the new
version check against whatever codex was on the machine's PATH: refused
on CI (no codex), and on a dev machine it ran the real codex --version.
A scripted transport now must state the Codex it stands for
(readCodexInstallation), asserted at install, and the tests get it from
one scriptedCodexTransport fixture.

The relay's Codex maintenance path reached @orca/windows-registry through
resolvePathEnvKey's re-export in windows-environment-path; import it from
its registry-free module instead.

Also update the catalog-probe test for the shared child environment.

* refactor(native-chat): drop the Update Codex action; keep Install for a missing Codex

A too-old Codex now gets only the plain message ("Codex X is too old for
chats. Update to 0.136.0 or newer.") with no button and no job. The
existing rechecks (window focus, the <=30 s expiry of a saved result)
retry the refused chat once the user has updated Codex themselves.

A missing Codex keeps the Install Codex button: `npm install -g
@openai/codex` with the npm on the chat's PATH, as a host-run job with
its log, then a recheck and recovery. A configured Command that is
missing gets the message only, since a global npm install would not
land at that path.

Removed: the `codex update` command and its 0.126 floor, manual
instructions, npm layout and launcher identity detection, the action
descriptor (install|update) and its titles, the legacy capability, and
the SSH relay maintenance path (structured chat never runs over SSH or
WSL), so the relay preflight handler is back to main's.

* refactor(native-chat): keep Install Codex in Settings only

When Codex isn't installed, main already hides Codex from the New tab menu,
so the in-chat Install button was almost never reachable. Settings → Agents
already shows "Codex isn't installed." with Install Codex.

- Chats and the new-workspace composer now only state "Codex isn't
  installed." (no button, no install job from the chat).
- The Install action, its busy spinner and the host-busy subscription move
  into the Settings Codex row, the only place that starts the job.
- The store no longer re-checks every other workspace context when a job
  finishes. A refused chat recovers through the same re-check a too-old
  Codex uses (window focus, or the <=30 s expiry of a saved result).
- The chat recovery key no longer includes the install job id.
- Tests: Settings drives the install; a new test proves a chat refused for
  a missing Codex recovers through focus after a Settings install, not
  through the job; the expiry recovery test covers missing and too old.

* refactor(codex): check chat compatibility when acquiring the child

* Restore workspace submit guard and simplify Codex maintenance

* Keep only the Codex chat minimum version
2026-10-12 14:30:27 -07:00
Brennan BensonandClaude 6af34a4d99 fix(chat): name the agent when it refuses a message (#27946)
* refactor: move provider-retry pieces into a sibling words file

Pure move of the six provider-retry translation pieces out of
agent-session-failure-words-text.ts, which sits at the 300-line limit.
No key or behaviour change.

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(chat): name the agent when it refuses a message

"The provider did not accept this message" becomes "{{agent}} didn't
accept this message" ("The agent" when the name is unknown), in English
and every locale. The providerRejected failure kind is unchanged.

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-10-12 14:29:38 -07:00
Neil be25938415 [CI] Reduce repeated test imports, waits and unrelated jobs (#28069)
Preserve all 52 Markdown cases while consolidating one file and importing existing direct owners. Advance editor debounce windows through public selective timers, exclude unrelated lint tests from Performance PR triggers, and remove a pane assertion case entailed by retained coverage.
2026-10-12 14:22:19 -07:00
Brennan Benson fe1f0223af Native chat: read one saved row per subagent (part 1 of 2) (#27914)
* feat(chat): read one saved row per subagent

Adds the singular `subagent` journal block (one row per child, revised in
place, with a plain-text twin for older clients) and teaches every reader
to draw it: desktop and phone fold a turn's sibling rows into the existing
"Ran N subagents" row, the client and off-page rosters name children from
their own rows, the reopen pass settles a row a dead host left working,
and the phone and worker-transcript wires bound it. No producer writes the
block yet; that switch lands in the follow-up.

* fix(chat): open subagent background flag, separate nested group ids, page window skips subagent rows

- background is an open boolean so a later build's value never fails a chat load
- a helper's own helpers fold under a groupId of their own, so the phone opens them apart
- each subagent's own row rides along with the page window instead of counting as conversation

* refactor(chat): a subagent's group entry as one object
2026-10-12 14:21:25 -07:00
OrcaWinandOrca Worker de5470a900 fix(rate-limits): start the Codex usage probe off the main thread so focus doesn't freeze the UI (#27707)
* fix(rate-limits): spawn the Codex usage probe off the main thread

On Windows, creating a cold standalone codex.exe can spend seconds in code
integrity checks on the calling thread. The usage probe runs on window focus,
so that froze the UI. The probe's child is now created and piped on a worker
thread and proxied back to the main thread.

Fixes #27362

* refactor(rate-limits): type the worker's children through @orca/process-host

* fix(rate-limits): kill an orphaned Codex probe by pid when its worker dies

Once the worker thread that owns the probe's child is gone, kill() can no
longer reach the child through it. The proxy now falls back to a pid-addressed
kill; the Codex probe uses the app-server tree kill so a node launcher's
codex.exe child goes too, while SIGTERM stays a graceful root signal.

* refactor(build): move the main build's entry map out of electron.vite.config.ts

The new worker entry pushed electron.vite.config.ts past the 300-line limit.
The Rollup input map for the main build now lives in its own module.

* refactor(rate-limits): drop the orphan pid kill and trim the worker child-process stand-in

A worker thread's death closes its children's pipes, so an abandoned Codex
probe already gets stdin EOF, its graceful stop. The pid-addressed orphan kill
was redundant with that and with terminateCodexProbeChild's own gated Windows
tree kill. Its hard kill also bypassed the tree-kill gate's recycled-pid refusal
by turning the handle-addressed fallback into a pid kill.

- Remove orphan-pid-kill, killOrphanedCodexProbe, the injected killOrphan, and
  the orphaned flag. kill() now reports false once a child is no longer tracked.
- Process-creation failure is one 'spawn-error' event that the stand-in expands
  into error/exit/close, replacing the duplicated triple and the exported exit
  code. Stdin errors arrive as child errors, as the probe already treated them.
- Fold both suites into one over a real MessageChannel and real children. Add a
  real-worker test showing that terminating or crashing the worker lets the child
  exit on stdin EOF.

* test(rate-limits): run the worker child-process suite under Node

The suite asserts Node behavior that Electron main relies on: a failed spawn
closes with a null signal, and a dead worker thread closes its children's
pipes. Bun reports an undefined signal and, on Linux, leaves the pipes open,
so the Bun lane failed on runtime differences, not on the product code.

* test(rate-limits): count a reaped-by-nobody zombie as an exited child

On Linux and macOS a child whose worker thread died gets stdin EOF and exits,
but libuv reaps a child only through the handle on the loop that spawned it.
Once that loop is gone, nothing waits on the pid, so it stays a zombie and
kill(pid, 0) still succeeds. The real-worker tests now treat a zombie as
exited, and still require the child's EOF marker.

---------

Co-authored-by: Orca Worker <orca-worker@localhost>
2026-10-12 14:19:43 -07:00
Brennan Benson 8e8c087581 test(orcad): retry launcher-isolation teardown on Windows EBUSY (#28061)
An exited node.exe copy can stay locked for a moment on Windows, so the
afterAll rm failed the hourly build's windows-11-arm persistence job.
2026-10-12 14:07:43 -07:00
OrcaWinandOrca Worker 8f063f3567 perf(copilot): run the Windows status hook as a direct .cmd, faster and UTF-8 safe (#27718)
* perf(hooks): register direct .cmd hooks for Gemini, DSH, Kiro, Droid and Command Code on Windows

On a cmd-safe profile path these agents now register the managed .cmd path
itself instead of a `powershell.exe -EncodedCommand` launcher, so each hook
event no longer pays a second PowerShell start-up on top of the runner's own
shell. Profiles the bare path cannot carry (spaces, non-ASCII, UNC) keep the
encoded launcher. Same pattern as Claude (#18875) and Muse (#26755).

Refs #20913

* fix(kimi): register a cmd.exe-runnable hook on Windows

Kimi runs hooks with asyncio.create_subprocess_shell, which is cmd.exe on
Windows. Orca registered a POSIX `if [ -f ... ]` command there, so cmd failed
with "-f was unexpected at this time" before reaching the script and, since
Kimi fails open, no status event was ever sent.

On win32 write a cmd+curl kimi-hook.cmd and register it the way the other
direct .cmd agents do: the bare forward-slash path on a cmd-safe profile,
the encoded launcher otherwise. The ownership matcher already covers every
extension of the stem, so the old kimi-hook.sh entry is replaced on the next
install. SSH remotes keep the POSIX script.

Kimi joins the direct .cmd runner suite as a row run the way CPython's
shell=True does, and every row now also runs from a profile with a space
(the encoded launcher) and outside an Orca pane.

* fix(copilot): run the Windows status hook as a direct curl .cmd

Copilot runs a Windows hook as `pwsh|powershell -nop -nol -c <command>`. Orca
registered an encoded `powershell.exe -EncodedCommand` launcher there that
started a second PowerShell to run copilot-hook.ps1, which posted with
Invoke-WebRequest: two PowerShell starts per event (~850-900 ms median), and
the .ps1 read stdin through the console code page, so non-ASCII prompt text
arrived as "caf?? ...".

On win32 write copilot-hook.cmd (answer `{}`, refresh the endpoint, env guard
before stdin, curl form POST with hookEventName) and register its bare path
with the event as its argument; non-cmd-safe profile paths keep the encoded
launcher, which now sets ORCA_COPILOT_HOOK_EVENT for the .cmd. The managed
matcher covers every extension of the stem, so old .ps1 entries are replaced
on the next install while user hooks stay. SSH remotes keep the POSIX script.

Copilot joins the direct .cmd runner suite as a row run under both
PowerShells, checking the byte-exact payload and the posted hookEventName.

Refs #20913

* refactor(hooks): one Windows batch-hook helper and one payload-delivery table

- Add wrapWindowsBatchHookCommand (bare .cmd path, encoded launcher
  otherwise) and use it for Muse, Gemini, DSH, Kiro, Droid and Command Code.
- Fold the five agents into windows-hook-payload-delivery.test.ts as rows
  run under each vendor's shell, from a plain and a spaced profile, with a
  stale encoded registration seeded per row; drop the separate runner suite
  and its CI list entries.
- Replace the Windows-only per-agent registration tests with ones that stub
  process.platform, so they run on every CI OS.
- Fix stale launcher, helper and DSH comments.

Refs #20913

---------

Co-authored-by: Orca Worker <orca-worker@localhost>
2026-10-12 13:52:28 -07:00
Neil e02e2bb1d9 [CI] Advance transcript clocks and skip test-only computer checks (#28060) 2026-10-12 13:46:53 -07:00
OrcaWinandOrca Worker 28dc0c9771 fix(kimi): give Kimi a Windows .cmd status hook that cmd.exe can run (#27714)
* perf(hooks): register direct .cmd hooks for Gemini, DSH, Kiro, Droid and Command Code on Windows

On a cmd-safe profile path these agents now register the managed .cmd path
itself instead of a `powershell.exe -EncodedCommand` launcher, so each hook
event no longer pays a second PowerShell start-up on top of the runner's own
shell. Profiles the bare path cannot carry (spaces, non-ASCII, UNC) keep the
encoded launcher. Same pattern as Claude (#18875) and Muse (#26755).

Refs #20913

* fix(kimi): register a cmd.exe-runnable hook on Windows

Kimi runs hooks with asyncio.create_subprocess_shell, which is cmd.exe on
Windows. Orca registered a POSIX `if [ -f ... ]` command there, so cmd failed
with "-f was unexpected at this time" before reaching the script and, since
Kimi fails open, no status event was ever sent.

On win32 write a cmd+curl kimi-hook.cmd and register it the way the other
direct .cmd agents do: the bare forward-slash path on a cmd-safe profile,
the encoded launcher otherwise. The ownership matcher already covers every
extension of the stem, so the old kimi-hook.sh entry is replaced on the next
install. SSH remotes keep the POSIX script.

Kimi joins the direct .cmd runner suite as a row run the way CPython's
shell=True does, and every row now also runs from a profile with a space
(the encoded launcher) and outside an Orca pane.

* refactor(hooks): one Windows batch-hook helper and one payload-delivery table

- Add wrapWindowsBatchHookCommand (bare .cmd path, encoded launcher
  otherwise) and use it for Muse, Gemini, DSH, Kiro, Droid and Command Code.
- Fold the five agents into windows-hook-payload-delivery.test.ts as rows
  run under each vendor's shell, from a plain and a spaced profile, with a
  stale encoded registration seeded per row; drop the separate runner suite
  and its CI list entries.
- Replace the Windows-only per-agent registration tests with ones that stub
  process.platform, so they run on every CI OS.
- Fix stale launcher, helper and DSH comments.

Refs #20913

---------

Co-authored-by: Orca Worker <orca-worker@localhost>
2026-10-12 13:28:28 -07:00
Brennan Benson 0309341ac4 feat(native-chat): wrap long lines in code blocks (#26130)
* feat(native-chat): wrap long lines in code blocks

Code blocks in native chat now have a wrap-lines toggle next to Copy.
It is off by default, per block, and not persisted; when on, the block
wraps long lines instead of scrolling sideways.

* fix(native-chat): give each document code block its own component

The document pre renderer called the code-block renderer as a function after
an early return, so a renderer with hooks ran them as the pre's own hooks and
React saw the hook order change when a fence switched between diagram and code.

* fix(native-chat): keep a block's line wrap when its row scrolls away

Wrap lived in the code block's own state, so a row that windowing unmounted,
or a growing reply re-cutting its top-level blocks, came back unwrapped and
shorter than its measured height. The flag now lives in the transcript's
disclosure store, keyed by the row and the fence's offset in the whole
message, which appended text never moves. Outside a transcript the block
keeps local state as before.

* fix(native-chat): key a block's line wrap by its line, not its character offset

While a reply streams, its last block is parsed after the streaming fixes
escape characters such as x~y mid-line, so a later fence's character offset
moved by one when the block was next parsed as written (the reply finishing,
another block following, or the reply merging into one block), and the wrap
reset. Those fixes never add or remove a line break before a fence, so the
block splitter now records each block's starting line and the wrap key uses
the fence's line in the whole message.

* fix(native-chat): keep line wrap in plan, reasoning and approval cards too

Plan cards and reasoning bodies rendered their markdown without a row key,
so their code blocks fell back to local wrap state and lost it when the row
scrolled away. Reasoning text now uses its existing disclosure key, shared by
the live line and the finished row, and a plan card uses plan:<message id>, so
neither can share a key with the reply of the same message. The approval card
sits in the composer slot, outside the transcript and its store, and is never
windowed, so it keeps local state.
2026-10-12 13:22:03 -07:00
Brennan Benson 9efce92d91 Keep a "chats resumed" entry in the bottom bar until you've seen the result (#27580)
* fix(native-chat): keep a 'chats resumed' bottom-bar entry until the result is seen

After every chat resumed, the bottom-bar entry vanished the moment the run
finished, leaving a fading toast as the only way back to the list. Show
'N chats resumed' while a finished run's summary has not been shown yet,
reusing the run's existing finishedViewShown flag so the entry ends exactly
when the summary is viewed and closed.

* Keep resume results until their rows are seen

* Retire viewed empty resume runs

* Show finished resume rows after in-flight view settles
2026-10-12 13:10:24 -07:00
Brennan Benson 9fcbb2ce3e fix(chat): name orchestration worker chat tabs after their task title (#28045)
* fix(chat): name orchestration worker chats after their task title

Worker chats were skipped by chat naming and stayed "Claude Chat".
Name them from the task they were started for instead, with no model call.

* Fix legacy worker chat titles without prompt generation

* Remove unrelated lockfile dependency
2026-10-12 13:02:26 -07:00
Neil d4d12120c6 [CI] Advance test clocks and preserve cleanup checks (#27791)
* [CI] Remove wall-clock waits and stabilize GC fairness check

* [CI] Include the final GC timer gap in the fairness check

* [CI] Advance test clocks and preserve cleanup checks
2026-10-12 12:54:28 -07:00
Brennan Benson a94092908c fix(native-chat): message rail lights a message sent while the agent was working (#27821)
* fix(native-chat): light the tick of a message sent mid-turn on the message rail

A message sent while the agent is working joins the running turn's key, and the
rail lit ticks by turn key, so that message's tick never lit: not at the bottom,
not after clicking it. Light the newest tick at or above the read row within its
turn instead.

* fix(native-chat): light the nearest tick above the read row, and keep a clicked tick lit

Review round 1. The within-turn walk stopped at a row of no turn landing in the
running turn, and could return a tick from the turn above. The lit tick is now
the newest loaded rail item at or above the row at the fold, falling back to its
turn key above the first loaded one (that prompt is in the outline).

A jump that cannot bring its row to the top lands clamped at the bottom, and the
settled position report then lit the last tick instead of the clicked one. A
clicked tick now stays lit until the reader scrolls or starts another navigation
(the existing reader-input path), or the set of prompts changes.

Rail picks and the start of every navigation move into their own hook, as the
list is at its line limit.

* fix(native-chat): hold a clicked rail tick by position, not by reader input

Review round 2. Releasing the clicked tick on reader input needed every scroll
source to report itself, and several do not (scroll-to-top button, inline diff
reveal, prompt refold, selection-drag autoscroll); releasing it whenever the
prompts changed dropped it on every head trim of a long live session.

The hold now records where the jump settled, at the first idle read after the
click, and keeps the clicked tick lit while the view rests there. Any movement,
whatever caused it, releases it, as does the clicked message leaving the rail.
Drops the navigation wiring and the hook it was moved into.

* fix(native-chat): anchor a clicked rail tick's hold to its row on screen

Review round 3. Holding while scrollTop stayed put recorded the reader's spot when
they kept scrolling within the idle period of the jump, kept an off-screen row lit
after the viewport shrank, and let go when a row above re-measured late and shifted
scrollTop with the content.

The hold now records how far below the viewport top the clicked row settled, and
keeps the tick lit while the row is still on screen at that offset.
2026-10-12 12:36:31 -07:00
Brennan Benson c34574180b Terminal header button: Resume in New Native Chat replaces Show chat view (#27184)
* feat(terminal-pane): header button resumes the CLI session in a new native chat

The pane header's "Show chat view" toggle is replaced by the tab menu's
"Resume in New Native Chat" move. The Session History lookup runs on click,
so an always-visible header never scans in the background. A pane already
in chat view keeps its "Show terminal" way back.

* fix(terminal-pane): fold the resume gate into the pane's existing store subscription

Keeps a mounted terminal pane inside its pinned store-listener budget.

* fix(terminal-pane): resume the pane's own conversation, open the chat that already holds it

The header "Resume in New Native Chat" button gated on chat-view eligibility but looked up the
tab's best conversation, so a Grok pane beside a Claude pane resumed the Claude conversation, and
Grok/OpenCode panes showed a button that always failed with a "try again" toast.

- Map one pane to its conversation with the tab-title mapping narrowed to that pane (same sources,
  same Claude/Codex rule), for both the button's visibility and its click; this also keeps the
  button after the agent exits, as the tab menu does.
- A conversation a native chat already holds now matches its history row; the move's gate still
  refuses it (tab menu unchanged, pinned in the parity test), and the button opens that chat.
- Distinct messages: nothing saved yet, cannot be resumed in a native chat, or the existing
  generic failure. The click forces a fresh scan like the panel's Refresh does.
- Call the plain resume launch directly; drop the shared actions module and restore the tab menu.
- A tab closed during the lookup no longer opens a chat.

* fix(terminal-pane): hide Resume in New Native Chat where the chat route is refused

On a local workspace whose project runs in WSL (or needs runtime repair), the header button showed
on Claude/Codex panes, but native chat cannot launch there, so every click said the conversation
can't be resumed while the tab menu offered nothing. The button's visibility now also asks the
same launch-route check the Session History row gate uses (structuredAgentSessionLaunchFeasible).

* fix(terminal-pane): offer the header resume only while the pane's agent is running

The tab menu now offers Resume in New Native Chat only while the tab's agent
runs (#27520). The header button still found its pane's conversation from the
retained or sleeping record an exited agent leaves behind, so it stayed after
exit. The pane lookup now honours the same runningAgentsOnly option, and both
the button's visibility and its click pass it.

* refactor(terminal-pane): the header's pane lookup reads only the running agent

Every caller of resolvePaneAiVaultTitleRequest asked for running agents only,
so its retained and sleeping sources were never read. The pane lookup now maps
just the pane's live agent, and collectAiVaultTitleRequests returns to main's
shape, sharing only the live-agent mapping with it.
2026-10-12 12:13:05 -07:00
Brennan Benson 8a7d19e330 Show turn edit diffs inline in native chat (#27758)
* Show recorded turn diffs inline in native chat

* Resolve only selected turn diff tool pairs
2026-10-12 12:09:24 -07:00
OrcaWinandOrca Worker c62c5a4cfc perf(agent-hooks): drop the extra PowerShell per hook event for Gemini, dsh, Kiro, Droid and Command Code (#27717)
* perf(hooks): register direct .cmd hooks for Gemini, DSH, Kiro, Droid and Command Code on Windows

On a cmd-safe profile path these agents now register the managed .cmd path
itself instead of a `powershell.exe -EncodedCommand` launcher, so each hook
event no longer pays a second PowerShell start-up on top of the runner's own
shell. Profiles the bare path cannot carry (spaces, non-ASCII, UNC) keep the
encoded launcher. Same pattern as Claude (#18875) and Muse (#26755).

Refs #20913

* refactor(hooks): one Windows batch-hook helper and one payload-delivery table

- Add wrapWindowsBatchHookCommand (bare .cmd path, encoded launcher
  otherwise) and use it for Muse, Gemini, DSH, Kiro, Droid and Command Code.
- Fold the five agents into windows-hook-payload-delivery.test.ts as rows
  run under each vendor's shell, from a plain and a spaced profile, with a
  stale encoded registration seeded per row; drop the separate runner suite
  and its CI list entries.
- Replace the Windows-only per-agent registration tests with ones that stub
  process.platform, so they run on every CI OS.
- Fix stale launcher, helper and DSH comments.

Refs #20913

---------

Co-authored-by: Orca Worker <orca-worker@localhost>
2026-10-12 11:37:25 -07:00
OrcaWinandOrca Worker 982b3d4234 fix(browser): settle WSL tunnel reads so responses past 256 KiB complete (#27625)
* fix(browser): settle WSL tunnel reads so responses past 256 KiB complete

The WSL execution route hands the deferred socket a credit-accounted tunnel
duplex, but the deferred socket forwarded its data without settling it, so the
relay's 256 KiB receive window was never replenished and larger responses
stalled. Settle each chunk once tunnel-socket listeners have taken it; their
pause() still holds the source, so unsettled bytes stay bounded by the window.

Fixes #27356

* test(browser): bound the WSL backpressure wait so a regression fails instead of hanging

* test(browser): keep unchanged WSL route test lines in their original form

---------

Co-authored-by: Orca Worker <orca-worker@localhost>
2026-10-12 11:36:23 -07:00
Brennan Benson b40554b2dc fix(browser): end a background page's mount admission after its first load (#27754)
* fix(browser): end a background page's mount admission after its first load

A page opened in the background is admitted so its guest starts, but the
admission was only released on close. Its overlay stayed laid out (invisible)
in the visible workspace and its browser chrome never unmounted in hidden
workspaces. Release the admission when the guest's first load settles, so the
normal active-page and paint-retention rules own the page afterwards.

STA-7435

* test(browser): pin how a released mount admission parks a background page

Covers the overlay contract the release relies on: in a hidden worktree the
page's browser chrome unmounts once the admission ends; in the visible worktree
it stays mounted and only its slot stops being laid out. Also corrects the
page-record comment that still said background tabs sit idle until shown.
2026-10-12 11:29:15 -07:00
Brennan Benson fa4ef2b961 ci: stop new main-process code from freezing Orca with synchronous launches (#27756)
* ci: ratchet synchronous child-process launches in main and shared code

The Electron main process freezes every window while a synchronous child
process runs, and on Windows each launch is slow because antivirus scans
it. Nothing stopped new synchronous launches from landing in main-process
code, and the spawn observer behind ORCA_MAIN_THREAD_DIAGNOSTICS only saw
async spawns, so the worst blocks were invisible to it.

Ratchet: check:main-sync-spawn-ratchet parses production src/main and
src/shared files (test-only paths excluded) and fails when a file not in
config/main-sync-spawn-baseline.txt reaches execFileSync, spawnSync or
execSync from child_process, runProcessSync from @orca/process-host, or
one of the synchronous secure-file writers (they run icacls on Windows).
Named, aliased, namespace, default, require() and import() forms count.
A baseline entry whose file no longer launches synchronously fails as
stale, so the list can only shrink. It runs in lint and in the PR
static-analysis job next to the process-host import check.

Observer: runProcessSync now reports to the spawn observer, timing the
whole call (that is how long the thread was stuck) and reporting from a
finally so failing, throwing and timed-out children are counted too.

Runtime behavior is unchanged except that the diagnostics observer now
sees synchronous launches; with no observer registered the sync path
pays one extra branch.

* ci: count sync launches per file and gate the repo's sync wrappers

The sync-spawn ratchet only tracked which files launched synchronously, so a
file already on the list (wsl.ts, win32-utils.ts, git-exec-file.ts) could add
more launches unnoticed, and new callers of the repo's own synchronous
wrappers passed CI.

The baseline now stores a per-file count of uses, in the same format as the
focus-read ratchet: a rise or a new file fails, a lower or zero count fails
until `pnpm check:main-sync-spawn-ratchet --prune` lowers it, and --prune
never raises an entry. Every value reference to a gated function counts
(call, hand-off, alias and its uses, re-export); imports alone count nothing.

Gated wrappers: gitExecFileSync (and the git/runner re-export),
restrictWindowsPathSync, hardenSecurePath when called with sync: true,
grantDirAcl, getWslHome, listWslDistros, wslUncDirectoryExists,
isWslAvailable (and the wsl.ts re-export), isPwshAvailable. Their 18
current caller files join the baseline.

Failure messages now give advice per family: the async process-host API for
child-process launches, the async twin for wrappers, and for the secure
writers a note that no async secure writer exists yet, so the sync one stays
and must never become a plain write (that drops the Windows ACL hardening).

Also refresh two stale blockMs descriptions and drop a no-op line from the
spawn-observer "stays silent" test.

* ci: count hardenSecurePath unless visibly async, and tighten ratchet tests

hardenSecurePath was counted only when `{ sync: true }` was written inline,
so moving the options into a variable or a spread dropped the count and the
check then asked for a prune. It now counts every use except a call with no
options or a spread-free object literal whose `sync` is absent or false.

Also: resolve backslash paths in the importing file as well as the
specifier; still visit heritage and instantiation expressions
(`extends mixin(fn)`, `fn<T>`); state the reference-only counting limit;
and point the secure-writer advice at raising this file's baseline count.

Tests cover the hardenSecurePath shapes, the alias chains that need the
fixpoint loop, and a prune that must not raise a grown row.

* test(ci): pin the main sync-spawn ratchet background step

* fix: count secure hardening calls with spread arguments
2026-10-12 11:28:25 -07:00
Brennan Benson 3fb4b06dbb perf(notifications): stop every Windows notification from freezing Orca (#27731)
* perf(notifications): save mobile dismissal history off the main thread

Every agent notification recorded its id in mobile-notification-dismissals.json
through the secure-file writer. On Windows that writer runs about five
synchronous icacls launches per write, so each notification froze every Orca
window (STA-9692).

The file holds notification ids, sequence numbers and session ids, not
credentials, so it now goes through the existing AtomicSnapshotWriter: async
temp-plus-rename, mode 0600, bursts coalesced into one write of the latest
entries. Memory stays authoritative, so delivery and retirement are unchanged.
The pending write is flushed in the quit barrier after chat and RPC teardown,
and in the pre-relaunch hook, since renderer relaunch exits without the quit
pipeline.

* perf(notifications): flush dismissal history when a headless host shuts down

orcad never runs the desktop quit barrier, so its cleanup list awaits the
pending dismissal write after RPC stops.

* fix(persistence): drain a snapshot write requested just after a drain ends

AtomicSnapshotWriter cleared its pending-write promise one microtask after
the drain loop saw no more requests. A write() in that gap set
writeRequested, got the settling promise back, and nothing drained it, so
the newest bytes stayed unwritten and waitForPendingWrite() (the dismissal
store's flush) resolved without them. The drain now clears the pending
promise in the same tick its loop exits, on success and failure, so a later
write() always starts a new drain.

* test(notifications): flush dismissal writes before deleting temp state dirs

Dismissal history now saves asynchronously, and the writer recreates its
directory before each write. Tests that deleted their temp dir with a write
still pending left recreated dirs behind in the system temp folder and could
race the delete on Windows. They now await the controller's flush first.

* perf(notifications): close dismissal history at committed exits

The shutdown flush only waited for writes already queued. A notification
recorded after it (orcad cleanups still running, or the 150 ms before a
renderer relaunch calls app.exit) started an async write the exit dropped,
so the phone could keep an alert the desktop could no longer withdraw.

The store's flush() becomes close(): it waits for the pending write, and
every later record saves synchronously through the snapshot writer's
existing writeSync (plain fs, no icacls; a failure only warns). It is called
only where the exit can no longer be vetoed: the will-quit teardown, a new
exit-only hook that app:relaunch runs before app.exit(0) (restart and
profile switches share onBeforeRelaunch but quit through will-quit), and
orcad's cleanup after RPC stops.

The quit test now holds chat teardown and RPC stop and proves the close
starts only after both settle, in either order. The orcad push-startup
runtime mock gains the method the entry now calls.

* fix(notifications): bound the dismissal close before a renderer relaunch

app:relaunch waited on the dismissal store's close with no limit, so a
stalled userData disk left Restart stuck on "Restarting…" with a half-quit
app saving synchronously. The close now races a 2 s cap through the
existing settleWithinMs helper, the same cap the restart path's auth
preservation uses, and logs when it gives up.

* test(notifications): keep async dismissal writes under test between reads

Mid-test disk reads used closeDismissals() as a barrier, which switched
every later record to the exit-only synchronous path. The store gains a
non-closing flush() (close() = mark closed + flush), exposed on the
controller as flushDismissals for tests; mid-test barriers use it, and
close stays only where a test simulates an exit or teardown. Also notes why
the writer's pendingWrite assignment is safe, renames a store test to what
it covers, and groups the orcad cleanup-order test under a describe.
2026-10-12 11:25:35 -07:00
Brennan Benson 56a12f54cf fix(browser): end a page's mount admission whenever it leaves the store (#27755)
* fix(browser): end a page's mount admission whenever it leaves the store

A background-opened page's start admission was released only by the two
close actions, so a bulk worktree purge (listing refresh, repo or folder
removal) dropped the page while its admission stayed in the set. Release it
in the one cleanup every page removal already runs through
(destroyRemovedBrowserWebview), and drop the per-path copies.

STA-7436

* refactor(browser): one removed-page sweep shared by the app and its test

Moves the store sweep that destroys removed pages into
destroyWebviewsRemovedFromStore so the admission regression test runs the
same code the app runs, and adds an eviction case.

* test(browser): assert pages were admitted before the eviction case
2026-10-12 11:09:49 -07:00
OrcaWinandOrca Worker b0e73d9342 fix(codebuddy): run Windows hooks without requiring PowerShell 7 (#27626)
CodeBuddy spawns hooks registered with shell: 'powershell' as a bare
`pwsh`, so on Windows without PowerShell 7 every hook failed with
spawn pwsh ENOENT and the failed UserPromptSubmit hook blocked prompts.
Register CodeBuddy's hooks the way Claude's are: the managed .cmd path
on cmd-safe profiles (runs under CodeBuddy's Git Bash or Windows
PowerShell default shell) and the absolute powershell.exe encoded
launcher otherwise. The next install rewrites existing registrations.

Fixes #27182

Co-authored-by: Orca Worker <orca-worker@localhost>
2026-10-12 10:47:16 -07:00
25f87544d0 feat(terminal): persistent banner to restart a terminal daemon whose files are gone (#28021)
Co-authored-by: m4air <m4air@Mac.localdomain>
Co-authored-by: Claude <noreply@anthropic.com>
2026-10-12 09:09:24 -07:00
fde0fe35a7 fix(daemon): stop giving new terminals to a daemon whose AppImage files are gone (#28011)
* fix(daemon): stop giving new terminals to a daemon whose AppImage files are gone

On Linux AppImage, a terminal daemon can outlive the mount it runs from and
keep answering health checks until it SIGBUSes. Orca now reads the daemon's
/proc exe link and mount table at adoption: an idle severed daemon is
replaced; a busy one is kept for its sessions but quarantined, so fresh
terminals use the local provider and a healthy RPC never re-admits them.
Status and a verified restart are available over RPC and as
`orca terminal-daemon status|restart`.

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(daemon): lift the severed quarantine once a new daemon owns the endpoint

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(daemon): a severed-daemon restart counts fallback terminals and never stops a replacement

The restart re-checks the authenticated endpoint owner before any teardown
and again at shutdown, and never falls back to a pid kill when targeted.
Status counts the local fallback terminals the restart also ends.

Co-Authored-By: Claude <noreply@anthropic.com>

* chore(daemon): merge duplicate import

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(runtime): classify the terminalDaemon RPC namespace as host-owned

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: m4air <m4air@Mac.localdomain>
Co-authored-by: Claude <noreply@anthropic.com>
2026-10-12 08:50:25 -07:00
5f1f9ea07a fix(serve): give the first AppImage serve one unpacking deadline and progress (#28015)
Direct AppImage serve now unpacks its stable copy once, under one overall
deadline that also bounds the registration lock wait and both extraction
attempts, prints progress to stderr, and refuses fast when the private
cache is not writable, not executable, or too full.

Co-authored-by: m4air <m4air@Mac.localdomain>
Co-authored-by: Claude <noreply@anthropic.com>
2026-10-12 08:39:07 -07:00
OrcaWinandm4air cb92b6ce57 feat(serve): run the --supervise supervisor from a copy outside the app (#28007)
* feat(serve): run the --supervise supervisor from a copy outside the app

* fix(serve): record supervisor copy users beside the copies so a cleanup never races a fresh copy

* fix(serve): remove a partial supervisor copy when copying fails

* test(serve): skip the unreadable-copy case where mode 000 does not block reads

---------

Co-authored-by: m4air <m4air@Mac.localdomain>
2026-10-12 08:36:28 -07:00
OrcaWinandm4air dedea9c76c fix(updater): clear a cancelled macOS install request before staging the next update (#28010)
* fix(updater): clear a cancelled macOS install request before staging the next update

* fix(updater): match only Orca's ShipIt and recheck owners right before clearing

---------

Co-authored-by: m4air <m4air@Mac.localdomain>
2026-10-12 08:28:58 -07:00
3d27142407 fix(linux): keep extracted AppImage copies that something still runs from (#28002)
* fix(linux): keep extracted AppImage copies that something still runs from

New builds extract into a namespace that pre-lease builds never prune.
Pruners now delete a generation only when no same-user process runs from
it and no lease names a live process (pid, start ticks, boot id).

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(linux): skip non-dumpable processes in the AppImage residency scan

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: m4air <m4air@Mac.localdomain>
Co-authored-by: Claude <noreply@anthropic.com>
2026-10-12 08:22:31 -07:00
OrcaWinandm4air 65a1611926 fix(serve): explain what to run when an SSH host's orca cannot start or install a server (#28003)
* fix(serve): explain what to run when an SSH host's orca cannot start or install a server

* fix(serve): keep an app launcher on PATH usable from a server terminal; share the app-CLI check

---------

Co-authored-by: m4air <m4air@Mac.localdomain>
2026-10-12 08:06:24 -07:00