Commit Graph
10249 Commits
Author SHA1 Message Date
Neil bedbe5997b test: match explorer filenames independently of git badges (#18997) 2026-09-05 19:45:42 -07:00
Neil 681119dc05 test: isolate window mocks from inherited launch flags (#18989)
* test: isolate mocked window activation from inherited launch flags

* Preserve background window regressions added on main
2026-09-05 19:33:47 -07:00
Neil fd10758eae ci: expose existing E2E spec selection for manual dispatch (#18987) 2026-09-05 19:13:55 -07:00
Brennan BensonandMerge Sim 84432d3aa1 fix(native-chat): repair a structured chat tab permanently fenced by an inherited publication epoch (#18906)
* fix(native-chat): repair a structured tab fenced out by a returning publisher

A publication epoch is retired whenever another publisher takes over a worktree, and a
retired epoch is then rejected forever. But a live publisher can return after transient
interlopers - a `removed:` retraction, then a headless rebuild whose version restarts at
1 - and the structured tab publish inherits the worktree's existing epoch rather than
minting one, so it arrives under the blacklisted epoch and is dropped. The chat tab never
reaches the tab bar.

The fence is right to reject the frame: it cannot tell a returning publisher apart from a
delayed frame queued by a dead generation, whose version can outrank the live cursor. So
the drop is no longer final - it schedules one bounded, debounced authoritative
`session.tabs.listAll`, and only that census may revive an epoch, and only the one it
names current. Subscription frames stay fenced exactly as before.

* fix(native-chat): decay the structured tab repair cap and prune its state

The attempt cap latched: three transient RPC failures left `exhausted` set for the
renderer's lifetime, permanently hiding a chat tab behind a single console warning. It
now decays, so a worktree that has been quiet for a minute gets its full budget back.

The repair map was also missing from the sweep that drops publisher cursors for vanished
worktrees, leaking an entry per deleted worktree. Pruning it there required inverting the
repair lane's dependency on the inventory refresh, which is now injected.

---------

Co-authored-by: Merge Sim <sim@local>
2026-09-05 18:45:34 -07:00
Neil 6d691a4c04 test: bound release checkout lock fixtures and gate delayed imports (#18981) 2026-09-05 18:43:36 -07:00
Neil e7dc9b6099 test: honor background launch in paired client window helpers (#18978) 2026-09-05 18:41:24 -07:00
Neil ef3f507903 ci: verify release ref trust and preserve case twins during checkout (#18980) 2026-09-05 18:38:45 -07:00
Neil 6031c19e9f ci: reduce dependency, checkout, and test deadline overhead (#18968)
* ci: reduce dependency, checkout, and test deadline overhead

* ci: avoid generic E2E jobs for native-only IME changes
2026-09-05 18:20:12 -07:00
Neil d7722a698c test: drain project menu focus restoration before teardown (#18971) 2026-09-05 18:09:53 -07:00
github-actions[bot] 75d4add344 Update README downloads badge 2026-09-06 01:05:38 +00:00
Neil b852aa74a6 fix(ci): store vetted refs in a reftable so case-twin branches don't fail the fetch (#18970)
The adhoc mac and dev-channel Windows builds vet the requested ref by
mirroring every branch and tag of this repo into a scratch bare repo and
proving the commit is reachable. Both runner disks are case-insensitive,
and the repo now has two branches differing only in casing, so the files
backend refuses the fetch outright — the whole job dies before checkout.

reftable keys refs in a table rather than as file paths, so both refs
store and every ref stays in the reachability set.
2026-09-05 18:03:01 -07:00
OrcaWinandOrca Worker b6ca8dad99 fix(hooks): register the Claude hook script directly on Windows (#18875) (#18905)
* fix(hooks): register the Claude hook script directly on Windows (#18875)

The Windows Claude Code lifecycle hook was registered as
`powershell.exe -NoProfile -EncodedCommand <...>` whose entire decoded payload
was a `Test-Path` and a call to `~/.orca/agent-hooks/claude-hook.cmd`. Every
hook event paid a full PowerShell start-up to reach a script that exits at its
first `ORCA_PANE_KEY` guard, so sessions outside Orca paid it to do nothing.

Register the script path itself instead, with `|| echo {}` for the
neutral-JSON-when-missing contract (#14818). Measured on Windows 11, invoked as
Claude Code invokes it (`printf payload | bash -c -l "<command>"`):

  idle (n=12)          baseline 177ms | before 471ms | after 213ms
  10-way conc (n=40)            --    | before 656ms | after 296ms
  p95 under load                --    | before 696ms | after 337ms

It also drops an interpreter from the chain the hook's timeout kill must tear
down. Killing the hook does not kill its PowerShell grandchild, which still
holds the stdout handle the agent reads to EOF -- measured, EOF arrived 352ms
AFTER the kill, when the orphan exited by itself. msys2 creates children
suspended and resumes them after, so a kill landing in that window strands one
that never exits and EOF never comes; that is the reported frozen session.

The encoded launcher stays as the fallback for profile paths the shells cannot
carry bare (space, `%`, `^`, `&`, non-ASCII) and for hosts where Git Bash is not
resolvable, because PowerShell 5.1 rejects `||`. Every other agent's hook is
untouched, as is the remote/SSH path.

Not adopted from the report: `cmd.exe /d /c <path>` (MSYS rewrites the `/c`
under Git Bash -- measured, the invocation fails), and raising the 10s timeout
(the orphan survives the kill regardless; the fast path puts the hook 30x under
the budget so the kill effectively stops firing).

* fix(build): list the new hook launcher modules in the CLI tsconfig project

config/tsconfig.cli.json enumerates its files explicitly, so the two new
imports reached by src/main/claude/hook-settings.ts failed tc:cli with TS6307.
src/main/git-bash.ts pulls in only node:fs, node:path and a shared constant,
so it adds nothing heavy to the CLI project.

* fix(hooks): address review of the direct Windows Claude hook launcher

- Make the Windows hook suites host-independent. A box with a cmd.exe AutoRun
  (HKCU\...\Command Processor\AutoRun) failed them at HEAD too: the tests
  redirect USERPROFILE, the AutoRun target vanishes, and MSYS spawns a .cmd
  without /d so AutoRun runs and lands on the hook's stderr. Seed an empty
  target, including under the deliberately-absent profile.
- Note in managed-hook-stdin-lifecycle why the "missing managed script" case no
  longer exercises the fallback for the direct shape (it carries an absolute
  path, so a redirected profile changes nothing); that path is covered live in
  windows-direct-cmd-hook-command.test.ts.
- Keep the direct shape off UNC profiles: WINDOWS_CMD_SAFE_PATH admits them, but
  //server/share/... is not a command cmd.exe reliably starts.
- Correct the comments: `|| echo {}` also fires when cmd.exe itself exits
  non-zero (failing AutoRun), printing {} twice. The encoded launcher exited 1
  on that same box, so neither shape is clean there.
- Test the contract that replaced runtime %USERPROFILE% resolution (STA-3348): a
  stale absolute path reports not_installed and is rewritten on install.
- Record the standing unmeasured assumption in windows-edr-posture.md: `||` does
  not parse in Windows PowerShell 5.1, so a compat consumer that hosts hook
  strings there would fail closed. Measure before widening to another agent.
- Trim the launcher comments per AGENTS.md; the numbers live in the doc.

* test(win32): register the new Windows-gated hook test in the CI lane

win32-test-lane-registration guards against exactly this: a Windows-gated file
that self-skips on ubuntu and reports success, so it runs on no machine. The new
windows-direct-cmd-hook-command.test.ts needs both entries — WINDOWS_PACKAGE_TESTS
decides whether package_windows runs for a diff, and the workflow argv decides
whether the file runs once that job started.

* test(win32): remove the hook temp tree through the retrying helper

windows-lane-tree-removal-boundary scans exactly the specs in the Windows CI
lane, so registering windows-direct-cmd-hook-command.test.ts subjected it to the
rule: cmd.exe and bash have just exited in that tree, and a raw recursive rm
throws EPERM on Windows while their handles drain, turning a green spec into a
lane failure. Use removeTreeSync, which carries the repo's maxRetries policy.

---------

Co-authored-by: Orca Worker <orca-worker@localhost>
2026-09-05 17:50:33 -07:00
Jinwoo Hong 61b09b7a02 fix(relay): abandon dead client accepts, jitter and lengthen the control lease, fail direct probes fast (#18959)
* fix(relay): abandon a client accept once the phone hangs up; jitter the control lease

The accept runs several serialized Postgres calls behind the contended
cell-inventory lock, and phones bound their dial. Finishing that work for a
phone that had already left acquired (and leaked for 90s) an activity lease and
then failed at bind with host_data_reservation_already_bound. Check the client
socket between the DB steps and unwind what was taken, reporting the stage on
orca_relay_client_accept_abandoned.

Jitter the control lease grant so a cohort that reconnected in the same minute
(a cell recreate dumps hundreds at once) walks apart instead of rebinding
together every cycle.

On the phone, treat a probe session that enters 'reconnecting' as a failed
probe: it is the direct client's own backoff after a dead-LAN 1006, and waiting
it out held the supervisor's operation mutex for the full 12s bound.

* perf(relay): lengthen the control lease to 6h

The lease bounds how long a host lingers on a cell after a missed drain, and
rebinding it is the only passive rebalancing we have, so it stays finite. 6h
keeps both properties while cutting control-activation traffic on the contended
cell-inventory lock ~6x. The relay JWT (5 min, refreshed by the desktop) and the
75s silence watchdog are enforced separately, so the longer grant authorizes
nothing extra. The jitter widens with it, to +/-30 min.

* fix(relay): let one flap recover the direct probe; correct the leak window

'reconnecting' is published on any socket close, so rejecting on it outright
turned a single access-point flap into a booked direct failure and a 60s
cooldown. Give the first 'reconnecting' a 2s grace in which a 'connected'
transition still resolves; a dead LAN still fails in ~2s rather than holding the
supervisor's operation mutex for the 12s bound.

The abandoned accept held its activity lease for the 10s attach deadline, not
90s -- the attach timer is armed before bind throws and already unwinds it.

Also cover the assignment-stage check that guards reserveCredential, and drop a
spread assertion the two exact-value assertions above already imply.

* fix(relay): extend the probe grace once on a handshake; pin the lease band top

The redial fires at 500ms but 'connected' waits on the Noise handshake and a
capability RPC, so one 2s window is too tight for real work. A 'handshaking'
transition is evidence the peer answered, so extend the grace once; a stalled
handshake still fails at ~3.5s, far inside the 12s bound.

The longest-lease case only had an upper bound, which a jitter clamped to one
side would satisfy. Pin it to the exact top of the band instead, and assert the
assignment resolve ran so the third-guard test cannot pass vacuously.
2026-09-05 20:47:27 -04:00
Neil 5238a4d576 test: isolate Source Control generation from shared repository remotes (#18962) 2026-09-05 17:39:02 -07:00
Neil 9f0054d89c ci: skip idle Mac allocations and redundant native compiler setup (#18954)
* ci: avoid idle Mac allocations and cached native toolchain installs

* test: anchor artifact fixtures before their fixed expiry
2026-09-05 17:35:20 -07:00
Jinwoo Hong 54a8afc91d fix(orchestration): typed error codes for dispatch and worker-start refusals (#18902)
* fix(orchestration): typed error codes for dispatch and worker-start refusals

orchestration dispatch (and worker-start, which composes it) surfaced task
not found, task not ready, and inject rejected as the same bare
runtime_error, so an agent reading the receipt could not choose between
creating the task, waiting on dependencies, or picking another terminal.

Add task_not_found (data.taskId), task_not_ready (data.status,
data.unmetDependencies), and inject_rejected (data.terminal, data.reason),
each carrying data.nextSteps so every shipped CLI already prints the
recovery. worker-start's not-ready refusal moves from task_not_startable
to task_not_ready with the same detail. runtime_error stays for genuinely
unexpected failures.

Proven red-first from RpcDispatcher through the CLI's own failure
formatting, plus an SSH bridge test that the host CLI's typed refusal
relays unchanged.

* test(orchestration): load CLI formatter at runtime in the dispatch-code test

The composite node typecheck (config/tsconfig.node.json without
--composite false, as CI runs it) rejects a static import of src/cli from
a main test with TS6307. Load the formatter and error class dynamically
behind narrow structural types, as the CLI/runtime boundary test does.

* fix(orchestration): keep task_not_startable and split the CLI-format proof

Review on #18902:

- Drop task_not_ready. worker-start already published task_not_startable
  for a not-ready Task, so renaming it would change an existing receipt
  value under old clients. dispatch now emits task_not_startable too (it was
  a bare runtime_error before, so this is purely additive), with the new
  data.status / data.unmetDependencies / data.nextSteps.
- Move the refusal receipts (code, message, data) into
  src/shared/orchestration-dispatch-refusal-contract.ts so the runtime
  emits them and the CLI test formats the identical envelope. The RPC test
  under src/main asserts toEqual against the contract; the new
  src/cli/orchestration-dispatch-refusal-format.test.ts feeds those same
  receipts to formatCliError / reportCliError. Neither tsconfig widens and
  the composite typecheck CI runs is clean.

* fix(orchestration): keep published refusal messages and type the DB claim guards

Codex review of #18902:

- Every call site keeps the exact message it published on main
  ("Task not found: <id>", "only a ready Task can start.", "cannot retry
  from Dispatch"); the shared contract now takes the message per site and
  only owns the code and data. Baseline strings are pinned as literals.
- createDispatchContext's own missing/non-ready guards, including the
  atomic-claim loser, now emit the same typed receipt instead of a bare
  Error, so a dispatch that races a status change no longer flattens to
  runtime_error. Covered by a dispatcher-level race test.
- Invalid --retry-of keeps task_not_startable but now carries status,
  unmetDependencies, retryOf, and a retry-specific next step.
- Dependency recovery text distinguishes waiting on running deps from
  retrying/unblocking failed ones.
- CLI test adds an unknown-code case so the old-client claim rests on an
  assertion, not a comment; SSH test asserts exact stdout.
- Guide table narrowed to the covered preflight cases; occupancy stays
  runtime_error and is named as such.
2026-09-05 20:27:29 -04:00
Neil 2e2ecc5193 test: order restart fixture readiness around daemon recovery (#18949) 2026-09-05 17:26:54 -07:00
Neil 6a3e446c69 test: make SSH artifact regression fixtures reliable at narrow widths (#18947) 2026-09-05 17:24:12 -07:00
Jinwoo Hong 3bb038a185 docs(relay): 2026-09 reconnect findings, improvement checklist, roadmap, and Roll 2 plan (#18958)
Operator record for the 2026-09-04 relay reconnect incident and the Roll 1
same-cap cell image roll (complete 2026-09-05, selector gen 148), plus the
follow-up checklist, roadmap, and the Roll 2 implementation plan.
Docs only; split out of #18565 so the record merges independently of the code.
2026-09-05 20:20:59 -04:00
Neil 71f2c5d3f9 test: keep artifact share fixtures unexpired across calendar dates (#18955) 2026-09-05 17:15:57 -07:00
Neil 7bb54cc2f7 ci: reduce runner overhead and disposable package compression (#18948)
* ci: reduce PR runner overhead and package compression time

* ci: validate mobile when its dependency action changes
2026-09-05 16:56:57 -07:00
Neil 22a7bfd380 test: align Source Control AI fixtures with current settings (#18941) 2026-09-05 16:45:48 -07:00
Neil ab8e10e298 test: isolate skill cloud fixture ports across workers (#18942) 2026-09-05 16:43:41 -07:00
Neil 59756b8a1c test: deliver real terminal input and preserve setup reports (#18939) 2026-09-05 16:31:11 -07:00
Neil 55dcc5ceee test: pin terminal Codex home to an explicit managed account (#18935) 2026-09-05 16:26:36 -07:00
Neil abdee9ebd3 feat(automations): restore column sorting on the list (#18885)
The flat-table redesign in #16532 dropped the sort UI, orphaning
AutomationListSortHeader, nextAutomationListSort and the whole
AutomationListViewItem layer. Wire them back to the rendered list.

Name and Last run become interactive header cells again; the other six
columns stay plain text. Sorting now spans local and external rows as
one list, so the panel renders per-row components from a single sorted
collection instead of two independent sections.

Two model fixes fall out of that:

- View items key on the host-qualified row key, not the bare automation
  ID. The old builder predated automation-list-row-identity, so under
  All hosts two authorities returning the same ID collapsed in the sort
  tie-break.
- sortAutomationListViewItems takes the locale as a parameter instead of
  reading getIntlLocale(). A hidden global read is invisible to a
  dependency array, and the list result is memoized.

Keyboard traversal and focus recovery now read the sorted order, so
arrow navigation matches what is on screen. The dead unified filter is
removed in favor of the live row/entry filters the page already used.
2026-09-05 16:16:08 -07:00
Neil a730becd7a fix(automation): keep explicit background launches off screen (#18898) 2026-09-05 16:12:52 -07:00
Neil 08c3e85440 test(e2e): stabilize terminal launch and rename menu fixtures (#18928) 2026-09-05 16:11:04 -07:00
Neil 6a5c1f9535 refactor(agent-session): consolidate wire type imports below lint limit (#18930) 2026-09-05 16:02:14 -07:00
Brennan BensonandMerge Sim c58d7a0ecd fix(agent-session): never open a sibling terminal on an unproven create (#18735)
An `agentSession.create` the host could not confirm — it committed the session but
could not publish its tab, and answered `agent_session_operation_unknown` — was
rejected with a bare `Error` carrying a `code`. Nothing in the type said "unknown",
so the verdict lived only in the code string, and the shared transport matcher was
still free to re-read that error's *message*: an unknown refusal whose text ends in
a definitive token (`Owner check failed: method_not_found`) classified as definitive,
which is exactly the answer that permits a legacy sibling terminal.

Make the class the verdict. `StructuredAgentSessionCreateUnknownOutcomeError` is a
sibling of `StructuredAgentSessionCreateRefusalError`, not a subclass, so the nine
existing `instanceof` consumers keep reading "refusal" as "you may fall back" with
zero edits, and an unknown outcome flows down the lost-reply path instead —
replaying the same envelope, re-publishing the tab the host failed to publish, and
parking as visibility-unknown rather than creating anything. Classification now
short-circuits on our own classes, so a message we wrote can never invert the
verdict we already reached.

Adds an end-to-end guard that drives the real classifier through
`startStructuredAgentLaunch`: an unknown outcome opens zero legacy terminals, a
definitive refusal opens exactly one. Ablating the branch turns that green suite red
with `['legacy-terminal']` — the duplicate session the guard exists to prevent.

Co-authored-by: Merge Sim <sim@local>
2026-09-05 15:37:52 -07:00
Brennan Benson 8ab8c950be fix(native-chat): tell a pre-SQLite chat how to carry on (#18808)
A chat whose journal is still the pre-SQLite `log.jsonl` opened empty and indistinguishable from one created seconds ago. It now carries one status row naming the transcript still on disk and saying to send a message to continue, and read restore no longer drops such sessions — an unpublished chat had its tab pruned from persisted state, leaving nowhere for the message to appear.

The notice survives a crash between the epoch commit and its append (re-offered while the epoch holds nothing) and stays out of a journal the same open just repaired, where it would have retired the unreconcilable_prefix marker and permanently ended provider-history recovery.

No importer: the history is explained, not replayed. Nothing reads the remnant beyond its existence, and nothing moves or deletes it.
2026-09-05 15:36:10 -07:00
Brennan BensonandMerge Sim f8780a2c86 feat(native-chat): stop monitored tasks individually (#18807)
* feat(native-chat): stop monitored tasks individually

* test: expect Claude task stop capability

---------

Co-authored-by: Merge Sim <sim@local>
2026-09-05 15:35:39 -07:00
Brennan BensonandMerge Sim 2513e21390 fix(native-chat): publish structured session status from the host so the sidebar never goes stale (#18776)
* fix(native-chat): publish structured session status from the host

The sidebar learned whether a structured chat was mid-turn by replaying
the session journal in the renderer, through a reader whose lifetime was
tied to the chat pane. Hiding the pane stopped the reader before the
turn's settlement arrived, so the row stayed on "working" until the chat
was reopened. The same coupling meant a tab never opened this session
showed no status at all, and a reloaded renderer lost every settled row.

The host owns the journal, so it now projects each session's status once
per journal publication and fans the changes out on one stream per client
(`agentSession.subscribeStatus`). The projection survives eviction of an
idle session's provider child and is republished when readable sessions
are restored. The renderer bridge subscribes to that feed per runtime
target and never opens a transcript reader; the observation hook is gone.

Additive wire surface behind the existing structured capability; old
hosts reject the method and the renderer retries, showing no status.

* fix(native-chat): negotiate the status feed and stop losing a change on subscribe

The status stream is additive to a surface that already shipped, so a host
advertising agent-session.structured.v1 can still answer subscribeStatus with
method_not_found. Every renderer error path reconnected, so a remote host one
release behind got a relay round-trip every 5s and no sidebar status at all.
Give the method its own capability and probe it before subscribing; a failed
probe still retries, an absent capability does not.

Re-projecting on subscribe also wrote straight into the shared cache, so a
second client could pin the first to a stale summary. Route those diffs
through publish() before the arriving subscriber is registered.

* fix(native-chat): bound the status prompt, merge snapshots, and prove the unread path

One status frame carries every retained session and a send admits 256 KB per
prompt, so ~16 large-prompt sessions could push the snapshot past the 4 MB
outbound guard and into the retry loop. Bound latestPrompt to the same
200-char single-line preview every other agent-status row already carries.

A snapshot also replaced the cached map wholesale, so the empty first frame
from a restarting host retracted every row before restore republished them.
Merge instead; the tab map, not this feed, decides which sessions are listed.

Tests: the hidden-pane claim now sits at the host, where a journal with no
transcript subscriber is driven from running to idle; the RPC test reads a
real projection instead of its own stub.

* fix(native-chat): merge the duplicated status-event type import

* test(native-chat): pin the restart status publication, and log the unsupported host

Startup restore indexes a readable session and publishes its status, which is
what puts a never-reopened tab back in the sidebar. Only an Electron screenshot
covered that wiring; a sitting status subscriber now pins it directly.

The terminal "host too old" branch was silent, so a mixed-version report showed
an empty sidebar with nothing in the log to explain it.

---------

Co-authored-by: Merge Sim <sim@local>
2026-09-05 15:35:03 -07:00
Brennan BensonandMerge Sim 471a5f4aa7 feat(native-chat): model Codex MCP and web-search items instead of leaking opcodes (#18763)
* feat(native-chat): model Codex MCP and web-search items instead of leaking opcodes

Codex's app-server sends 19 thread-item types; the structured translator handled
six. The rest fell through to a generic gray `codex · item:<type>` row, even
though the disposition table's own comment says it exists so a new item type
cannot leak like that — the table had one entry.

Give `mcpToolCall` and `webSearch` real tool-call bodies, and chrome `sleep`,
which carries only a duration and renders as nothing in Codex's own TUI.

`subAgentActivity` and `collabAgentToolCall` deliberately keep their generic
rows. They arrive in real sessions today and are currently the only visible
sign a subagent is running; hiding them before the subagent UI lands would
render minutes of work as an idle turn. Tests pin that they stay visible.

MCP tool names pass through verbatim when they contain `:`, `.`, `/` or `__`,
so `mcp__server__tool` survives instead of being title-cased into nonsense.

* fix(native-chat): keep Codex MCP tool identity and web-search results on the row

Four fixes to the Codex MCP / web-search item bodies:

- Drop the title-casing display name. `get_forecast` became `Get Forecast`,
  which no longer matches the raw snake_case identifiers that the diff
  renderer, question parsers, and tool-input previews dispatch on, and does not
  match how the Claude lane or the sibling `shell`/`apply_patch`/`web_search`
  bodies name a tool. The row name is now `server/tool` verbatim, the bare
  `tool` when no server is given, and `mcp` when the item names no tool at all.
  Server-qualifying also stops an MCP tool that happens to be called
  `apply_patch` from hijacking the diff renderer.

- Pass the MCP call's own `arguments` as the tool input instead of wrapping it
  in `{server, tool, arguments}`. Row-label derivation only reads top-level
  keys, so the wrapper degraded every MCP row to a truncated raw JSON blob.
  A non-object `arguments` stays addressable under a key rather than being
  dropped; an absent one becomes null, which labels as empty rather than `{}`.

- Carry a web search's `results` as the call output, bounded like every other
  inline payload and omitted when there are none. They were being dropped
  entirely, which showed less than the generic fallback row it replaced.

- No streaming branches were added for these two item types: the Codex delta
  stream is a closed set of six methods that neither can reach, so such
  branches would be unreachable.

* fix(native-chat): label Codex web searches and argument-less MCP calls

A row label is derived from top-level `input` keys only, so a webSearch
whose detail lives inside `action` — an opened page, an in-page find, or
a bare `other` — fell through to the raw JSON of the whole input, as did
the empty `query` Codex leaves on a completed search. Hoist the action's
`url`, `pattern` and `type` beside the query, keep the full `action`
object so the expanded detail loses nothing, and emit no input at all for
the start frame.

An MCP tool that takes no arguments sends `arguments: {}`, which passed
straight through and labelled the row a literal `{}`; treat it as absent
so the row reads as a bare `server/tool`.

Split the durable-identity half of the item translator into
`codex-thread-item-identity.ts`, re-exported so every existing import is
unchanged, to keep both files under the max-lines cap.

---------

Co-authored-by: Merge Sim <sim@local>
2026-09-05 15:33:04 -07:00
Neil 239e3c7e0b test: select seeded workspace and confirm sidebar reveal (#18921) 2026-09-05 15:29:46 -07:00
Neil 9faa27c5f4 test: align desktop platform oracles with native behavior (#18915) 2026-09-05 15:12:19 -07:00
Neil d7767fb196 perf(worktree): remove redundant creation and terminal startup work (#18793)
* perf(worktree): remove redundant creation and terminal startup work

* test(worktree): cover optimized creation call signatures

Preserve explicit branch adoption, WSL callback routing and sparse cleanup expectations.

* perf: preserve user Git checkout worker settings

* perf(git): skip malformed remote base probes

* perf(cli): avoid loading other agent hooks for Codex preflight

* fix(build): retain Codex preflight entry for packaged CLI

* test(ssh): wait for replacement PTY before lease recovery input

* test(ssh): verify recovered shell execution and lease ownership

* test(electron): reap isolated macOS crash reporters on teardown

* test: allow either observed self-exit snapshot ordering

* test: capture frozen-host input recovery evidence
2026-09-05 15:08:22 -07:00
Neil 7bec98466b test: canonicalize setup fixture paths before worktree lookup (#18912) 2026-09-05 15:04:52 -07:00
Neil 51eed5a1bc feat(cli): report SSH host platforms (#18896)
* feat(cli): report SSH host platforms

* feat(cli): include SSH connection status

* fix(cli): preserve unknown SSH connection state
2026-09-05 14:35:45 -07:00
Neil 2afc8b55ef test: pin worker visibility fixture command and handle (#18897) 2026-09-05 14:34:35 -07:00
Neil dce5ebd83d test: isolate native crash restoration and refresh stale fixtures (#18883)
* test: isolate native crash restoration and seed current integration facts

* test: await scoped GitLab preflight before URL transition checks
2026-09-05 14:18:22 -07:00
Jinjing cd70048092 Fix favicon retention across same-origin navigations (#18879)
* fix: retain favicons across same-origin navigations

Move favicon clearing from did-start-loading to did-start-navigation and
only clear when origin changes. Chromium re-announces favicons only when
the icon URL list changes, so clearing on every load orphans same-origin
navigations. Extract favicon URL validation into a shared module.

* fix: drop favicon on cross-origin redirects

When a same-origin navigation redirects to a different origin, the favicon should be cleared to prevent stale icons from displaying the wrong site's identity.
2026-09-05 14:10:43 -07:00
Neil 5cec2c2dfc test: preserve Docker context in isolated VM recipes (#18884) 2026-09-05 14:07:08 -07:00
Brennan BensonandMerge Sim ddc5b75ac7 feat(native-chat): label Codex tool rows by what the command actually did (#18760)
* feat(native-chat): label Codex tool rows by what the command actually did

Codex's app-server `commandExecution` item carries `commandActions`, which
already classifies each command as a read, a search, or a directory listing
with the target path, name, or query extracted. Orca ignored the field, so
every shell call rendered as an undifferentiated row of raw argv.

Read it and name the row by its class, keeping the raw command and cwd for the
expanded view. Unclassified commands are untouched: absent, null, or malformed
`commandActions` produces byte-identical output to before.

Rank the search term above the command in the shared label keys so a classified
search row reads by what it looked for rather than the shell text that ran it.
No first-party tool input carries both keys today, so this only reaches the new
rows; an MCP tool supplying both would prefer its search term.

Note `commandActions` is the app-server spelling. `parsedCmd` is the rollout-file
shape and never arrives on this lane; a test pins that it stays ignored.

* feat(native-chat): give tool rows a category glyph beside their word

A row named only by a word makes the reader parse text to tell a read from
a search. Pair the word with an icon: icon for category, word for action,
argument for target.

Name the full eight-category vocabulary in `src/shared/native-chat-tool-icon.ts`
now — read/search/listFiles/unknown/fileChange/webSearch/mcpToolCall/
subAgentActivity — even though only the classified shell categories reach a row
today, so the MCP and web-search rows landing separately inherit these names
rather than coining their own. Glyph ids are the lucide spelling shared by
`lucide-react` and `lucide-react-native`, so mobile can resolve one name to its
own component when it adopts this; mobile rows stay text-only for now.

The glyph is decorative and `aria-hidden`: the word is the accessible name, and
never renders without it. One glyph per category, fixed across running,
completed, and failed — a row that swapped icons on completion would read as
changing identity — so the run header's active row also takes its category glyph
instead of the generic wrench it fell back to once these rows stopped being
called `shell`. A word outside the vocabulary gets the terminal glyph rather
than a blank slot, so rows stay left-aligned.

Also stand `.` in for a `listFiles` action whose `path` is null, which is what a
bare `ls` sends. The row named the action and then showed the raw argv as its
target; now it names the directory it listed.

* fix(native-chat): hold the tool run header's glyph fixed and size its slot to 16/14

The header swapped its leading glyph on settle: the active tool's icon while
running, a check once done. That is the identity swap a fixed per-category glyph
exists to prevent — the row appeared to become a different thing when it
finished. Name the header by the run's latest tool in both states and move the
completion check to the trailing edge, where the rest of the state signal already
lives.

Size both header slots to the mock's 16px slot with a 14px glyph, matching the
tool rows beneath them and the subagent summary row landing separately. They were
24/16, so the icon columns sat 8px apart and broke the left alignment the icon
treatment depends on.

The fixity test walks running, completed, and failed and pins the leading glyph
of every row by lucide's own class name, so a swap shows up as a different name
rather than a still-present icon.

* fix(codex): stop a classified shell row from asserting facts the command doesn't support

Three claims the `commandActions` row model was making on its own:

- `listFiles` with a null path was given `path: '.'`. Codex sends null for a
  recursive walk and for the repo root, and the invented path flows into
  `createToolInputDisplay().filePath`, which mobile turns into a tappable
  "open file" link onto a directory — an affordance that can only fail. The row
  now keeps the raw command, which is what the label logic already falls back to.
- A command whose actions classify as two different things (`cat a.txt && ls src`)
  was named after the first one, silently dropping the rest. Recognized actions
  must now agree on one class; a repeat of one class keeps the class and only a
  target every entry names.
- `read` lifted `name` into the journal payload, where no label ever reads it —
  `path` always wins — so it was bounded weight carrying nothing.

* fix(native-chat): give an unmodelled tool row a generic glyph, not a terminal

The row-word vocabulary named seven words, and everything else fell through to
the terminal glyph — which reads as "a shell ran here" for rows where nothing
says one did. Codex's own `apply_patch` row, `Grep`/`Glob`/`Task`/`WebFetch`/
`TodoWrite`, and every `mcp__*` tool all rendered a terminal, leaving the
declared `mcpToolCall` and `subAgentActivity` categories unreachable.

- Split the vocabulary: `unknown` stays the shell command Codex could not
  classify and keeps the terminal, while a new `other` carries the generic
  wrench that unmodelled words now fall back to.
- Read the edit family from `EDIT_TOOL_NAMES` and the command tools from
  `isCommandToolName` rather than restating either. Command tools resolve first:
  `isEditToolName` counts `shell`/`exec` as possible patch carriers, and a shell
  row is not an edit.
- Result rows get no category glyph. Their word is `translate(…, 'Result')`, so
  keying a category off it resolved a different glyph per locale; an empty slot
  keeps the rows aligned.
- The header and the row now resolve through `NativeChatToolIcon`, so one `Grep`
  run can no longer show a wrench in the header and a terminal on its line. The
  glyph map and the unused `category` prop go with the duplication.

* fix(native-chat): give the projected Diff row the file-change glyph

Every Codex fileChange item projects to a tool call named `Diff`, which the
edit set does not name — it names the tools that carry the edit in their own
input. So a run whose body renders an edited-file card was headed by the
generic wrench.

* fix(codex): stop a classified shell row offering a folder as a file to open

A listFiles action's path is a directory, and a search action's path is the
root it scanned. Lifted under `path`, both became the row's file target, which
mobile renders as a tappable open-file link that can only fail — the same dead
link the removed `{ path: '.' }` stand-in would have produced. They lift to
`directory` instead, which still labels the row but is never a file target.

* fix(mobile): keep the terminal glyph on a classified Codex shell row

Mobile's run header picks between a terminal and a generic glyph by tool
name. Now that the host publishes `read`/`search`/`list` for the same
commands it used to publish as `shell`, that name check answers false and
a command that really ran heads its run with a wrench.

Ask the shared category vocabulary instead. Mobile keeps its two icons —
porting the full glyph set is a separate lane.

* fix(native-chat): say what the run header's glyph actually guarantees

The comment claimed the header names the same tool in both states, so its
glyph cannot change on settle. It can: the live header names the running
call while the settled one names the run's last tool call, and with
out-of-order completion those differ. The glyph is fixed for whichever
tool the header names — say that, and drop the never-taken running branch
from the settled header's call.

Also pin the other half of the file-target rule: `read` keeps `path`, so
its row stays tappable, where `list`/`search` lift a folder to
`directory` and offer no target at all.

* fix(native-chat): give a rollout-transcript shell row the terminal glyph

`exec` and `local_shell` are what the Codex rollout transcript names a
shell call — `native-chat-edit-normalize` already treats those three
words as the command tools — but the activity set the glyph vocabulary
reuses carries neither, so both rows headed a real command with the
generic-tool wrench.

Named in the vocabulary rather than in that activity set, because that
set also picks the running row's copy and this is only about the glyph.

* fix(mobile): pick the run-header glyph from the call's input, not its word

Codex now names a classified shell row `read` / `search` / `list`, which
lowercase to Claude's own `Read` / `Grep` / `Glob`. Mobile has only a terminal
and a wrench, so keying that choice on the row word gave Claude's filesystem
tools a terminal for a shell that never ran.

The input separates them: Codex keeps the raw command on a classified row,
while Claude's `Read` carries only a file path. `isShellActivityToolCall`
replaces `isShellActivityToolRow` and asks the command tool names first, then
the call's input.

* fix(native-chat): give the projected diff fixture its required digest

* fix(native-chat): head a settled run with a glyph the whole run shares

The settled run header drew the glyph of the run's last tool call while the
text beside it summarizes the run's first three, so a ten-call run ending in a
`read` showed an eye above "shell npm test · shell git status · …" — a category
the summary never described.

Resolve the header's glyph from every call in the run instead: the shared
category's glyph when all agree, the generic tool glyph when the run spans
categories, and no glyph when there are no tool calls. The running header still
names the active call, whose glyph is true of it.

---------

Co-authored-by: Merge Sim <sim@local>
2026-09-05 14:03:45 -07:00
Neil 1924c8f5b1 feat(perf): lint repeated sort setup and schedule regression contracts (#18822)
* feat(perf): audit comparator setup and schedule performance contracts

* test(sqlite): close readers after expected busy failures

* ci(perf): trigger contract workflow on the contract files themselves

Without these paths a contract rename lands green on PR CI and only
breaks the next nightly, where nobody owns the failure. Also run the
OS-independent source audit once instead of on all three runners.
2026-09-05 13:56:06 -07:00
Neil d8c4c83063 test(e2e): fence SSH recovery and release exited Electron pipes (#18880) 2026-09-05 13:53:27 -07:00
Jinwoo Hong 61ebffa86e fix(runtime): bound terminal-wait blocked-prompt rules to the live screen bottom (#18817)
The Codex prompt rules in terminal-wait-detection scanned the whole retained
tail (up to 256 KiB) with lastIndexOf, so any quoted prompt phrase in
scrollback registered as a live prompt. A Codex agent working on Orca prints
rg hits from this very file; one such line ~300 lines above an idle input box
made `orca terminal send` refuse with agent_prompt_blocked and `terminal wait
--for tui-idle` report codex-interactive-prompt. `clear` did not help because
the detector reads the retained tail, not the visible screen.

A prompt that owns the terminal is at the screen bottom, so every blocked
rule now runs over the last 12 non-blank lines (real Codex dialogs are 4-8
lines), the way the cursor approval rule already was. The returned index is
offset back into full-tail coordinates so ready-header comparisons keep
working. The sentinel fast path is unchanged.

Line-window primitives move to terminal-wait-tail-window.ts to keep the
detector under the max-lines cap.
2026-09-05 16:49:59 -04:00
Neil 79350f4551 test(e2e): follow current sidebar project and activity actions (#18878)
* test(e2e): follow current sidebar project and activity actions

* test(e2e): reopen activity after revealing a workspace
2026-09-05 13:39:56 -07:00
Jinwoo Hong a3c1d32995 fix(relay-ops): per-region cell latency bar and attributable preflight failures (#18877)
The incident monitor froze three healthy 15-minute production gates on
2026-09-05 because asia-east2 cells are judged against a bar calibrated
for us-central1. A cell's /ready fetches the auth JWKS and runs SELECT 1
against Cloud SQL, both in us-central1, so from the US GitHub runner the
asia-east2 round trip measures p50 0.88 s / max 2.7 s against 0.08-0.5 s
for us-central1 cells.

Give cell.<id>.latency_ms a per-region threshold (us-central1 2000,
asia-east2 4000) carried on IncidentCellExpectation from the tfvars
region. Director and auth latency rules keep the flat 2000 bar, and hard
faults are still caught by the .health/.ready equal-1 checks and the
probe's 8 s fetch timeout.

Also name the signal and its observed/threshold in the live preflight
failure message, keeping the source/code tokens other tooling matches on.
2026-09-05 16:21:50 -04:00
Neil 9c3d957ae3 perf(automations): reuse collation setup for list name sorting (#18823) 2026-09-05 13:19:25 -07:00