mirror of
https://github.com/stablyai/orca.git
synced 2026-10-31 17:17:38 +00:00
a75cdc828771891fb3554e866ebdb605e0bebdfa
90
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
85e690ff60 |
fix(provider-process): kill a provider group its SIGKILLed supervisor leaves behind (#30997)
* fix(provider-process): kill a provider group its SIGKILLed supervisor leaves behind * fix(provider-process): re-read a provider start in its read second; poll instead of sleeping in tests * fix(provider-process): keep the report channel through the Codex session adapter; re-prove same-second starts * fix(provider-process): never kill a leaderless provider group; list guard files in the CLI project |
||
|
|
e43f3e7e04 | feat(opencode): open OpenCode 2 in the chat view and list 2.x models before the first message (#28424) | ||
|
|
002320c17d |
fix(native-chat): verify the actual CLI conversation during resume (#29124)
* fix(native-chat): identify the owner when resume create is refused * test(native-chat): consolidate transcript refusal cases * test(native-chat): keep ownership coverage at the launch boundary * test(native-chat): update adoption fixture for early ownership admission * test(native-chat): align resume admission assertions with host ownership * test(native-chat): keep RPC coverage focused on ownership proof * test(native-chat): assert blank request presence before optional source * fix(native-chat): admit the actual CLI source during chat creation * fix(native-chat): satisfy source admission lint checks * test(native-chat): provide the waiting hook prompt * test(native-chat): isolate source admission fixtures from host homes * test(native-chat): canonicalize adoption fixture directories * test(native-chat): preserve early ownership admission in source stack * fix(native-chat): preserve resume refusal behavior and honest transcript feedback * fix(native-chat): retain import read evidence in typed result * fix(native-chat): preserve admitted source and prepared execution home * fix(chat): retain source evidence for dispatched adoption * fix(chat): keep source capture within module limits * test(chat): allow host evidence in create routing * fix(native-chat): admit an unsaved transcript under a linked account folder A missing transcript stayed unresolved while its account root resolved through symlinks, so containment failed and a not-yet-saved conversation showed a load error instead of the unsaved notice. * refactor(native-chat): move the newest-item import window into its own module Keeps journal-legacy-import.ts under the line limit after the source identity observer landed on top of the streaming import. * fix(native-chat): restore the refusal error import lost in the main merge * fix(native-chat): admit Pi transcripts against the root Session History walks A PI_CODING_AGENT_DIR that already ends in sessions, or any custom folder, made admission expect <dir>/sessions while the lookup searched <dir>, so a found conversation was refused. * refactor(native-chat): admit and look up CLI transcripts in one store per account Each agent's import described where an account keeps its conversations twice, once for the path check and once for the lookup, and the two could disagree (the linked-folder and Pi custom-root refusals). The import now names one conversation store per account; the create path resolves it once and both the hook-path check and the fallback lookup judge that same store. OpenCode's store is its exact database. |
||
|
|
fa59ed0295 |
test: advance mocked OpenCode probe close timers (#29772)
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com> |
||
|
|
b16a71c7fb |
ci: reuse Windows hook builds and shorten test setup and shutdown (#29439)
* ci: remove Windows build and persistence test overhead * ci: collect unit phase diagnostics during PR checks * test(ci): seed hook reuse fixture from the canonical prebuild The hook reuse regression first built into a missing private --output, so it always reached Cargo (74s warm on Windows). Build through the default output instead, which reuses a warm canonical prebuild, then copy its executable and fingerprint into the private fixture root. * fix(lint): clear inherited max-lines and broad object tab parameter after main merge Combine the duplicate shared/execution-host imports in runtime-host-services (301 -> 300 lines) and type the overlap test's tab mapper with the RuntimeMobileSessionClientTab owner type instead of object. * fix(ci): align inherited runtime and workflow contracts * test: establish changed runtime probe signatures deterministically * test: await asynchronous daemon profile cleanup * test: shut down owned daemons before deleting profiles * test: clean up oracle daemons during final app close * test: recognize verified Linux zombie exit during daemon teardown * fix: preserve folder SSH ownership when project groups conflict |
||
|
|
3074852b7c |
fix(orcad): seal integration credentials on the headless server instead of storing them in plaintext (#29412)
* fix(orcad): seal integration credentials on the headless server instead of storing them in plaintext The Node SecretStore always reported encryption unavailable, so every credential writer on orcad fell back to plaintext. orcad now seals with AES-256-GCM using a host-owned master key kept in the OS keyring (macOS Keychain, Linux Secret Service) when reachable at first start, else in a 0600 profile file bound to the machine ID. A key that cannot load fails closed instead of downgrading. Existing plaintext credentials are sealed on startup without loss. * docs: describe how the headless server seals credentials and its limits * fix(orcad): unique credential staging files, no keyring items for temp profiles, isolate startup test * fix(orcad): keep the credential key with the credentials, one record per machine * fix(orcad): never re-key over an unreadable record, serialize first-start key creation, refuse an unrestricted key file * fix(orcad): verify a key record's Windows ACL and wait out a concurrent first start's keyring write * fix(orcad): keep a keyring claim and fail closed when its store result is uncertain * fix(credentials): restrict the staged file's Windows ACL before it replaces a credential * fix(orcad): a temporary profile never creates a key in, or re-seals, the real credential home * ci: baseline the two single-file credential ACL restrictions * fix(credentials): stage Windows credentials born restricted and verify the key record asynchronously; document rollback * test(bitbucket): inject the write failure into the Windows staging writer too |
||
|
|
16eaceb6a3 |
fix(orcad): orca serve owns its Claude and Codex accounts again (#29209)
* fix(orcad): orca serve owns its Claude and Codex accounts again (#28962) orcad never wired account services, so every accounts.* RPC failed once orca serve moved onto orcad. Build the same account services the desktop builds (now in provider-account-services.ts), made Electron-free, and install them in orcad except on a WSL host, whose desktop owns its accounts. * test(orcad): keep account-services test off the real userData when XDG_CONFIG_HOME is set * fix(orcad): structured Codex chats and session discovery follow this host's managed accounts Create orcad's account services before the runtime so its structured Codex launch/read resolvers and catalog-probe prep are constructor deps, as on the desktop. A client-sent account home still wins, and a WSL host keeps them unset so the desktop-pushed CODEX_HOME applies. AI Vault session discovery now includes this host's managed Codex homes. * fix(orcad): automatic Codex resumes follow the account home their session was recorded in Share the desktop's resume launch prep as a factory and wire it into orcad's PTY registration and AI Vault resume, so a pane restored after an account switch resumes from its origin home. A client-sent account home keeps resuming from itself. --------- Co-authored-by: Neil <neil@stably.ai> |
||
|
|
1a19671ec5 | test: remove low-value coverage and shorten expensive fixtures (#29135) | ||
|
|
941a0f2a8a |
fix(opencode): refuse a picked OpenCode model in any \\wsl$ repo (#28236)
OpenCode in a \\wsl$ repo runs in WSL whatever the project's setting, and Orca can't verify a model there yet, so picking one is refused, as it already is for a project set to WSL, instead of being checked on Windows. |
||
|
|
76d1c68ab5 |
Resume in New Native Chat for OpenCode (#28072)
* feat(chat): Resume in New Native Chat for OpenCode terminal conversations An OpenCode conversation started in the terminal now offers "Resume in New Native Chat" in its Session History row menu and its tab's right-click menu, as Claude, Codex, Grok and OMP do: a new OpenCode chat takes it over, showing its history and continuing it. OpenCode 2.x rows (`opencode2`) and 1.x rows both open an OpenCode chat. Two pieces kept OpenCode off the shared adoption path. Its account is a locator, not a folder, so the transcript import now works over the account the chat pins, whatever its kind; the folder agents wrap their lookups in directoryTranscriptImport, unchanged. Its conversations live in a database, so the journal import reads the found database with the reader the OpenCode transcript view already uses. OpenCode refuses to reopen a session from another folder, so the workspace rule covers it too. * test(chat): Pi is now the agent with no transcript import * fix(chat): find an OpenCode 2.x terminal tab's Session History row The pane reports `opencode` while Session History lists the conversation as `opencode2`, so the pane header's and tab menu's Resume in New Native Chat found no row. Match by the chat agent that takes the conversation over. * Fix OpenCode chat adoption environment and pane eligibility * test(chat): adapt parent adoption fixtures to account identities * test: keep only the tests that pin user-visible behavior * refactor(chat): map a Session History row to its chat agent where the chat starts Both Resume in New Native Chat callers repeated the row-to-chat agent lookup. The launch now takes the row agent and maps it once, so the Session History actions file stays unchanged. |
||
|
|
26a265d704 |
Host plans agent launches with the window's runtime rule and one runtime fact (C1) (#27692)
* feat(agent-launch): host plans agent launches with the window's runtime rule and one runtime fact
Host-started agent launches now pick their platform with the same rule the
window uses (project runtime, else a \\wsl$ path, else the host), and resolve
one runtime fact that supplies both the command quoting and the PTY shell.
Adds the agent.launch.terminal-inputs.v1 capability and its optional
title/extraAgentArgs/requestKind/terminalOnly fields; no sender yet.
OpenCode's createAgentSession stays on today's rules until C2 (temporary).
* fix(agent-launch): one shared runtime rule, a derived fact, and only quoted commands carry it
Round-1 review fixes for C1:
- Window and host read a workspace's runtime through one shared resolver
(local-workspace-execution-runtime.ts); a repo-less \\wsl$ folder gets no
runtime, as in the window.
- The fact is { platform, remote, pty } with the spawn's own PTY type; the
quoting shell is derived from it, never stored.
- createAgentSession uses the same fact (its pre-C1 exception is deleted).
- Worktree startups quote from a fact for the repo and pass it on; a runtime
that needs repair fails the startup, not the create. `shell` is required
when building a launch command.
- The host planner stamps a fact only on commands quoted with it; a
caller-quoted command keeps the spawn's own rule.
- Qoder probes where the process runs (\\wsl$ cwd, then a WSL shell).
- wslShellRule 'host-legacy' keeps today's WSL-shell outcome where no
project chose a runtime (temporary, removed by C8).
- terminalOnly reaches the early-tab decision with an accurate receipt
sentence; the unused client gate is removed.
- Adds one host C0 row for a \\wsl$ repo with no saved runtime.
* fix(agent-launch): round 2: keep request fields for C2, extend main's resolver, one answer per workspace
Problem: the first round still carried request fields and window-parity
switches that only the next step sends, added a new shared resolver that
also changed window code, and left some background launches using a
different runtime answer than the one they spawn with.
- Move the agent.launch request fields, their capability and fingerprint
handling, and the prompt/Codex parity switches out to the next step;
today's host behaviour is kept.
- Drop the shared resolver: main's resolveLocalWorkspaceRuntime now finds
the project by repo id or source repos, folders borrow their single
local repo, and a \\wsl$ path is not refused on a stale distro list.
Window code is back to main.
- Agents started in an existing worktree use that worktree's answer; the
phone's fallback paths spawn with the answer they quoted for, and a
runtime that needs repair shows its message instead of a plain shell.
- The OpenCode model check looks where the agent will run.
- A Windows --shell in a \\wsl$ workspace names the path, not a runtime
setting that cannot change it.
- The temporary folder rule names its follow-up, STA-10231.
* fix(agent-launch): OpenCode model picks in WSL folders keep today's Windows runtime
Problem: once a folder follows its repo's WSL runtime, an OpenCode launch
that picks a model would be checked and started inside WSL, where Orca
cannot yet verify OpenCode models, so picks that work today could be
refused.
Where main had no runtime for the scope (only folders), a model-picked
OpenCode launch keeps main's answer. Start, resume and the worker's model
check read the same answer. Temporary until OpenCode models are verified
inside WSL.
* fix(agent-launch): round 3: OpenCode keyed on the agent, new worktrees quoted for where they land
Problem: round 2's OpenCode exception keyed on a model pick, so a resumed
chat could run somewhere other than where it started, and the worker's
model check read WSL from the Terminal shell and refused folders that work
today. A new worktree's startup used the repo's answer even when the
worktree lands in a WSL share.
- In a folder whose repo runs in WSL, every OpenCode launch keeps main's
Windows answer: create, resume and the worker's model check agree. The
model check reads WSL from the path alone, as main did. STA-10251.
- A new worktree's startup is planned for its WSL-share base folder when
one is set (newWorktreeLaunchScope).
- A \\wsl$ working directory runs in WSL whatever the project says, so
non-OpenCode agents there are written for Linux.
- The planner test derives the answer instead of passing it in.
- Smaller shapes for the PTY settings type, the cache-less resolve,
executionRuntimePlatform, spawnShellInputs and two comments.
* fix(agent-launch): round 3c: one labelled rule for where the path decides; OpenCode follows the WSL-share fix
Problem: OpenCode in a repo on a WSL share whose project is saved as
Windows host still got PowerShell quoting typed into WSL, because the
WSL-share fix skipped OpenCode. The fix and the two temporary parity
rules were also split across two returns, so deleting the temporary
ones could take the permanent fix with them.
- One expression decides when the path places the agent, each term
labelled: WSL share + Windows host (permanent fix), folder + Windows
host (STA-10231), OpenCode in a folder (STA-10251).
- OpenCode now gets the WSL-share fix too; a model pick there is refused,
as main's model check already did.
- The OpenCode entry-point tests set a WSL Terminal shell, so the model
check test fails if it reads WSL from that shell again.
- Tests trimmed with no coverage loss; the spawn-precedence unit test is
dropped (the C0 WSL-share row covers the reachable case).
* fix(agent-launch): keep the window's WSL answer for panes the window spawns
Live Windows QA (Terminal shell wsl.exe, global default Windows host, repo
with no project setting) saw an agent command written with PowerShell
quoting typed into a WSL pane, so codex lost the apostrophes in its prompt.
The host wrote the command for its own answer (Windows, PowerShell), but on
routes where the window spawns the pane (the phone while a window is open,
a focused desktop chat create, `orca terminal create --command` in a repo)
the window spawned it by the Terminal shell, which is WSL. Quoting and the
pane came from two different answers.
Those routes ran in WSL with Linux-style quoting on main and worked, so the
temporary host-legacy term (STA-10231) now also covers them: when the window
spawns the pane, the path and Terminal shell decide, as on main. Background
routes keep the approved change (PowerShell instead of cmd.exe with
Linux-style quoting, which was broken on main). createTerminal and the
pre-launch planning share one `windowSpawnsTerminal` rule so they can't
disagree about who spawns the pane.
* Revert "fix(agent-launch): keep the window's WSL answer for panes the window spawns"
This reverts commit
|
||
|
|
7246a0e269 |
Fix delayed agent launches such as sleep 30 && claude (#28205)
* fix(terminal): preserve preparation and discovery for delayed agent launches * refactor(agents): centralize launch intent recognition * refactor(agents): keep launch intent in one module apart from process recognition Move recognizeAgentLaunchCommand next to the sleep-prefix scanner so the strict process-table matcher is untouched, name the module away from the agent-launch funnel files, share one executable-name normalizer, and colocate the preparation tests with their modules. |
||
|
|
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. |
||
|
|
88a82c0a7d |
refactor(providers): import provider contracts directly (main) (#27813)
Move the 52 non-runtime, non-ipc importers of src/main/providers/types.ts onto the per-domain contract files. The barrel itself stays until the runtime and ipc importers move (3.4b/3.4c). Co-authored-by: m4air <m4air@Mac.localdomain> |
||
|
|
83db5b5ec1 |
[CI] Reduce test teardown waits and duplicate CI setup (#27253)
* [CI] Reduce test teardown waits and duplicate CI setup * [CI] Map browser ownership to the combined test check |
||
|
|
1912c90141 |
Extract subprocess handling into @orca/process-host (#26963)
Extract subprocess execution and process contracts into @orca/process-host with explicit public entry points, independent checks, and one Node 18 compatible CommonJS runtime. Migrate application consumers and integrate package builds, dependency selection, import enforcement, and runtime resource staging. Preserve daemon startup ownership and profile isolation, and qualify development rebuild recovery and concurrent output publication. Remove unnecessary aliases, test forwarding exports, and a duplicate output scan. Validation: full typechecks, changed-code quality, package and artifact checks, native Windows builds/types plus 306 runtime tests, and independent native reviews. Matched full E2E controls on original main and unchanged reviewed head both pass all 17 applicable tests (nine skips). Earlier intermittent E2E failures are documented in the PR; no assertions were weakened. The final combined-tree audit preserves both branches with no import-boundary violations. |
||
|
|
8455c97183 |
refactor(pty): key the PTY registry by execution host (#26995)
The PTY ownership map and provider registry now speak ExecutionHostId (`local` / `ssh:<target>`), with this machine as an ordinary host instead of `null`. An unowned PTY id resolves through getPtyExecutionHost: a bare id stays local by explicit rule (daemon restores arrive before ownership is recorded), while a paired-runtime `remote:` id or a malformed `ssh:` id now fails closed instead of being driven on this machine's provider. Co-authored-by: m4air <m4air@m4airs-MacBook-Air.local> |
||
|
|
6ba86414bf |
Show model and effort choices for every agent before its session starts (#26407)
* feat(native-chat): one saved model catalog for every structured agent Every agent registration now declares how its models are listed without a session (a probe, or explicitly none). Probes share one supervised runner (15 s budget, bounded output, teardown on every path). Grok lists from initialize (or x.ai/models/list), OpenCode from models --verbose, Pi from --list-models; Claude moves onto the shared runner. ACP option reads no longer copy the running model's effort menu or selection onto every model. Desktop and phone pickers read the same catalog for every agent, with a non-pickable provider-default model pill before any list exists. * fix(native-chat): host names a listing's configured model; live listings saved for every agent - The catalog answer carries an optional listingNamesConfiguredModel field, set per registration (Codex, Grok) and per workspace; pickers name the listed default from it instead of a Codex-only client flag (older hosts fall back). - Grok, OpenCode and Pi live option reads save their model list through one host step, keeping the account's known default and per-model efforts. - Phone reads the host catalog like the desktop and seeds every agent. - Saved picks accept any known agent id (OpenCode, Pi). - ACP agents with no session-free listing register as unavailable; Grok's live effort menu falls back to the session's when the advertised one is empty. * fix(native-chat): discovery and live listings kept apart; every agent's live listing saved by the host; phone-created chats name their model * fix(native-chat): phone new-chat mark clears on the chat's first live answer; Claude's startup listing reaches the host catalog * fix(native-chat): live-listing type lives beside the catalog entry so wire types don't pull the store's persistence into the phone * fix(native-chat): the model picker never waits on a listing; a first listing lands in place * feat(native-chat): the host lists every agent's catalog ahead of use and traces each listing * feat(native-chat): a new Claude chat names the model and effort its config resolves to * test(native-chat): catalog service fakes gain prewarm * test(native-chat): prewarm span recorder without a type assertion * fix(native-chat): project agent config overrides the account default only when it sets a model or effort A committed .claude/settings.json for permissions or hooks, or the settings.local.json Claude writes on always-allow, no longer hides the configured Claude model in that project. Unreadable config still counts; Codex config.toml follows the same rule. * test(native-chat): the Codex picker read and the launch it starts resolve one account key, through the real resolvers Runs resolveHostCodexHomePathForLaunchReadOnly and prepareCodexRuntimeHomeForLaunch as the runtime service wires them, for the system account on the real home, on the shared runtime home, and a managed account. Also scopes the prewarm suite to agents in use. * fix(native-chat): Claude's configured model and effort are saved together behind the project check; prewarm lists only agents in use and stops with its host A live Claude chat no longer stamps its effort onto the account catalog; the effort travels with the configured default, which a no-pick chat whose config resolves to no listed model now forgets. Prewarm lists agents with a saved catalog or a chat, and host teardown (quit included) starts no queued listing and stops running ones through an abort signal the listing runners honor. * test(native-chat): new catalog tests use typed hoisted state and type-only namespace imports * test(native-chat): a prewarm probe sets and clears the sign-in verdict like a read's, and re-checks an aged one beside a fresh catalog * fix(native-chat): an aged sign-in verdict re-probes only from probe paths, not a Codex chat's picker The main merge put the held-verdict check into store.shouldRefresh, which also gates the live Codex picker. A chat's own listing never clears the verdict, so a signed-out Codex whose chat works re-listed model/list on every picker read. shouldRefresh is back to main's rule; the catalog read's probe and prewarm ask probeDue instead. * fix(native-chat): Claude project settings with an effort cap, a main-thread agent or an Anthropic cloud provider count as an override maxEffortLevel clamps every effort including the model default, and agent applies that agent's model (both from the bundled SDK settings type). CLAUDE_CODE_USE_ANTHROPIC_AWS and CLAUDE_CODE_USE_ANTHROPIC_GOOGLE_CLOUD sit in the SDK's provider-switch list beside Bedrock and Vertex. A project that sets only these no longer shows, or saves as the account default, the account's model or effort. * refactor(native-chat): the catalog store's picker waiters live in their own module No behavior change: pendingListing and the waiter notifications move as they were, which keeps the store under its line limit for the fixes that follow. * fix(native-chat): changing an agent's command or environment re-lists its catalogs and re-checks its notice The settings change called prewarm, but prewarm skipped any catalog still fresh (the catalog key is agent + account, not the command), and a held "not installed" verdict kept its 30-second window. The change now marks that agent's catalogs due and ages its held verdicts, so the prewarm it triggers re-lists them and re-checks the notice. Only agents whose command or env actually changed are touched. * test(native-chat): the sign-in notice tests check the model picker is really usable They checked only that a model entry existed, which a disabled or empty pill also passes. They now check it is settable, carries no disabled reason, and offers the catalog's or the seed's model. * fix(native-chat): a probe the host stops keeps the held notice reason, and a stopped prewarm reads nothing A probe aborted because the host is going away says nothing about the account, so it no longer replaces a held sign-in or not-installed reason with a plain failure. A prewarm after stop returns before reading chat records. * test(codex): the catalog account-key test uses the runtime's real Codex launch-home resolvers The resolvers move to codex-launch-preparation as one export the runtime service spreads, so the test runs exactly what the runtime is handed instead of a copy. * feat(native-chat): keep each host's saved model lists in the renderer A chat picker could only learn the host's saved model list by reading it after it mounted, so its first frame was a guess. The renderer now keeps the host's answers per host and agent: this machine's lists are read once its runtime answers, for the agents its chats were used with; a paired host's again when this client regains contact; and an account or launch setting change drops this machine's lists. Every entry is a host answer. * fix(native-chat): render the model pills once, from the host's list A new chat's composer painted its first frame from the built-in model list: a saved pick that list lacks (Claude's `opus[1m]`) showed as a raw id with no effort pill, then jumped to "Opus (1M context) · High" when the host's list landed. Every new chat painted a guess and then corrected it. Now the first frame reads the host's saved list the renderer already holds, so it is the final frame. Without one, the quiet "Model" placeholder stands until the host answers; a host with nothing saved makes the built-in list usable, naming nothing, until its listing lands. A saved pick is named only from a host or session list that names it. The phone follows the same rule and keeps the host's list across a new fence. * feat(native-chat): answer a saved-only model catalog read The app could preload a host's saved model lists only for agents with a saved pick, because the catalog read starts a listing (spawns the agent's CLI) when nothing is saved. Grok, OpenCode and OMP chats with no saved pick were never preloaded, so their pickers showed "Model" before the host's list. `agentSession.modelCatalog` now takes `savedOnly`: the host answers from what it saved (the entry, configured default and held reason) and starts, joins or re-checks no listing. Its params are strict, so the host advertises `agent-session.model-catalog.saved-only.v1` and clients send it only to hosts that do. An answer also says when its named default holds in every workspace (Grok reads no project config for its model), so a new chat in any workspace can use it on its first frame. OMP stays without a session-free listing: `omp models --json` lists every model without the `enabledModels` filter its chats apply. * fix(native-chat): preload every registered agent's saved model list Grok, OpenCode and OMP chats painted the "Model" placeholder, then the host's pills, because the app preloaded only agents with a saved pick. Now the app reads every agent a host registered through the saved-only read: this machine once its runtime and agents are known, a paired host at connect and on each reconnect, and this machine again after an account or agent-setting change. A host that does not advertise the saved-only read keeps the earlier rule (saved picks only; a paired host's answered agents). An agent the host saved nothing for stays unknown. An answer sent before the host's lists were dropped is not recorded. * test(native-chat): drop a type assertion from the snapshot sync test * fix(native-chat): learn an ACP agent's default from a chat started with no pick Grok's session-free listing named the model `initialize` computed (grok-4.7), but chats ran another (grok-4.6), so a new chat painted 4.7 and then jumped. The listing no longer names a default; a new ACP session with no model pick reports what it runs, and the host saves that as the account's configured default behind the workspace check. A catalog read waits for a recording still in flight, so the chat's own follow-up read names it. * feat(native-chat): check OpenCode and OMP project config before naming their default OpenCode reads opencode.json(c) and .opencode/ in every ancestor of the workspace; OMP merges its .omp settings and other agents' project settings. A workspace whose config sets a model, effort or provider key (or can't be parsed) names no default; any other lets a no-pick chat's model become the account's default, so the next new chat names it on its first frame. * fix(native-chat): re-read the host's list once a no-pick chat reports its model The renderer kept the answer it read before the chat started, so the next new chat's first frame still named no model. A new chat that named no model reads the host's list again once it reports what it runs; a default no workspace can replace also serves chats in other worktrees. |
||
|
|
59b9bdeb90 |
Reduce repeated test work and fix Windows cleanup and IME confirmation (#26608)
* ci: share test transforms and remove six redundant checks * test: remove obsolete project copies and two build string checks * test: use the injected clock for exact launch readiness budgets * fix: retain IME confirmation and retire disconnected Windows PTYs * ci: include relay installer in existing Windows boundaries * perf: import the canonical SSH manager for filesystem operations * test: remove 42 source-spelling checks * test: advance deadlines after real entry and await actual PTY exit * Preserve native Windows exit acknowledgments and skip daemon-only smoke triggers |
||
|
|
b1e0092d5d |
feat(opencode): open OpenCode 2.x in the structured chat too (#26395)
* feat(opencode): run OpenCode 2.x in the structured chat too `opencode acp` on stable 2.x starts its own private `opencode serve --stdio` child with the chat's environment and ends it when stdin closes, so the chat's account pin reaches it just as it does on 1.x. Admit stable 2.x from 2.0.14 beside stable 1.x from 1.18.31; pre-releases, older releases, and other major lines keep the terminal chat. The `opencode2` agent is unchanged. Restart recovery already reads a 2.x session's own tables first; tests now cover a database an upgrade left with both generations, and one whose 2.x message table has an unknown shape (nothing found, no error). * fix(opencode): show OpenCode's own permission option names * test(opencode): run the 2.x recovery tests with the real-SQLite Node project |
||
|
|
a551aa5fe6 |
OpenCode structured chat over the Agent Client Protocol (#25845)
* refactor(native-chat): Grok's registration declares where it runs; ACP no longer borrows Codex's location rule
The rule a self-supervised agent child runs under (this machine, no WSL, Windows only with process
start-time proof) is its own module that Codex and the ACP adapter both use. Grok's registration
takes the full account-home resolver signature, and D3's tests build hosts with the agent registry.
* fix(native-chat): Grok follows the ACP runtime's request contract and the managed process's close
A request the agent or a Stop cancels is answered with the agent's own cancelled reply by the code that
owns it (the runtime no longer answers a silent handler), so a Stop needs no separate decline pass. A
permission answer still being saved when the agent stopped waiting is reported unconfirmed, since the
protocol already answered it cancelled. Cancelling the agent's own turn is the plain cancel. Request
rows are matched under their generation-scoped ids. A refusal's reason comes from the dialect's wording
path. The child drops its own stderr tail and close policy for the managed process's, and a close
whose process tree was not proven gone is reported as the adapter contract asks.
* fix(native-chat): a Grok chat Orca already holds resumes without writing what Grok replays
A chat with a saved Grok session reattaches with session/resume where the agent offers it, else
session/load. Either way the call runs inside the translator's load window, so what Grok sends while
it reattaches (its saved exchange, a task the dead process left running, ended by the restart) opens
no turn and writes no row; only context usage reads on. A reply an Orca or Grok crash cut short is no
longer completed from Grok's saved history: it reads like a Claude or Codex chat's, with the existing
notice. The attach window also closes after a failed attach, and a created session that session/resume
reports missing is replaced like one session/load reports missing.
The replay reconciliation is removed: the lane no longer reads the journal, and D3's replayed-input
grammar test and completed-turn check in the assembler go with it.
* refactor(native-chat): a failed Grok reattach needs no window close of its own; its lane is replaced
* test(native-chat): D3's merged tests use the shipped declarations and the launch options main requires
* fix(native-chat): typecheck fallout of the base merges; any agent's empty chat is reusable
Main's idle-empty-chat lookup and launch join now take any registered agent, as the rest of the
launch path does. The refusal check moved into the prompt turns and the prompt-block conversion beside
the turns that send it, keeping both files in their line limit.
* fix(native-chat): a Grok Stop ends the process once Grok settles its turn; the next send resumes
Grok's session/cancel ends only the running turn: work it already moved to the background keeps
running and can begin a turn of its own after the person pressed Stop. Stop is now a session
boundary, as it is for Claude: the cancel answers open requests and lets Grok end the turn, the host
waits a bounded grace for that, then ends the process; the next send relaunches and resumes.
The adapter's own bounded close of a turn Grok began is gone. Its named-turn check stays: the host
ends the session unless the provider declines a Stop naming a turn that has since ended.
* test(native-chat): a Grok Stop ends the process only after Grok answered the cancel
* fix(native-chat): Steer on a Grok card cancels the running prompt, then sends it
A send that reached Grok while a prompt ran was held in the adapter until that turn ended: Steer
on a queued card took the card out of the host's editable queue and meant 'send after this turn'.
It now cancels the running prompt (session/cancel; the session stays) and sends as the next prompt
once Grok answers the cancel, as the common pattern does; a steer behind another cancels it in
turn, so the last one runs. The adapter holds a send only while that cancel lands, so its general
held-send queue and its holdsDispatch report are gone (every send it holds has its turn open in
the journal). An older client's mid-turn send takes the same path. capabilities.steering is
unchanged and still unread.
* refactor(native-chat): a close or Stop cancels a start through the acquire's own abort signal
The host owns the acquire it runs, so it now owns its cancellation: each attach's acquire gets an
AbortSignal, aborted from outside the session's queue by a close and by a Stop admitted now (the
same admission rule as before). The optional abandonStart adapter hook, the router's fan-out to
every adapter and the ACP adapter's session-keyed start map are gone; the ACP adapter keeps an
unkeyed set of starts only so quit can prove their children gone, and keeps a failed start's
unproven child until its exit is proven.
The hook also let a later close ask that child again. The host now does that from state it holds:
a close of a chat with no live child whose record still names an owner process with no death
evidence asks the adapter to release it. The answer is not recorded as proof (the lease probe
does that), so an owner pid an earlier Orca left is never killed or marked gone. Claude and Codex
ignore the signal and hold no such child; their release is a no-op (tested).
* fix(native-chat): a Grok crash that closes stdout before its exit still ends with Grok's last words
On macOS and Linux the agent's stdout ends before its exit is observed, with or without the
supervisor's EOF forwarding, so the connection's loss closed the journal first and its error text
became the session's ended reason, dropping Grok's stderr. The reason is now read at the proven
exit: the agent's last words when it left any, else why the connection closed. The failure already
carried them. Comments that assumed the exit comes first, that early frames past the cap refuse the
start, and that dispatch re-checks image support are corrected.
* fix(native-chat): nothing Grok sends while a held chat reattaches is written, marked as replay or not
The reattach window relied on the dialect's replay verdict, and Grok's frames read as live unless
they carry isReplay, so an unmarked chat frame during session/resume opened a turn that never
ended. D3 now marks every frame inside the window as replay before the translator reads it, so the
translator keeps only context usage whatever the agent marked; options and commands are still
adopted. The translator's load semantics are unchanged.
* test(native-chat): a Stop after a resume finds no turn an unmarked old reply opened
* test(native-chat): a resumed Grok chat keeps its last context reading; the resume refreshes only the window
* test(native-chat): a Grok background task a Stop ended reads as stopped reporting
* refactor(native-chat): quit's stop of each start answers through one promise kind
* fix(native-chat): quit aborts every start the host has in flight before draining attaches
A Grok that never answered its handshake held quit until the start's own 60 s bound, past the
20 s quit deadline. The host's teardown now aborts each in-flight acquire (and any the drain
still begins), so the adapter's own quit controller and its map of starts are gone: a start
has one canceller, the host's signal.
* fix(native-chat): a Grok start's abort stops reaching its child once the start has returned
The listener stayed on the host's signal until the attach finished committing, so a Close in that
window killed the now-live child behind the host's back and it read as Grok crashing. The start
now detaches it when it ends; a later Close goes through the session's own stop.
* fix(native-chat): a close or Stop during any attach phase stops the start before it launches
The attach began its abort controller only after reconciling leases, resolving recovery and
probing the previous owner, so a close or admitted Stop in those phases reached nothing and Grok
launched anyway. The controller now begins first, and the acquisition checks it before asking the
adapter to start.
* test(native-chat): a close during the attach's owner probe asks no adapter to start
Also renames the close test after the hook it no longer exercises.
* test(native-chat): a close's re-ask closes a Claude or Codex child a failed cleanup left
The re-ask is not a no-op for them: when the adapter still holds the child its cleanup could not
prove gone, the close stops it again as a requested close, and Claude persists the handle of the
conversation it ran so the next send resumes it. Corrects the tests' and comment's wording; the
close awaits the re-ask, bounded by each adapter's kill ladder.
* fix(native-chat): Steer during a turn Grok began itself cancels it and sends once it ends
A send while Grok ran a turn of its own (a background task waking it) went straight to Grok, which
queued it behind that turn where Orca could no longer withdraw it, while Stop treated the same turn
as the running reply. The send now waits as a steer, the turn is cancelled once, and the message
goes when the turn ends; a Stop withdraws it and an exit rejects it as never sent.
* test(native-chat): a steer whose cancel Grok never answers ends Grok and is rejected as never sent
Pins the bounded steer cancel kept from the runtime: past the bound the connection closes, the
running reply reads unverifiable, Grok's end reads as its exit, and the waiting steer is rejected
as never sent.
* fix(native-chat): a Grok crash stays a crash when a stop lands before its exit is proven
After the connection broke and the close could not prove Grok's exit, any later stop Orca asked
for (the next start, a Stop, a Close) marked the child as closed by Orca, so the crash read as a
requested close and Grok's last words were dropped; a send meanwhile was recorded unconfirmed.
The connection loss now decides the cause, and a send on that session is rejected as never sent.
* test(native-chat): fixtures this PR's registered Grok and desktop capability made stale
CI's unit shards failed on tests outside the PR's own lists. Each encodes something this PR changes
on purpose: Grok is now a registered agent (the seam test's unregistered agent is now Cursor); the
desktop now advertises registered agents (the restart-offer tests' older client drops that
capability explicitly); the attach context carries the start's abort controllers (the forget-status
double gains them); and the ACP real-host test rig sends to the host directly (listed beside the
other real-host rig in the send ratchet).
* fix(native-chat): a start quit stops is not the queued message's start failure
With quit now aborting a start it would have waited for, the delivery step recorded the aborted
start as the message's failure ("couldn't restart"). After quit has stopped delivery, the step
leaves the message to quit, which settles it as a close does ("The chat closed before this message
was sent."). The test that pinned quit waiting for that start and stopping its child now pins that
nothing is launched behind quit.
* fix(native-chat): a message sent after a Stop or close aborted a start gets its own start
A start the host aborts (an admitted Stop, a close, or quit) returned its refusal to the delivery
loop, which then rejected whatever was queued at that moment with "couldn't restart", including a
message the user sent after the Stop. The attach now reports that the host aborted it, and the loop
re-derives from the journal instead: what the Stop or close withdrew is already settled, a message
accepted since gets a start of its own, and quit's next step stops the loop. This replaces the
quit-only carve-out with the same rule for every abort and every agent.
* test(native-chat): the message sent after an aborted start is answered, so no settlement outlives the test
* fix(native-chat): a Grok model pick Grok never answers no longer holds Stop or Close
The pick runs on the session's queue. It now registers in the host's out-of-queue
abort registry beside a start, so a close, an admitted Stop or quit abandons it, and
the ACP adapter bounds it at 30 s like Claude and Codex. A late answer is still adopted.
* fix(agent-launch): a phone's launch opens a terminal for an agent whose chat it cannot show
agent.launch now reads the caller's capabilities by the rule tabs and restart offers
use (clientRendersStructuredAgent). A phone without registered-agents.v1 gets Grok as
a terminal again, as on main; the host's own callers and desktop clients are unchanged.
* fix(acp): strip every agent hook variable from the ACP child, from the shared list
ACP_CHILD_ENV_TO_DELETE was a second copy of the hook runtime keys that missed
ORCA_AGENT_HOOK_TRANSPORT; it now spreads AGENT_HOOK_RUNTIME_ENV_KEYS beside the pane
identity keys.
* refactor(native-chat): the mutation context carries the provider-wait registry itself
Keeps the host file within its line limit; one field instead of two closures over it.
* fix(agent-launch): agent.launch.v2 still vouches for Claude and Codex chats
The caller rule from the previous commit also turned Claude and Codex into terminals
for a client advertising only agent.launch.v2, whose contract says it opens a chat
(mobile retry-authority tests). Only an agent beyond those two now needs the client to
read it (clientRendersStructuredAgent); the test fixtures go back to what they were.
* refactor(native-chat): drop saved-history adoption from the timeline assembler
The common pattern discards the history a provider replays while loading a
session, so the assembler has no use for an input.history event.
* refactor(acp): drop session/load history adoption from the translator
The common pattern discards the history an agent replays during session/load,
keeping only what it says about the context window. Remove the adoption path
(acp-history-adoption.ts, the adopt option, and the historical background-task
liveness rewrite it fed) so load replay is always dropped except usage.
* refactor(native-chat): a pending input is only Orca's send now
Review follow-up to the adoption removal: drop the comment naming the
provider's saved message, and make requestedAt required since every pending
input comes from input.accepted.
* test(acp): keep the task-result status table on live frames
Review follow-up to the adoption removal: the result-status mapping was only
tested through adopted history, so run the same table on live frames, and
cover an unmarked task notice during a load being dropped.
* test(acp): a frame helper for a shell command Grok is running
* fix(acp): a Grok crash settles through the host's provider-exit batch, scoped to the turn it ended
A Grok crash ended the journal unverifiable before the adapter reported the exit, so the host's
provider-exit settlement found no running turn and wrote nothing: the adapter's failure (with
Grok's last words) never reached the journal, and a later stale-session pass wrote a bare,
thread-scoped cut-short row, so the partial reply was not folded as Claude's and Codex's are.
At a proven exit the ACP lane now ends its running turn interrupted at the exit instant, as the
host's exit contract expects of a child's own translator (Codex's does the same). When Grok's
stdout closed first (every POSIX crash), the turn is unverifiable only until the exit is proven:
the host's provider-exit settlement now takes the exit as proof naming the child's fence and
revises what that child left unverifiable in the same batch, with the turn-scoped row and the
adapter's failure. Claude and Codex write no unverifiable turn of a live child except a command
whose hand-off is in doubt; that turn is now revised at the exit instead of at the next open.
* test(acp): a crash seen first leaves the host no Grok turn to revise
* refactor(native-chat): what a gone generation left unfinished gets its own module
The settlement file passed 300 lines with the exit-proof revision. The unfinished-work reads
(capture, interrupted-by-the-exit, in-progress) are their own concept and move out unchanged,
apart from the exit proof they now take.
* refactor(native-chat): a watched exit revises what its child left unverifiable without reading Stop marks
An exit's own instant is the turn's end, so the revision needs only each row's fence: the
settlement's journal type gains itemFence alone, and the host test fakes say so.
* test(native-chat): drop the duplicate itemFence on the fake that already had one
* test(claude, codex): an exit whose stdout ended first still reports as it always did
The provider supervisor now ends Orca's stdout when the agent's ends, so on every crash EOF
arrives before the exit is seen. Claude's and Codex's connections report nothing at EOF and
report the exit, with its usual reason, once it is seen.
* fix(acp): reopen a chat with session/load, as the common pattern does
An agent that offers both now reloads its session instead of resuming it; the
reattach window still discards what it replays except context usage.
* fix(acp): drop the 60 s handshake bound; an abort fails the start's waits at once
Neither common design bounds an ACP handshake: Close, Stop and quit end a start
that never answers. The abort now also closes the connection, as a kill there
does, so the start settles even before the child's exit is proven. The
host-stopped start refusal only this bound produced goes with it; the idle
sweep keeps its words.
* fix(acp): a Stop naming an ended turn follows Claude's rule
It still stops nothing while another turn is live, but in the gap before a
follow-up's turn opens, which no client can name, it now stops what is in
flight and the session ends, as a Claude Stop does.
* fix(native-chat): a close no longer re-asks a failed start's unproven child
Neither common design retries that stop at Close, and Orca's Claude contract
re-asks only at the next start and at quit. The ACP adapter keeps the child
until its exit is proven and asks it again there, as Claude does.
* fix(acp): a message sent during a turn the agent began itself goes at once
Both common designs send it straight to the agent with no cancel; only Orca's
own running prompt is steered (cancelled, then re-prompted).
* test(native-chat): dismiss-all through a remote client's audience keeps a newer Orca's offer
Uses an audience production sends (one that cannot show every agent), per review.
* fix(acp): launch Grok as `grok agent stdio`, without the update and leader flags
The common pattern passes neither --no-auto-update, --no-leader nor
GROK_DISABLE_AUTOUPDATER; full access still adds --always-approve.
* fix(acp): an agent that ends its stdout, or answers unreadably, is not a lost connection
As in the common pattern, only a broken stdin (or Orca's own close) ends the
agent; one that closed its output but can still be written to stays until a
Stop, a close or its exit. The provider supervisor goes back to its base
content, so Claude and Codex no longer get the forwarded stdout end either.
* fix(native-chat): a person's close joining a failed one still binds the turn its child end cuts
On main every close of the chat writes its own Stop and settle. Here a later close joins the
first and writes no row, and the first's settle closed when its kill failed, so a turn that opened
in between and was cut by the next close read as failed. A person's close joining a person's close
whose Stop opened a settle now reopens that settle until its attempt is done.
Tests: a close whose kill failed still closes its settle; a turn opened between a failed close and
the next reads as the person's cancellation (each fails without its half of the fix).
* test(native-chat): Grok opens as a chat only behind the structured-chat setting
agent.launch and orchestration worker-start read the same setting as the
renderer route; pin both states for Grok on each. The setting's description no
longer names only Codex and Claude, in every catalog.
* docs(acp): generic ACP comments say what holds for every agent, not Grok
Stop ends the session for every ACP agent, as in the common pattern; the
adoption hook comment goes (adoption is not planned); a failed start's child is
retried at the next start or quit.
* test(claude, codex): type the EOF-before-exit test's streams; the supervisor no longer forwards EOF
The Claude test wrote to the child's stdout and stderr through their Readable
type, which the node typecheck rejects; it now holds its own PassThrough
streams. The comments no longer credit the reverted supervisor change.
* feat(acp): a steer's cancel asks once and never ends the agent
The runtime had one cancel: send session/cancel, wait at most 10 s for Orca's prompt to settle,
then close the connection, which ends the agent. A steer used it too, so a slow agent lost its
process just because the person added a message. requestSteerCancel() now sends session/cancel
once per prompt, cancels the agent's open requests and answers later permissions cancelled, and
never bounds or closes: the prompt's own reply ends it and the steer's prompt follows. cancel()
stays the Stop: bounded, then close. A Stop after a steer still bounds and closes. Both cancel
paths move into acp-prompt-cancel.ts over one cancel channel.
* chore(native-chat): keep the record store and recovery capsule under max-lines after the main merge
* fix(acp): a repeated steer shares the cancel in flight; say what the caller owns
Per review: a second steer before the first write lands returns that write instead of resolving
early. The steer's JSDoc says the wait for the prompt's reply is unbounded and that a prompt that
fails instead must not take the steer until the caller rebuilds the session; the Stop's says a
prompt that settles in time leaves the agent for the Stop's owner to end. The steer test now gives
the runtime a handler that would allow: the open permission's signal aborts and the late one never
reaches it.
* fix(native-chat): drop the stopDelivery the A3 merge doubled
* fix(acp): a steer's cancel asks Grok once and never ends it
A steer now uses D1's notify-only cancel. Two messages sent during a reply Grok began itself
cut that reply, as the common pattern does, and then both run; before, the queued first
message could not answer the bounded cancel and Orca ended Grok although Grok answered.
A Stop keeps the bounded cancel and its 4 s grace.
* fix(acp): a permission Grok asks with no prompt of Orca's running is declined
During a turn Grok began itself nobody asked it to act, so the request is answered
cancelled at once instead of opening a card that waits, as the common pattern does.
* fix(acp): a Grok that dies while starting is reported with its own last words
A dying process's stdout ends before its exit is seen, so the start failed as a closed
connection and Grok's stderr was lost. A start whose connection closed now waits, bounded by
the Stop grace (or a Close/Stop), for the exit before it is told.
* test: a Stop after a steer sends its own cancel; drop the import the A3 merge doubled
* test(native-chat): main's Stop-note test builds its turn context with the agent registry
* test(claude): say why the close test's fake child cast is safe
* test(native-chat): build the Stop-opened-turn test's identity and turn context the current way
The test (#25056) landed before the opaque provider handle (#24991), so main still built
the old {kind, threadId} handle; the turn context also needs this branch's agent registry.
* test(native-chat): build the Stop-opened-turn test's identity with the opaque handle
The test (#25056) landed before the opaque provider handle (#24991), so main still built
the old {kind, threadId} handle.
* test(ratchet): require src/main/provider-process now that it has landed
* test(native-chat): keep main's opaque-handle import in the Stop-opened-turn test
Main's #25706 and this branch both added the import at different lines; the merge kept both.
* test(native-chat): keep main's opaque-handle import in the Stop-opened-turn test
Main's #25706 made the same fix as this branch at a different line; the merge kept both imports.
* feat(native-chat): record a fresh provider conversation that replaced one the agent could not restore
A chat whose saved conversation the agent cannot reopen can now continue in a
fresh one: the handle chain records the new conversation as a creation that
replaces the lost one (which, why, and when), keeping every earlier link.
Rows keep a shape older builds read: the stored chain starts at the latest
replacement and carries the earlier links inside it.
* test(native-chat): build this stack's journal identities with main's opaque provider handle
Main's #24991 replaced the {kind, ...} handle with {transport, agent, nativeId}; three test
files from this stack still wrote the old shape. Same lines the downstream ACP branch uses.
* docs(acp): every reattach drops the agent's replay, not only for a chat the journal holds
* refactor(native-chat): store a replaced conversation flat; refuse it where older builds read the row
Older builds only read Claude and Codex records, so the nested stored form
protected rows no replacement can reach while adding a cap mismatch after a
downgrade. Store the chain as held, refuse a replacement in a Claude or Codex
chain until one has a stored shape older builds read, and refuse a supersession
key on a replacement that names no creation in the chain.
* refactor(native-chat): read hosts' structured agents from the app-shell services
Main grew the startup hydration hook to its line limit; the host agents sync is an app-lifetime subscription like the structured session tabs sync beside it, so it moves there.
* Use current provider handles in transition tests
* Use current provider handles in timeline fixtures
* test(native-chat): prove replacement rows survive downgrade and re-upgrade
* Require the ACP directory in the runtime import check
* test(ratchet): require src/main/acp now that this PR lands it
* feat(acp): a saved session the agent cannot reopen continues in a new one, with one warning row
When session/load (or session/resume) of a saved ACP session fails, the chat starts a new session and records it as a creation that replaces the lost one (#25747's 'replaces' link), and writes one warning row that the agent no longer remembers the earlier messages. A created session the agent reports missing is still superseded silently; a signed-out agent or a start that is over (Close, Stop, a lost agent) still fails the start.
* chore(acp): rewrap the acquire header comment
* test(acp): a start closed while the agent reopens fails without opening or announcing a new session
* Let ACP connections own their supervised agent process
* Preserve ACP cleanup evidence and isolate exit observers
* Expose ACP cleanup observations and type the permission fixture
* refactor(native-chat): the registered-agents capability lives in its own module
Main's growth put protocol-version.ts one counted line over its 300-line limit once the capability
was added; like main's other per-feature capabilities, it now has its own module, and importers read
it from there.
* refactor(acp): one connection owns the Grok process and its protocol
D3 now opens each ACP agent through createAcpAgentConnection (ACP-ALIGN #25810): one object spawns the
process on the execution host, owns its stdio and protocol, and reports its proven exit. It is built and
tracked before the handshake, so a start's abort (Close, Stop, quit) still reaches it, and a failed start
keeps that same connection for the next close to retry rather than spawning another process.
Deleted: the spawnAcpStructuredChild wrapper and its test, the raw-stream runtime assembly, the caller's
exit -> runtime.close wiring, the stdout-EOF heuristic (the connection no longer treats stdout EOF as
exit), and the 10 s steer/Stop cancel bound with requestSteerCancel. Reader control maps to
pauseReading/resumeReading; a close is connection.close after the host's existing 4 s Stop grace.
The adapter owns what the protocol no longer does: one session/cancel per running prompt however many
steers arrive (cleared with that send's settlement, retried after a failed write), and a Stop or steer
answers every open agent request the person has not already answered with the agent's own cancelled
reply. An answer already being saved when the Stop lands is sent.
Tests: blocked cancel write never holds Stop's grace, two quick steers send one cancel, a failed cancel
write is retried, a real process exiting while a child holds its stdout ends the session, and the
existing start-abort, retention, crash, connection-loss and reload-failure suites on the new rig.
* Add chat-owned OpenCode server transport foundation
* Combine server fixture type imports for CI lint
* fix(acp): Grok signs in on its own machine with its API key or cached sign-in
When Grok reports that it needs authentication, Orca now names a sign-in method on the machine Grok runs
on, read from the same environment Grok was launched with: xai.api_key when XAI_API_KEY is set there and
Grok offers that method, else cached_token when Grok offers it, else none and the chat keeps the existing
not-signed-in refusal. The rule lives in Grok's launch spec; the adapter applies any agent's rule for new
and reopened sessions through the protocol client's caller-named method (authenticate, then retry once).
No new sign-in UI; interactive methods are never chosen.
* Bound request serialization and correct transport fixture types
* fix(acp): the adapter decides which of Grok's requests reach the person
The turn owner now admits every agent request, permission or question, from its own turn state: a
request reaches the person only while Orca's prompt runs and no steer or Stop is cutting it short (a
question may also come from a turn Grok began itself, until a Stop). Anything else gets the agent's
own cancelled reply and opens no card, so a question arriving after Stop or during a steer never
appears. A steer, like a Stop, withdraws the requests already open; an answer already being saved is
still sent. The protocol client's abort-on-cancel path is no longer used: after the connection
change its request signal aborts only when the connection closes.
* fix(acp): a plan Grok proposes shows as a plan, with no approval card
When Grok leaves plan mode it asks the client to approve its plan (x.ai/exit_plan_mode). Orca showed a
blocking 'Approve plan / Request changes' card for it; the common pattern has no such gate. Now the
plan goes into the chat's existing Plan row (the plan-document status row Codex and ACP plan updates
already use) and the request is answered at once with 'abandoned' plus feedback telling Grok to stop and
wait for the person's feedback or a request to implement it in a later turn, so nothing is approved on
the person's behalf. Dialects gain settleRequest for requests answered without asking anyone.
* fix(orchestration): worker-start opens a Grok worker in a terminal, as before
With the structured chat setting on, worker-start decided 'structured' for Grok and then the structured
worker factory (Claude and Codex only) refused it, so the start failed; main opened a terminal Grok
worker. Worker-start now decides with no registered agents beyond Claude and Codex, so Grok gets a
terminal worker as before. agent.launch and the app's own launches still open Grok as a structured
chat. Temporary until structured workers take registered agents.
* Bound OpenCode event consumers and isolate chat caller identity
* fix(acp): a prompt answer Orca can't read ends the turn instead of hanging it
A session/prompt rejection that was not the agent's own error answer (an answer that fails Orca's
schema, or one too large to read) left the turn running: the next message became a steer with nothing
to cancel and was never sent or settled, and Stop waited its full grace. As in the common pattern, any
prompt failure now ends the turn as failed (a failed-turn row without words, since none are the
agent's) and settles the send, so the next message goes. Only a closed connection keeps the send
running, for the connection-loss path to settle.
* fix(acp): send Grok's prompt-identity extension only to agents that echo it
session/prompt carried _meta {promptId, requestId} for every ACP agent, though only Grok's dialect
echoes it (injectedPromptIdentity). Now only an agent whose dialect declares it gets the extension;
other ACP agents get a plain prompt.
* refactor(native-chat): the registered-agents capability lives in protocol-version again, as on main
This reverts
|
||
|
|
61bcca9fee |
fix: stop Codex and OpenCode helper servers when Orca quits or crashes (STA-9254, 2 of 2) (#25753)
* fix(supervisor): add a one-shot lifetime that runs past stdin end, and relay a provider's last output A one-shot CLI (claude -p, codex exec -) reads its request until stdin ends, so the supervisor's session rule (stdin end means the owner is closing) would stop it about 1 s into its answer. The one-shot lifetime passes stdin end through and leaves only the owner-death watch and explicit signals to stop it. The supervisor also exited as soon as its provider did, dropping output still in the pipes when the owner reads slowly. It now waits, bounded, for the provider's output to be relayed before exiting. * fix(text-generation): run agent one-shots under the provider supervisor on POSIX The Claude model-list probe, model discovery for every agent, and commit message, pull request and branch name generation all spawned the agent CLI as a plain child of Orca. If Orca quit, crashed or was killed while one was running, nothing stopped it, and a CLI that hung kept running after Orca was gone. They now run under the provider supervisor that native chat already uses, in its one-shot lifetime, so Orca's exit stops the agent's whole process group however Orca exits. A timeout or cancel asks the supervisor to stop (SIGTERM) and only tears the tree down once it has had its full stop time; killing the supervisor first would orphan the agent's group. A missing binary now reaches Orca as the supervisor's exit 127, which is mapped back to the existing not-found message. Windows and WSL keep spawning the agent directly. * fix(supervisor): carry the provider argv on the supervisor's argv, map every spawn error back, and share one stop ladder - The supervisor read the provider's command and arguments from one base64 JSON env string. Linux caps a single env string at 128 KiB, so an argv prompt of about 90-120 KiB, which passes the 120 KiB per-argument guard, failed execve with E2BIG. The provider argv now follows the supervisor script's '--' as real arguments; the env keeps only small fields. - A supervisor that cannot start its provider reports Node's spawn error line and exits 127. That line now becomes the same error a direct spawn emits, so ENOENT still reads as 'not found on PATH' and EACCES or any other spawn error reads as 'failed to start'. - stopSupervisedProvider is the one ask, wait and force ladder: it gives a supervisor its full stop time before forcing. Agent one-shots, the Codex app-server close and the Claude child exit proof now share it, with the same requests, bounds and forced steps as before. * fix(supervisor): report every provider spawn failure on one marked stderr line Node throws most spawn failures (ENOEXEC, ENOTDIR, ELOOP, EPERM, ...) instead of emitting them, and the supervisor had no catch, so it died with exit 1 and a stack trace that the user saw as the agent's failure. A thrown or emitted spawn failure now exits 127 with one marked line carrying whether it was thrown, its code and its message. Orca reads only the last stderr line, so a runtime warning printed earlier cannot hide it, and maps it to the message a direct spawn gave: thrown is 'could not be started', ENOENT is 'not found on PATH', and any other emitted error is 'failed to start'. * fix(supervisor): keep the user's Node options away from the supervisor and hand them to the provider The supervisor runs Electron in Node mode, which honours NODE_OPTIONS and NODE_REPL_EXTERNAL_MODULE. A user value such as a --require of a missing file stopped the supervisor from starting, breaking even native CLIs like Codex that never load it. The launch now takes both out of the supervisor's environment, carries them in its spec, and restores them for the provider only, so a Node-based CLI still gets them. * fix(text-generation): file a forced agent one-shot teardown under its own breadcrumb site The forced tree teardown recorded every self-initiated kill as the Codex app-server's, so a forced commit-message or model-discovery stop read as a Codex teardown in crash breadcrumbs. The teardown now takes the caller's site; source-control stops pass the site their Windows tree kill already uses. * test(text-generation): cover the supervised stop under timeout and output limit; name the direct-child suites The commit-message suites that drive fake children spawn them directly, the unsupervised shape Windows and WSL use, so they now say so. The supervised POSIX stop gets its own compositions: a timed-out Codex generation settles at once but holds the Codex home until its supervisor has stopped (faithful fake, fake timers), and an agent that floods past the output limit is stopped through its real supervisor with no process left behind. * fix(codex): run short-lived app-server sessions under the provider supervisor on POSIX The Codex model-list probe, the hook trust grant and the session index heal each start a short-lived `codex app-server` as a plain child of Orca, and counted on it exiting when its input closes. A wedged Codex (a cold model/list waiting on the network, say) kept running after Orca quit, crashed or was killed. These sessions now run under the provider supervisor that native chat's Codex connection already uses, in its session lifetime, so Orca's exit stops the server's whole process group. The session's end and its deadline share the one stop ladder: end its input (and SIGTERM a session past its deadline), give the supervisor its full stop time, and only then tear the tree down. A missing binary reaches Orca as the supervisor's exit 127 and is mapped back to the spawn error, so the trust-grant telemetry still reads it as a missing binary. Windows and WSL keep spawning the server directly, with the same timings as before. The supervisor now takes only the command line it starts, so the CLI build, which also runs these sessions, no longer pulls in the native chat connection's types. * refactor(supervisor): share the stop of a supervised child process Agent one-shots stop their supervisor with SIGTERM through the shared stop ladder, watching the child's own exit. That adapter moves into one helper so the Codex backfill recovery can use it with its own stop request, instead of a copy. * fix(codex): supervise the app-server that keeps a Codex index backfill alive While Codex rebuilds its session index, Orca keeps a read-only `codex app-server` running for up to an hour so Codex can finish. It was a plain child of Orca with its input held open, so a quit, crash or kill left it running. On POSIX it now runs under the provider supervisor in its session lifetime, so Orca's exit stops its whole process group. Stopping it (done, aborted, or given up) ends its input, as a Codex connection close does; the supervisor then SIGTERMs the group and SIGKILLs it after the grace, and the tree is torn down only if the supervisor outlives its full stop time. Windows and WSL keep the direct spawn and the drain-first probe termination. The spawn and stop of that process move into their own module. * fix(opencode): stop the launch model preflight server with Orca on POSIX Before an OpenCode launch, Orca starts `opencode serve` to read the configured agent and models, then stops it. The server was detached into its own process group and never exits when its input ends, so if Orca quit, crashed or was killed during that preflight (up to 10 s), nothing ever stopped it. On POSIX the server now runs under the provider supervisor in its one-shot lifetime: the preflight's closed input does not stop it, and Orca's exit does. The preflight's teardown asks the supervisor to stop (SIGTERM) and forces the tree only after the supervisor's full stop time; signalling or SIGKILLing the supervisor's own group would orphan the server's. A descendant that ignores SIGTERM is now killed with the group rather than left running once the pipes close. Windows keeps the direct spawn and its existing teardown. * test(codex): pin when supervised session and backfill stops escalate A session past its deadline is SIGTERMed through its supervisor rather than waiting out the stdin-end grace, and its tree is torn down only after the supervisor's full stop time; Windows keeps its deadline kill and 1.5 s close wait. A supervised backfill app-server is stopped by ending its input, with the same full stop time before any teardown. * test(text-generation): run the direct-child suites on the Windows path and cover supervised discovery The commit-message suites that drive fake children mocked the supervisor away on POSIX, so they asserted a direct root SIGKILL that production no longer takes there. They now pin the platform to Windows (with an empty PATH, so host installs cannot answer a bare agent name) and assert the Windows kill, taskkill included. The three tests that check the host's own discovery spawn shape run on the host and read the agent argv past the supervisor's '--'. Model discovery gets its supervised composition: a timed-out Codex discovery settles at once but holds the Codex home until its supervisor has stopped. * fix(supervisor): show a supervised spawn failure in native chat as the spawn error it was Native chat's exit errors carry the provider's stderr tail into Details. Under the supervisor a missing CLI left the supervisor's internal spawn-failure report there instead of Node's own 'spawn <cmd> ENOENT'. The report, its parser and a display formatter now live in one module; the Codex app-server and Claude stream-json exit errors pass the tail through the formatter, which turns a report back into the spawn error and leaves any other stderr unchanged. * fix(supervisor): report a spawn that failed without a pid instead of crashing on its missing pipes When the provider spawn fails outright (EMFILE, ENFILE), Node emits 'error' later and leaves the child with no pid and no stdio. Piping stdin into the missing pipe threw first, so the supervisor died with exit 1 and a stack trace and never wrote its spawn-failure report. The pipes are now wired only for a provider that started. * refactor(supervisor): share the stop of a supervised child process Agent one-shots stop their supervisor with SIGTERM through the shared stop ladder, watching the child's own exit. That adapter moves into one helper beside the ladder, so other supervised children can use it with their own stop request instead of a copy. The caller's breadcrumb site still reaches the forced teardown. Same request, wait and force as before. * fix(codex,opencode): file forced backfill and preflight teardowns under their own breadcrumb sites A forced teardown of the Codex backfill app-server is filed under 'codex-state-db-backfill-recovery', and one of the OpenCode launch model preflight under 'opencode-launch-model-preflight', instead of the generic 'codex-app-server-teardown'. * test(codex): write the stand-in pid report atomically A loaded host let the test read the pid file between its creation and its write (Unexpected end of JSON input); the stand-in now renames it into place. * fix(supervisor): give a session provider its stdin end and grace when its owner dies The owner-death watch went straight to the group SIGTERM and cancelled any stdin-end grace, so when Orca quit or crashed a session provider such as the Codex app-server never saw the EOF that lets it finish writing its state (auth.json, the state database). A session whose owner is gone now closes as an owner's stdin end does: the provider's stdin is ended, it gets the stdin-end grace, and only then the SIGTERM and SIGKILL ladder. A one-shot already had its EOF at the end of its request, so its owner's death still stops it at once. * fix(opencode): stop the preflight server through the shared supervised stop, and trust only a proven stop The preflight's own stop wrapper waited on the supervisor's pipes and counted a forced teardown as proof, though the teardown reports success even when it found no descendants to check, and a supervisor that failed to reap its group exits 1 with its pipes closed. It now uses stopSupervisedChildProcess with its breadcrumb site, and counts the server stopped only when the supervisor ended on its stop signal or relayed the server's own exit. A forced stop, or an exit of 1, returns no context, as an unverified stop did before. * chore(codex): state the supervised stop time in the probe and trust grant deadline comments * test(codex): assert the signals a supervised stop sends itself, not a SIGKILL the mocked teardown never could * fix(supervisor): kill the rest of the provider group once the provider exits on a stop A requested stop waited out the whole SIGTERM grace for the provider's group even after the provider itself had exited, so a SIGTERM-ignoring helper it left behind held every stop for up to 3 s. Under a stop, the rest of the group is now SIGKILLed as soon as the provider has exited, the same rule its own exit already follows. * test(text-generation): cover a supervised Codex discovery past its output limit Model discovery's supervised stop was covered only under timeout. A Codex discovery that floods past the output limit now runs through a real supervisor: it settles with the too-much-data error, its agent is stopped through the supervisor, and the next discovery on the same Codex home starts only after that agent is gone. The direct-child suites' headers now list exactly the supervised cases that are covered. * fix(supervisor): close a provider whose owner is gone the way its owner closes it Owner death gave every session provider the stdin-end grace, so after an Orca crash a Claude session, whose close is a stdin end plus SIGTERM, could keep working on its turn for a second with nobody watching. The spawn spec now names the provider's close request: 'stdin-end' (the Codex app-server drains and exits on EOF, then gets its grace) or 'stdin-end-and-sigterm' (Claude; the default). A gone owner gets that same request. One constant per provider feeds both its spawn spec and its owner-side close, through one requestProviderClose, so the two cannot drift. One-shots still stop at once. * fix(codex): close short-lived sessions and the backfill app-server the way a Codex connection closes Codex finishes its writes and exits on its stdin end, so the Codex connection's supervisor closes it by ending stdin, and an owner that is gone now gets that same close. The short-lived sessions and the backfill app-server now name the same close request, through one shared constant, in their spawn spec and in their own close: Orca quitting or crashing gives them the stdin end and its grace before SIGTERM, instead of an immediate SIGTERM. A session past its deadline still adds a SIGTERM, since it is wedged. * test(codex,opencode): an owner's death drains Codex before SIGTERM, and the preflight stop no longer waits out the grace The stand-in now records when its stdin ended and when SIGTERM arrived. A SIGKILLed owner leaves a Codex session or backfill app-server its stdin-end grace before SIGTERM, and the OpenCode preflight ends within 1 s of its server's SIGTERM even with a descendant that ignores SIGTERM. * test(codex): give the trust-grant deadline tests room for a supervised start on a loaded host At 500 ms a loaded full run hit the deadline before the supervisor had started the stub, which never wrote the pid the test reads. * fix(supervisor): keep the SIGTERM grace for a session's group after its provider exits Killing the rest of the group the moment the provider exited under a stop also reached native chat's closes, so an MCP server, a tool's child or a dev server still in Claude's or Codex's group was SIGKILLed mid-cleanup instead of getting the rest of the SIGTERM grace. The early group kill now applies only to one-shots, where the saved wait was the point; a session's stop is back to waiting out the grace for its group. * test(claude): pin that Claude's spawn passes its close request explicitly The spawn-spec assertion matched the default close request, so dropping Claude's explicit request still passed. The test now checks that the spec is built with the exit-proof ladder's own constant. * refactor(codex): give the Codex app-server close request its own module Other Codex app-server spawns will name the same close request as the connection does. Holding it in its own small module lets them import it without the connection itself. * build(cli): list the Codex close request and the provider supervisor in the CLI project The command-line build runs the short-lived Codex app-server session, which will name the same close request as the Codex connection. Listing the close request, the provider supervisor it takes its type from, and the spawn-failure report the supervisor uses lets the CLI project typecheck that import without pulling the connection in. * test(codex): give a stand-in 20 s to report its pids on a loaded host At a load average near 50 both owner-death tests timed out waiting for the bundled owner's stand-in at 10 s; they pass alone. * test(codex): start the deadline tests' clocks past the stand-in's start, and cover a server that ignores SIGTERM The session and trust-grant deadline tests ran a 2-4 s deadline from spawn, so a loaded host could stop the stand-in before it wrote its pid. The session test's deadline now outlasts its pid-read budget, and the trust-grant deadlines are 8 s. The trust-grant comment said a wedged server may ignore everything but SIGKILL, but that stub dies on its stdin end; a new POSIX case pins that a server ignoring both its stdin end and SIGTERM is SIGKILLed after the SIGTERM grace. * test(text-generation): check the ENOEXEC start failure only where Node reports one On Linux, glibc's execvp hands an executable that is not a program to /bin/sh, so both a direct and a supervised spawn run it and it exits 127; only macOS throws ENOEXEC. The not-a-program case now runs on macOS only; the path-through-a-file case (ENOTDIR) still covers a thrown start failure everywhere. * test(wsl): follow the backfill's wsl.exe spawn into its new process module The WSL invocation boundary lists files that spawn wsl.exe directly. The backfill recovery's spawn moved into codex-state-db-backfill-recovery-process.ts, so the entry moves with it; the count is unchanged. * test(codex): keep factory child_process mocks loadable now that Codex stops reach the process-table reader The backfill recovery and Codex sessions now stop through the shared supervised teardown, whose process-table reader binds execFile when it loads. Five rate-limit fetcher tests mocked node:child_process with only spawn and failed at load: they now mock the backfill recovery, as their sibling fetcher tests already do. The account add-login tests' child_process mocks gain an execFile stub. * fix(opencode): count no forced preflight stop as proof the server is gone A forced teardown walks and group-kills the supervisor's tree, but the server leads its own detached group, so a teardown verdict of 'exited' does not cover members left in the server's group. Only a supervisor that reaped the group itself, by its own stop or relaying the server's exit, now counts as a proven stop. The Windows session-stop test now says why it sees no direct kill. |
||
|
|
761d63a4e5 |
feat(agent-launch): keep long prompts off the launch line and paste them after readiness (step 1 of 7) (#24257)
* feat(agent-launch): host-side prompt delivery for agent.launch The host's agent.launch typed any launch prompt into the shell as part of the launch command. A long or multi-line prompt then ran line by line in the shell, and an agent that never showed readiness or crashed at startup had nothing guarding where its text went. agent.launch now carries a prompt on the typed line only when the line stays one line, control-free and at most 512 bytes; otherwise the agent starts clean and the host pastes the prompt once the agent's own ready signal fires (bracketed paste plus its composer marker or a quiet render, read only after the shell's last hand-off, never while the pane's own shell is proven in front), with main's draft-paste bytes and an Enter 50 ms later. Orchestration worker starts wait on tui-idle as before. A replay-safe launch admits and claims its ledger row in one write, Qwen Code gets a second Enter, the desktop and phone share one launch-refusal classifier, and hosts advertise agent.launch.prompt-carry.v1. Split out of #23748, which moves the desktop source-control buttons onto this path. * fix(agent-launch): keep a short-lined multi-line prompt on a local zsh launch line, as main did #24257 moved every multi-line or over-512-byte prompt off the typed launch line and pasted it after readiness. The phone's AI buttons and review notes, whose multi-line prompts main typed whole into zsh, then reached Claude 0.5-3 s later and their RPC reply waited for the paste. The host now names the shell a local macOS or Linux line is typed into, the way the spawn picks it, and a multi-line prompt rides a zsh line when every line is at most 512 bytes and the whole line at most 8 KB. A real-zsh test types such a line through Orca's own ready barrier and startup write, including when a slow user config makes the write land early. Elsewhere the measured unsafe cases keep the paste: bash 3.2 runs multi-line lines piecemeal, fish drops an early multi-line write, and any shell loses a line over 1 KB written early. * test(agent-launch): keep the real-zsh launch-line test out of the Windows lane's gate scan The Windows lane registration check read `const ZSH_PATH = process.platform === 'win32'` (the head of a multi-line ternary) as a Windows-true flag, so `describe.skipIf(!ZSH_PATH)` looked like a Windows-only suite. The file is POSIX-only; the zsh lookup is now a function. * refactor(protocol): move the agent.launch capabilities into their own module Main's protocol-version.ts sits at the 300-line cap, so the prompt-carry capability pushed it over. The four agent.launch capabilities and their doc move to agent-launch-runtime-capability.ts, re-exported by name and spread into RUNTIME_CAPABILITIES at the same position; the advertised lists and every export are unchanged. * refactor(protocol): import the agent.launch capabilities from their own module `export *` from protocol-version left the four names undefined under the mobile recording loader, which resolves a relative import through a Proxy with no own keys, so 37 phone recordings lost agent.launchReplay. Importers now name agent-launch-runtime-capability directly; protocol-version only spreads its list. * refactor(agent-launch): drop the unshipped viewMode field and trusted local caller id Both were inert in step 1 and existed only for step 2. agent.launch will become a public plugin API, so every wire field is permanent once shipped; a top-level viewMode reads as "choose terminal vs chat", which the host decides. Step 2 introduces placement and view intent under a placement object instead. * fix(agent-launch): read Codex's provisional startup from the rule files' hold anchor Main (#24375) moved Codex's provisional-header check into codex.json's provisional_startup hold anchor and deleted codex-terminal-readiness.ts, so the launch readiness hold now asks showsHoldAnchor, as main's own settled check does. * fix(agent-launch): hold rule-file name titles to quiet for a launch, and census the zsh fixture Main (#24375) answers a name-only title from each agent's rule file ahead of the sustained-title lane, so gemini.json's name_title settled a launch readiness wait on the shell's auto-title while Gemini was still booting. A launch now asks quiet of every weak idle verdict, as that lane did. Main's readiness census requires a recorder for every runtime fixture; the zsh prompt recording is a non-agent control. Gemini's synthetic baseline is regenerated for this PR's stated change: a bare gemini title is no longer its rest mark, so name-only rows settle weak, and a fresh working or blocked status is no longer overridden. * fix(agent-launch): paste a launch prompt only when the launched agent is proven in front A launch pasted its prompt unless a shell was proven in the terminal's foreground, so any read that could not prove one let the prompt through. After an agent exited at startup, its shell turned bracketed paste on at the next prompt, readiness fired on it, and the prompt was typed into the shell: - macOS: a pane runs its shell under login, so the process-group fence's root was never the shell's group and never proved it; the cached foreground name could also still name the exited process. - Windows Git Bash and WSL: the shell-alone-in-its-job check never answers. Now one fresh read of the terminal's foreground decides: agent, shell or unknown. Only 'agent' lets a write through (paste, Enter, second Enter, reused panes too); 'shell' still drops a ready signal. A Windows host never proves the agent, so there the launch line carries the prompt at any size, as on main. * test(agent-launch): cover the Windows QA stub, a grok override that exits at once * fix(agent-launch): keep the local socket alive while a prompted launch waits for its agent A launch with a prompt now waits up to 60 s for the terminal agent to be ready before it writes the prompt, and reports not-delivered when the agent never is. The local runtime socket closes a connection idle for 30 s unless the request is a long poll, so a launch whose agent exited at startup lost its reply and the caller saw 'runtime closed the connection' instead of not-delivered. Classify a prompted agent.launch and agent.launchReplay as a long poll, as orchestration.workerStart already is for the same wait. * refactor(agent-launch): narrow the launch params by 'in' instead of a cast * fix(agent-launch): find a launched agent behind a wrapper that leads its process group A tcsh or nu launch line runs the agent from /bin/sh '<script>', and a wrapper script that does not exec its agent does the same: the wrapper leads the terminal's foreground process group and the agent is a member of it. The fresh foreground read names the group's leader, sh, so a prompted launch was refused or pasted late (M4Air tcsh: 2 of 4 not delivered, 2 pasted ~9 s late). Before that read, take the host's process-group observation as positive proof when it names the launched agent among the foreground group's members and is younger than a ready signal's quiet window. It never proves a shell. * fix(agent-launch): judge the foreground-group proof by when its capture began, not how long ps took The age the host stamps on a process-group observation runs from the start of its whole-machine ps, so on a loaded Mac a capture begun after the read was asked for still read as older than 1 s and the proof was dropped. Count an observation whose capture began after the read was asked for, less the window a shared capture is reused across. * test(agent-launch): keep the crash-guard live test out of the Windows lane's gate scan The Windows-lane registration scan read the const assigned from a platform check as a Windows-only gate, though the suite runs everywhere but Windows; find zsh in a function instead, as the real-zsh typed-line test does. Under load the fresh foreground scan can fail to answer, which lets the shell's prompt settle readiness (2 of 4 paired runs). The guard still refuses that write, so assert the refused write, the property that must always hold. * perf(agent-launch): read a local pane's foreground from its own terminal, not the whole process table The foreground read that gates every launch paste ran the daemon's inspectProcess capture and then a fresh scan, each a whole-machine ps; the fresh one also waits for any capture already running before it starts its own. Measured here at load 5: 1.2 s a read (M4Air QA: 3.4-5.0 s, and worker starts 17.6-32 s against main's 9-12 s at load 25-84). On a local macOS or Linux host, take the pane's root pid from the provider's session inventory and run one ps limited to that pane's terminal. Its foreground process group decides: the launched agent or any non-shell member is the agent (a wrapper that did not exec its agent leads the group), a group of shells alone is the shell. Same pane, same verdict: 2.7 ms a read. SSH hosts keep the relay's observation and name. * test(mobile): re-measure the web app's script sweep after agent.launch's capabilities moved out of protocol-version The mobile web bundle check failed at 124 assets against a ceiling of 123. Main already sat exactly on that ceiling: its sweep table read 69 scripts at 16 routes while the tree builds 73, the whole margin of 4. This branch imports the agent.launch capabilities from their own module, so protocol-version is no longer pulled into the root layout and four other routes. That moves which routes share which modules, and the Qoder capability module, imported by protocol-version and the AI-vault resume path, no longer shares an importer set with anything, so it gets a chunk of its own: 74 scripts. The fence says to re-derive the bound rather than raise it, so the sweep is re-measured on this head (every prefix of the sorted route list). The worst route now adds 10 scripts (session), not 9, which moves the pinned shell crossing from 32 to 30 routes; main re-measured on its own lands on the same crossing. * fix(agent-launch): a worker's brief needs its agent found in front, and Grok's start answers on its composer A paired-server worker start whose agent exited at startup typed its brief into the server's shell, which ran it: the idle wait can settle on a shell back at its prompt, and the brief was written with no foreground read. Both worker-start paths now check before each brief write, as a launch prompt is checked: on a host that can find the agent in front it must be there; on one that cannot (Windows) a shell proven in front still refuses, and anything else writes as before. A Grok worker start waited ~10 s more than main: its only rest signal is its bare name, which a launch holds to quiet output, and Grok animates its logo for ten seconds after its composer glyph. A worker start for an agent whose rest signal is its bare name and whose composer draws a marker (Grok, DSH, mimo-code) now also answers on that marker, whichever comes first. |
||
|
|
8b5de39217 |
Submit initial OpenCode 2.0.12 prompts through the native startup plugin (#25428)
* fix(opencode): submit initial prompts for reviewed 2.0.12 Route the reported release through the existing native startup intent. Retain conservative behavior for other unreviewed versions. * Wait for OpenCode startup fixture file reads in the hydration test --------- Co-authored-by: nwparker <nwparker@users.noreply.github.com> |
||
|
+3 |
46c3c3b44d |
Keep OpenCode worker model selection separate from the default (#24768)
* feat(orchestration): support OpenCode worker model selection Allow supervised OpenCode workers to use per-launch model overrides through the existing launch-preference and receipt path. Preserve existing OpenCode agent arguments while replacing only model flags, and reject unsupported effort values explicitly. Keep Native Chat option exposure unchanged and update the worker CLI and orchestration guidance. * fix: wait for OpenCode worker composer before first dispatch Reuse captured composer readiness on local and paired execution hosts and revoke launching-shell paste anchors. Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com> * feat(opencode): probe execution-host CLI capabilities * fix(opencode): select plugin default for execution host loader * fix(opencode): limit prompt prefill capability to verified release * feat(orchestration): gate OpenCode worker model preferences by host capability Co-authored-by: user141514 <user141514@users.noreply.github.com> * feat(opencode): probe launch capabilities on the execution host * feat(opencode): probe execution-host CLI capabilities * feat(opencode): probe launch capabilities on the execution host * feat(orchestration): resolve explicitly configured command aliases * fix(opencode): select plugin loader for the launched host binary * fix(opencode): match WSL probe cwd and declared guest environment * fix(orchestration): verify available OpenCode model on execution host * Add host-owned OpenCode and Devin account profiles * fix(orchestration): inspect OpenCode models with selected account environment * fix(orchestration): bind model validation to direct existing-workspace launch * fix(opencode): preserve launch environment deletion boundaries * fix(orchestration): limit effective model contract to verified OpenCode release * wip(opencode): authorize native startup prompt intent at execution owner * fix(opencode): atomically replace status plugin entrypoints * fix(opencode): retain plugin permissions across restrictive umasks * test(opencode): resolve permission fixture from primary cwd * feat(opencode): install startup prompt plugin independently of status hooks * Restore inherited account environment and preserve cleanup retries * fix(opencode): select plugin loader for the launched host binary * fix(opencode): match WSL probe cwd and declared guest environment * fix(opencode): preserve launch environment deletion boundaries * wip(opencode): authorize native startup prompt intent at execution owner * fix(opencode): atomically replace status plugin entrypoints * fix(opencode): retain plugin permissions across restrictive umasks * test(opencode): resolve permission fixture from primary cwd * feat(opencode): install startup prompt plugin independently of status hooks * Check relay environment values before merging * fix(opencode): wait for admitted startup intent and preserve failed-launch briefs * fix(opencode): confine overlay manifest cleanup to owned directories Co-authored-by: Adnan Khan <adnank11427@gmail.com> * fix: wait for OpenCode worker composer before first dispatch Reuse captured composer readiness on local and paired execution hosts and revoke launching-shell paste anchors. Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com> * fix(orchestration): validate configured aliases with target shell grammar * fix(orchestration): use actual shell and refuse assignment-only aliases * fix(opencode): wait for admitted startup intent and preserve failed-launch briefs * Support verified OpenCode v2 model selection in fresh native workers * fix(opencode): refuse unsupported startup preferences before model probing * feat(opencode): probe execution-host CLI capabilities * fix(opencode): select plugin default for execution host loader * fix(opencode): limit prompt prefill capability to verified release * feat(opencode): probe launch capabilities on the execution host * fix(opencode): select plugin loader for the launched host binary * fix(opencode): match WSL probe cwd and declared guest environment * fix(opencode): preserve launch environment deletion boundaries * wip(opencode): authorize native startup prompt intent at execution owner * fix(opencode): atomically replace status plugin entrypoints * fix(opencode): retain plugin permissions across restrictive umasks * test(opencode): resolve permission fixture from primary cwd * feat(opencode): install startup prompt plugin independently of status hooks * fix(opencode): wait for admitted startup intent and preserve failed-launch briefs * fix(opencode): unsubscribe hook settings during async host shutdown * fix(opencode): confine overlay manifest cleanup to owned directories Co-authored-by: Adnan Khan <adnank11427@gmail.com> * test: preserve typed calls in configured worker target checks Replace Reflect.apply with the existing typed prototype call pattern so the unchanged regression cases pass the anti-slop lint gate. * test: check typed model host calls and terminal delay state Replace Reflect dispatch and property reads with checked access and typed calls. Complete the existing resume request fixture with its required identity fields without changing the rejection boundary or assertions. * test(readiness): census recorded OpenCode composer boots * fix(opencode): reject redirected overlay parents before cleanup * fix(orcad): retain runtime cleanup when subscribing to hook settings * refactor(launch): extract OpenCode config and attachment authority * fix(opencode): retain host version selection across relay restarts * fix(opencode): pass run prompts as positional messages Preserve run flags and use the existing shell quoting and run-command detector to append the initial message after --, reusing an existing separator. TUI launches retain their version-selected prompt transport and draft behavior. Original run-order work: @coelho-doti (#13065, tracked in #17551). * fix(opencode): keep wrapped run tasks positional Recognize supported environment prefixes and PowerShell call operators without mistaking prompt arguments for executables. Keep environment and run separators separate, preserve the task text and exclude run commands from native submission. Source-parent: |
||
|
+2 |
8e5080c132 |
Validate OpenCode worker model preferences on the execution host (#24624)
* feat(orchestration): support OpenCode worker model selection Allow supervised OpenCode workers to use per-launch model overrides through the existing launch-preference and receipt path. Preserve existing OpenCode agent arguments while replacing only model flags, and reject unsupported effort values explicitly. Keep Native Chat option exposure unchanged and update the worker CLI and orchestration guidance. * fix: wait for OpenCode worker composer before first dispatch Reuse captured composer readiness on local and paired execution hosts and revoke launching-shell paste anchors. Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com> * feat(opencode): probe execution-host CLI capabilities * fix(opencode): select plugin default for execution host loader * fix(opencode): limit prompt prefill capability to verified release * feat(orchestration): gate OpenCode worker model preferences by host capability Co-authored-by: user141514 <user141514@users.noreply.github.com> * feat(opencode): probe launch capabilities on the execution host * feat(opencode): probe execution-host CLI capabilities * feat(opencode): probe launch capabilities on the execution host * feat(orchestration): resolve explicitly configured command aliases * fix(opencode): select plugin loader for the launched host binary * fix(opencode): match WSL probe cwd and declared guest environment * fix(orchestration): verify available OpenCode model on execution host * fix(opencode): preserve launch environment deletion boundaries * wip(opencode): authorize native startup prompt intent at execution owner * fix(opencode): atomically replace status plugin entrypoints * fix(opencode): retain plugin permissions across restrictive umasks * test(opencode): resolve permission fixture from primary cwd * feat(opencode): install startup prompt plugin independently of status hooks * fix(opencode): wait for admitted startup intent and preserve failed-launch briefs * fix(opencode): confine overlay manifest cleanup to owned directories Co-authored-by: Adnan Khan <adnank11427@gmail.com> * fix: wait for OpenCode worker composer before first dispatch Reuse captured composer readiness on local and paired execution hosts and revoke launching-shell paste anchors. Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com> * feat(opencode): probe execution-host CLI capabilities * fix(opencode): select plugin default for execution host loader * fix(opencode): limit prompt prefill capability to verified release * feat(opencode): probe launch capabilities on the execution host * fix(opencode): select plugin loader for the launched host binary * fix(opencode): match WSL probe cwd and declared guest environment * fix(opencode): preserve launch environment deletion boundaries * wip(opencode): authorize native startup prompt intent at execution owner * fix(opencode): atomically replace status plugin entrypoints * fix(opencode): retain plugin permissions across restrictive umasks * test(opencode): resolve permission fixture from primary cwd * feat(opencode): install startup prompt plugin independently of status hooks * fix(opencode): wait for admitted startup intent and preserve failed-launch briefs * fix(opencode): unsubscribe hook settings during async host shutdown * fix(opencode): confine overlay manifest cleanup to owned directories Co-authored-by: Adnan Khan <adnank11427@gmail.com> * test(readiness): census recorded OpenCode composer boots * fix(opencode): reject redirected overlay parents before cleanup * fix(orcad): retain runtime cleanup when subscribing to hook settings * refactor(launch): extract OpenCode config and attachment authority * fix(opencode): retain host version selection across relay restarts * fix(opencode): pass run prompts as positional messages Preserve run flags and use the existing shell quoting and run-command detector to append the initial message after --, reusing an existing separator. TUI launches retain their version-selected prompt transport and draft behavior. Original run-order work: @coelho-doti (#13065, tracked in #17551). * fix(opencode): keep wrapped run tasks positional Recognize supported environment prefixes and PowerShell call operators without mistaking prompt arguments for executables. Keep environment and run separators separate, preserve the task text and exclude run commands from native submission. Source-parent: |
||
|
|
8ff6ec9fb1 |
Install OpenCode hooks in the terminal's config directory (#25296)
* test: reproduce OpenCode plugin installation in the wrong config root * fix: install OpenCode hooks in the execution config directory * test: isolate config installation from CLI version probing * style: format consumer config installation controls * fix: use checked startup environment and supported relay shell context * fix: install OpenCode hooks using the selected execution shell * test(opencode): assert consumer config root in PTY fixtures --------- Co-authored-by: Codex <codex@openai.com> Co-authored-by: Orca <orca@stably.ai> |
||
|
|
70cf91299b |
Clean up retired OpenCode configuration copies safely (#25222)
* fix: wait for OpenCode worker composer before first dispatch Reuse captured composer readiness on local and paired execution hosts and revoke launching-shell paste anchors. Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com> * feat(opencode): probe execution-host CLI capabilities * fix(opencode): select plugin default for execution host loader * fix(opencode): limit prompt prefill capability to verified release * feat(opencode): probe launch capabilities on the execution host * fix(opencode): select plugin loader for the launched host binary * fix(opencode): match WSL probe cwd and declared guest environment * fix(opencode): preserve launch environment deletion boundaries * wip(opencode): authorize native startup prompt intent at execution owner * fix(opencode): atomically replace status plugin entrypoints * fix(opencode): retain plugin permissions across restrictive umasks * test(opencode): resolve permission fixture from primary cwd * feat(opencode): install startup prompt plugin independently of status hooks * fix(opencode): wait for admitted startup intent and preserve failed-launch briefs * fix(opencode): confine overlay manifest cleanup to owned directories Co-authored-by: Adnan Khan <adnank11427@gmail.com> * fix: wait for OpenCode worker composer before first dispatch Reuse captured composer readiness on local and paired execution hosts and revoke launching-shell paste anchors. Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com> * feat(opencode): probe execution-host CLI capabilities * fix(opencode): select plugin default for execution host loader * fix(opencode): limit prompt prefill capability to verified release * feat(opencode): probe launch capabilities on the execution host * fix(opencode): select plugin loader for the launched host binary * fix(opencode): match WSL probe cwd and declared guest environment * fix(opencode): preserve launch environment deletion boundaries * wip(opencode): authorize native startup prompt intent at execution owner * fix(opencode): atomically replace status plugin entrypoints * fix(opencode): retain plugin permissions across restrictive umasks * test(opencode): resolve permission fixture from primary cwd * feat(opencode): install startup prompt plugin independently of status hooks * fix(opencode): wait for admitted startup intent and preserve failed-launch briefs * fix(opencode): unsubscribe hook settings during async host shutdown * STRICT launch CI contract correction * CAPS launch CI contract correction * INTENT launch CI contract correction * test: initialize Claude prompt state in output retention fixture * Wait for OpenCode location hydration in intent startup * Bind OpenCode startup readiness to the current location in intent startup * Collect retired source-scoped OpenCode configuration overlays conservatively Credit brennanb2025 for the original bounded, delayed overlay garbage-collection contribution in PR #7627. Preserve ambiguous legacy and shared-service state. * Correct inaccessible-source fixture without spying on native ESM exports * Keep delayed OpenCode cleanup within existing file limits * Reuse the overlay manifest module for existing owned-entry operations * Use the existing filesystem import in the ownership mock * test(opencode): keep overlay GC link tests portable --------- Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com> Co-authored-by: Ahmed Nagy <ahmednagy25t@gmail.com> Co-authored-by: Adnan Khan <adnank11427@gmail.com> Co-authored-by: Orca startup hydration review <agents@stably.ai> Co-authored-by: OpenCode Campaign <opencode-campaign@users.noreply.github.com> |
||
|
|
87bc51d371 | Reduce test deadline waits and exact byte comparison costs (#25187) | ||
|
|
8c617301f7 |
fix(opencode): keep overlay manifest cleanup inside owned directories (#24763)
* fix: wait for OpenCode worker composer before first dispatch Reuse captured composer readiness on local and paired execution hosts and revoke launching-shell paste anchors. Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com> * feat(opencode): probe execution-host CLI capabilities * fix(opencode): select plugin default for execution host loader * fix(opencode): limit prompt prefill capability to verified release * feat(opencode): probe launch capabilities on the execution host * fix(opencode): select plugin loader for the launched host binary * fix(opencode): match WSL probe cwd and declared guest environment * fix(opencode): preserve launch environment deletion boundaries * wip(opencode): authorize native startup prompt intent at execution owner * fix(opencode): atomically replace status plugin entrypoints * fix(opencode): retain plugin permissions across restrictive umasks * test(opencode): resolve permission fixture from primary cwd * feat(opencode): install startup prompt plugin independently of status hooks * fix(opencode): wait for admitted startup intent and preserve failed-launch briefs * fix(opencode): confine overlay manifest cleanup to owned directories Co-authored-by: Adnan Khan <adnank11427@gmail.com> * fix: wait for OpenCode worker composer before first dispatch Reuse captured composer readiness on local and paired execution hosts and revoke launching-shell paste anchors. Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com> * feat(opencode): probe execution-host CLI capabilities * fix(opencode): select plugin default for execution host loader * fix(opencode): limit prompt prefill capability to verified release * feat(opencode): probe launch capabilities on the execution host * fix(opencode): select plugin loader for the launched host binary * fix(opencode): match WSL probe cwd and declared guest environment * fix(opencode): preserve launch environment deletion boundaries * wip(opencode): authorize native startup prompt intent at execution owner * fix(opencode): atomically replace status plugin entrypoints * fix(opencode): retain plugin permissions across restrictive umasks * test(opencode): resolve permission fixture from primary cwd * feat(opencode): install startup prompt plugin independently of status hooks * fix(opencode): wait for admitted startup intent and preserve failed-launch briefs * fix(opencode): unsubscribe hook settings during async host shutdown * STRICT launch CI contract correction * CAPS launch CI contract correction * INTENT launch CI contract correction * test: initialize Claude prompt state in output retention fixture * Wait for OpenCode location hydration in intent startup * Bind OpenCode startup readiness to the current location in intent startup --------- Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com> Co-authored-by: Ahmed Nagy <ahmednagy25t@gmail.com> Co-authored-by: Adnan Khan <adnank11427@gmail.com> Co-authored-by: Orca startup hydration review <agents@stably.ai> |
||
|
|
e8310d5a4f |
fix(opencode): submit admitted native startup briefs without overwriting input (#24762)
* fix: wait for OpenCode worker composer before first dispatch Reuse captured composer readiness on local and paired execution hosts and revoke launching-shell paste anchors. Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com> * feat(opencode): probe execution-host CLI capabilities * fix(opencode): select plugin default for execution host loader * fix(opencode): limit prompt prefill capability to verified release * feat(opencode): probe launch capabilities on the execution host * fix(opencode): select plugin loader for the launched host binary * fix(opencode): match WSL probe cwd and declared guest environment * fix(opencode): preserve launch environment deletion boundaries * wip(opencode): authorize native startup prompt intent at execution owner * fix(opencode): atomically replace status plugin entrypoints * fix(opencode): retain plugin permissions across restrictive umasks * test(opencode): resolve permission fixture from primary cwd * feat(opencode): install startup prompt plugin independently of status hooks * fix(opencode): wait for admitted startup intent and preserve failed-launch briefs * fix(opencode): unsubscribe hook settings during async host shutdown * STRICT launch CI contract correction * CAPS launch CI contract correction * INTENT launch CI contract correction * test: initialize Claude prompt state in output retention fixture * Wait for OpenCode location hydration in intent startup * Bind OpenCode startup readiness to the current location in intent startup * Retry interrupted OpenCode startup prompt claims --------- Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com> Co-authored-by: Ahmed Nagy <ahmednagy25t@gmail.com> Co-authored-by: Orca startup hydration review <agents@stably.ai> Co-authored-by: Orca <dev@stably.ai> |
||
|
|
5a2aa87f1c |
Select OpenCode plugin exports from the execution host version (#24662)
* fix: wait for OpenCode worker composer before first dispatch Reuse captured composer readiness on local and paired execution hosts and revoke launching-shell paste anchors. Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com> * feat(opencode): probe execution-host CLI capabilities * fix(opencode): select plugin default for execution host loader * fix(opencode): limit prompt prefill capability to verified release * feat(opencode): probe launch capabilities on the execution host * fix(opencode): select plugin loader for the launched host binary * fix(opencode): match WSL probe cwd and declared guest environment * fix(opencode): preserve launch environment deletion boundaries * STRICT launch CI contract correction * CAPS launch CI contract correction * test: initialize Claude prompt state in output retention fixture --------- Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com> |
||
|
+48 |
4ea021f919 |
fix(opencode): finish status installation with invalid TUI settings (#25031)
* fix(opencode): keep server plugin installation independent of invalid TUI config * test(opencode): format invalid TUI installation control * Add host-owned OpenCode and Devin managed account profiles (#24636) * Add host-owned OpenCode and Devin account profiles * Manage OpenCode and Devin profiles in account Settings * Expose registered account roots to host transcript readers * Clarify managed profile provider flags * Retain isolated Electron home in browser sidecars * Restore inherited account environment and preserve cleanup retries * Check relay environment values before merging * Consolidate managed account type imports * fix(accounts): use existing localized provider names Align the new Japanese account copy with the existing catalog repair policy. * Keep managed account baselines private to the execution host * Align account enrollment help with accepted providers and flags * Show the active System account in managed profile lists * Document the validated Linux managed account scope * Fix managed account removal, credential audits, and runtime bundling * Quarantine removed accounts and audit captured credential snapshots * Continue Antigravity IDE and 2.0 history in new CLI conversations (#24692) * feat(antigravity): bridge IDE history into new CLI conversations * fix(antigravity): preserve fresh-launch model and environment for IDE references * fix(antigravity): forward IDE history opt-in through desktop IPC * fix(antigravity): rebuild remote IDE reference startup on its host * fix(antigravity): register IDE continuation action labels * fix(antigravity): confine IDE references and bound metadata reads * fix(antigravity): localize IDE continuation badges * Preserve scanner service cache assertions and refresh Antigravity opening metadata * Preserve Antigravity opening joins and target folder runtime authority * fix(opencode): retry timed-out SSH plugin updates (#24666) Preserve bounded retry behavior and the current-main status-envelope fields. Original-PR: #24124 Reviewed-source: |
||
|
+33 |
5e6587197a |
fix(opencode): keep shared-session TUI activity with its owning pane (#24962)
* fix(opencode): bind legacy attach status to its TUI pane Adapt the official v1 TUI API to the existing pane reporter and learn structural session identity only after execution-host admission. Preserve known creators, gate new reports to capable hosts, and register the legacy TUI entry without changing user settings or overlay targets. Credit werlang for the same-directory investigation in #22838 and #22847; no source from that PR is copied. (cherry picked from commit d9eeb028a23a66b78b08538a5ebf6052499ec24f) * fix(opencode): fence legacy TUI viewers before creator attribution Use the execution host's existing admission policy on the physical TUI pane and launch token before rewriting to a session creator or mutating the launch-token cache. Preserve live viewers, frozen server stamps and OpenCode 2 behavior. Add HTTP regressions for retired panes, closed tabs, replaced tokens and relay retirement, plus current-launch, alias and compatibility controls. Follow-up to #22838 and @werlang's original #22847; preserves the original intentional creator attribution without copying that PR's source. * fix(opencode): preserve current plugin exports in legacy TUI wrapper Keep the current server, setup, id and other default export entries when adding the legacy TUI entry. Cover their preservation and seed the ACL fixtures with the separate installed TUI/config bytes. This completes the current-main port of the legacy identity replacement for issue #22838, carrying @werlang's original PR #22847 contribution. * fix(opencode): retry timed-out SSH plugin updates (#24666) Preserve bounded retry behavior and the current-main status-envelope fields. Original-PR: #24124 Reviewed-source: |
||
|
|
53f9ea7839 |
fix(opencode): keep Go credentials private and resolve backend keys (#24615)
Preserve the complete credential storage, migration, IPC, Settings and rate-limit refresh change alongside standalone GLM plans, current-main database diagnostics and the reviewed unknown-backend environment correction. Keep native discovery cancellation third and selected environment fourth. Original-topic-commit: |
||
|
|
f7b1f9d8be |
fix(opencode): select tmux status on the execution host
Track the attested tmux selected pane in the canonical status store and fence retired refreshes after asynchronous root resolution. Fixes #10039. |
||
|
|
fee8143d61 |
fix(opencode): atomically install status plugin entrypoints
Retain complete status plugin files during replacement, symlinks and existing permissions, including legacy Windows directory permission recovery. Fixes #24121. Continues #24131; permission-recovery review credited to pullfrog. Co-authored-by: Ahmed Nagy <ahmednagy25t@gmail.com> |
||
|
|
b06f40f3ba |
fix(opencode): read the binder's session store off the main thread; ship the reader worker in orcad (#24638)
* fix(opencode): read the binder's session store on the foreign SQLite reader worker (STA-9122) Before: the OpenCode session binder listed new sessions from opencode.db with node:sqlite on the main thread every 60 s (and on SessionStart kicks), so a large or contended store could stall the app the same way Cursor's did. After: the read is a pure openCodeBinderSessions reader in foreign-sqlite-readers/readers/, run only on the worker. The binder's correlation, pane snapshot and process sweep stay where they were. - The binder round awaits listSessions and re-checks its generation right after, so a stop() during the read discards the round before it touches the unbound map or the watermark. - The client's in-flight dedupe key now includes the cursor, so a stale round from before a restart cannot hand its rows to the restarted round. - Idle teardown is per reader. The binder lane keeps its thread for 120 s, longer than its 60 s poll, so the thread is not respawned every round. - A timeout, crash, malformed reply or unstartable worker resolves to [] (no sessions), the value the old read already returned on failure. - An absent store still reads as [] without a log line, and a permission or corrupt-file failure still logs (kept from #24577, now in the reader: it stats the path and throws anything but ENOENT/ENOTDIR to the client's log). - The binder lane inherits #24572's limits from the shared lane: no respawn until a timed-out worker has exited, 2 consecutive deaths, a queue cap of 8. Its timeout stays 60 s, matching its poll. - dispatch switches on the destructured kind, so a new kind without a case still fails to compile. orcad: the hook server runs there too, so orcad now ships foreign-sqlite-reader-entry.js beside orcad.js (ORCAD_ARTIFACTS, built as an orcad child). build-orcad runs a smoke check that starts the built worker under the build's Node and under the pinned runtime, and does a real binder read on a fixture DB, a Cursor read of a missing file and an OpenCode history list. The OpenCode history scanner uses the same entry and was bundled into orcad without it, so on orcad it always failed closed; it can now run. Tests: reader (cursor, same-ms ids, OpenCode 2 rows, missing then created, corrupt, inaccessible directory), retirement gate for the binder lane, dispatch routing, client lane (rows, failure -> [], dedupe per cursor, own thread, idle teardown default and override), binder loop with an async listSessions (failure -> [], stop during the read), orcad path resolution through orcad's host adapters, artifact list, and the smoke check against good, missing and non-reading entries. * test(opencode): cover the binder read deadline with fake timers and name the failure test accurately (STA-9122) |
||
|
|
8765f8c9b1 |
fix(opencode): preserve turn outcomes and avoid auto permission attention (#24612)
* fix(opencode): retain failed and stopped TUI turn outcomes Adapt the root verdict proposal from brennanb2025 in PR #23105 to the current TUI-owned lifecycle, keeping hook-store authority and existing mainAgent semantics. * fix(opencode): let auto-approved permissions settle before attention * fix(opencode): reconcile cached outcomes with completed session turns * fix(opencode): bind terminal verdicts to ending event timestamps * fix(opencode): publish approval cards for permission requests |
||
|
|
cb03a0545a |
fix(opencode): keep absent session stores quiet (#24577)
* fix(opencode): skip absent session stores without warning * fix(opencode): retain warnings for inaccessible session stores --------- Co-authored-by: drakeo338 <paranoyouz@gmail.com> |
||
|
|
3727100cc9 |
fix(opencode): report OpenCode 2 status from each pane's own TUI (#23722)
* fix(opencode): report OpenCode 2 status from each pane's TUI OpenCode 2 serves every pane from one shared server whose env names only the pane that started it, so every same-folder pane's work showed on that pane, and the plugin's single aggregate swallowed the Idle of a pane whose turn ended while another pane was busy. Install the status plugin a second time as an OpenCode 2 TUI plugin (plugins/<name>-tui/tui.js, written only when its bytes differ, locally, in overlays and in the SSH/WSL relay installs). In a TUI process setup() runs the same engine, fed through the same event translation as the server, but only for root sessions this pane owns: the route's session from when it starts (or when the route reaches it while running, hydrated from the TUI's session status) until it settles, is deleted, or the TUI exits. The server plugin stands down in any serve process when the TUI copy is installed beside it; a relay that predates the TUI copy keeps the old behavior. OpenCode 1 loads no plugin directories and is unchanged. Delete the session-to-pane binder, registry, client sweep and ingest reattribution: with every post stamped by its own pane nothing is left for them to correct. Known gap: OpenCode 2 `opencode run` in a pane has no TUI, so it reports no pane status. * fix(opencode): settle missed OpenCode 2 turn ends and order TUI installs - The TUI copy now settles an owned root whose run the TUI's session data reports ended, with no pending permission or form, when the engine still holds it busy. An execution end missed across a service restart or reconnect no longer leaves the pane Working until the TUI exits. - The TUI copy stays idle without ORCA_PANE_KEY. post() cannot report without it, so OpenCode 2 TUIs outside Orca no longer run the route poll and engine. - Installers write the TUI copy before the server plugin file. The server decides at load whether to stand down, so a reload between the two writes now finds the TUI copy. * fix(opencode): reconcile OpenCode 2 TUI status only once queued events drain The TUI's session data applies each event before this plugin's queued handling reaches it, so settling against it mid-backlog published a false Done before a fast turn's later steps (Working, Done, Working, Done). Reconcile only when no event is queued; a mismatch then is a start or end missed across a reconnect, and both directions are now re-derived (a missed start left the pane on Done). Also install the TUI copy beside the server plugin in the retired shared hooks dir, so a TUI or service still loading that dir reports per pane instead of leaving the service reporting under its starter pane. * fix(opencode): keep OpenCode 2 TUI panes silent on plugin dispose A TUI plugin hot reload disposes the plugin while the pane's turn keeps running. The server path already passes sessionsOutliveDispose so dispose publishes nothing; the TUI adapter now does the same, or every TUI reload would still show a false Done. Also refreshes the generated-bytes digest after rebasing onto the write-if-changed installers. * fix(opencode): refresh installed OpenCode status plugins at app start After an Orca upgrade, an OpenCode 2 service that was already running kept the previous plugin, and with it the old wrong-pane status, until any new terminal pane rewrote the file. OpenCode 2 reloads a plugin whose file changes, so Orca now refreshes its existing installs once after the first window shows: the global config dir, source overlays and the retired shared dir, TUI copy first. It reuses the per-pane writers, which skip unchanged files, never creates an install the user did not have, and honours the status-hook and per-agent switches. An SSH relay does the same for its canonical install when Orca connects and ships the plugin sources. The plugin source assembly moves to its own module (re-exported unchanged) to keep hook-service.ts under the line limit. * fix(opencode): derive each OpenCode 2 pane's status from its TUI session data The TUI copy of the status plugin translated OpenCode events into the server-side engine and then patched the engine's latches back toward the TUI's own session data: a settle on every tick, a queued-event counter, a re-assert, synthetic Busy and Idle. Each review found another place where the two copies disagreed: a missed end left the pane Working, a backlog of slow posts flickered Done, a missed start showed Done mid-turn, a turn held only by a subagent never settled, and a request answered while disconnected pinned Needs input. The TUI reporter now reads the pane's level straight from the session data OpenCode keeps current (and re-hydrates on reconnect) on every event and on a 100 ms tick: for each root session this pane owns, Needs input (an open permission, else form, anywhere in its family while it runs) outranks Working (any family member running) outranks Done. It posts one status per level change through the plugin's existing delivery functions (retry, dedupe, message-part throttle, ordering), so the wire and Orca's ingest are unchanged. Ownership is kept in OpenCode's storage.memory, which survives a plugin hot reload, so a turn that ends during a reload still shows Done; dispose publishes nothing, and a level the old generation could not deliver is re-posted by the next. On reconnect it re-syncs blockers for the owned sessions OpenCode would not re-sync itself, ignoring requests already answered. Events are handled synchronously, so a slow post can no longer hold up event processing. The server path, OpenCode 1 and mimo are unchanged. * fix(opencode): keep an OpenCode 2 pane on Needs input while its root streams text Orca treats every OpenCode MessagePart as Working. While a background subagent waits on a permission or form, the root session can keep streaming reply text, and the TUI reporter forwarded that text as a MessagePart. The pane then flipped from Needs input to Working, and nothing restored it until the level changed, so the user could miss the open request. Skip reply text while the pane's level is Needs input, as the reporter already does for queued prompts. * fix(opencode): leave OpenCode 2 step events to the server path's own change The shared-server translation re-derived Working from session.step.started. The pane reporter no longer uses it, so it only changed the server path and duplicated a separate open change. Drop it; server behaviour matches main. * fix(opencode): reset an OpenCode 2 pane to idle when its TUI starts Before this change, a pane could keep a status an earlier process left on it. The case that matters is an upgrade mid-turn: the old shared-service plugin posted pane B's turn under pane A's key, then stood down, and pane A's TUI loaded the new reporter owning nothing, so it never posted and A showed a wrong Working until its own next turn. A freshly started reporter that owns no running turn now posts the host's existing session-start boundary once, which the host shows as connected idle: no completion, no notification, and an unseen Done it lands on stays unread. A plugin hot reload keeps its memory and skips it, and where OpenCode keeps no plugin memory it is never sent. * fix(opencode): keep OpenCode 1 serve + attach sessions on the attaching pane OpenCode 1 `opencode serve` in one pane plus `opencode attach` in others reports every session from the serve process, whose environment names only the serve pane. Before this branch the session-to-pane binder moved those posts to the attaching pane; deleting it for OpenCode 2 put them back on the serve pane. Restore the binder, registry, correlation and client sweep for OpenCode 1 (and mimo-code, which it also served), gated so OpenCode 2 never uses it: - The status plugin now sends `opencodeMajor: 2` on every post from a process whose loader called setup(), which only OpenCode 2 does. The host skips the binder (no rewrite, no kicked round) for any post that carries it. OpenCode 1 and older plugins send nothing and keep the previous behaviour. - The binder reads only OpenCode 1's `session` table. OpenCode 2 writes `session_v2`, so its sessions never bind; on a database both versions wrote, OpenCode 1 sessions are no longer hidden behind the v2 table. OpenCode-1-only; it goes when OpenCode 1 support is removed. * fix(opencode): show OpenCode 2 `opencode run` as Working, then Done, on its own pane OpenCode 2's `opencode run` loads no plugin, and the shared service that runs its session cannot tell which pane it belongs to, so a pane running it showed nothing. The runtime now reports it from the pane's own process lifetime. On the pane's OSC 133 command start (main already parses 133 for every local PTY; the start callback was never wired), after the pane tracker's 350 ms settle it reads the foreground process name through the existing foreground reader, and only when that name is OpenCode, the foreground command line from one fresh shell-foreground process-table capture. An `opencode run` posts Working through the existing terminal-status path into the hook server's store; the pane's 133;D (or the daemon's background fact) posts Done, marked interrupted on Ctrl-C. No polling. Exclusive with plugin reporting: each write carries the command's start time, and the store drops it once a hook has written the pane since then, so an OpenCode 1 `run` (in-process plugin) or a server plugin without the TUI copy owns its command alone and there is never a second Done. Local macOS and Linux panes only; SSH, WSL and Windows panes stay silent because their foreground cannot be read on this host. * fixup! fix(opencode): show OpenCode 2 `opencode run` as Working, then Done, on its own pane Type the selected descendants as process-table rows; ReturnType of the generic collector widened them to bare identity rows. * fix(opencode): keep an `opencode run` pane's Done after the command exits The run's Done was published inside the chunk that carried its OSC 133;D, before the chunk's command-finished fact. The renderer drops an exited agent's row on command-finished when the row has not changed since that fact arrived, so it took the Done as the stale row and dropped it, in the renderer and in main. Publish the run's Done after the chunk's side-effect facts are emitted (and after the daemon's background fact). The renderer then sees command-finished while the row is still Working, and the Done that follows counts as a change, so it stays, the same way a hook Done that lands after exit already does. * fix(opencode): let an `opencode run` revive a pane Orca retired When an agent Orca launched exits, command completion retires the pane, and only a hook new-turn event revived it. A later `opencode run` in that pane posts no hook, so its process-lifetime Working was refused and the pane stayed silent. A process-lifetime Working is posted only after a fresh OSC 133;C and the pane's own foreground argv prove a new OpenCode run, which is at least as strong as a hook new turn. The store now revives the retired pane on it the same way: it clears the retirement, drops the launch-token fence, and rebinds the observation. OSC status and a lone process Done still cannot write a retired pane, and a closed tab stays closed. * fix(opencode): report `opencode run` on local Windows panes too The run producer skipped every Windows pane, although Orca already resolves a Windows pane's foreground agent from the native process table. On main an OpenCode 2 `run` showed there (on the service's pane); on this branch it was silent. The argv read now has a Windows branch: one fresh native table read (windows-process-table, no interpreter spawn), the same foreground identity the Windows resolver already computes, and the command line of the process that identity names. It runs only after the name read says OpenCode. Local Windows panes whose shell prints OSC 133 C/D (PowerShell with PSReadLine, Git Bash with Orca's wrapper) now go Working, then Done; cmd.exe prints no markers and stays silent. SSH and WSL panes stay silent. * fix(opencode): re-read an `opencode run` foreground on the pane tracker's ladder The producer read the pane's foreground once, 350 ms after the command started. A wrapper, a shim or `sleep 1; opencode run` execs OpenCode later, so those runs stayed silent. The renderer's pane tracker already re-reads a command's foreground at 350, then 1200, then 6000 ms for exactly this. Move those delays to one shared module and use it from both. The producer re-reads only while the foreground is a non-shell process that is not OpenCode, and at most on those three rungs; no new polling. * fix(opencode): end an armed `opencode run` when the next command starts A new OSC 133;C in a pane whose `opencode run` was still armed dropped the armed state without a Done, so a run whose 133;D never arrived left the pane on Working. A new command start proves the previous command ended, so it now posts that run's Done (not marked interrupted: no exit code is known) before the new command is inspected. * fix(opencode): skip the session-start row inside an OpenCode 1 `run` process OpenCode 1 `run` loads the status plugin in its own process, and the plugin posts SessionStart when the run's session is created. The host lands that as an idle session boundary, so a pane the run producer had already shown as Working blinked idle before the plugin's Busy. The plugin now skips SessionStart when its own process is a `run`, read from its argv the same way isOpenCodeRunCommand reads a pane's foreground (first positional after global options, past the compiled binary's entry path). A `run` session goes Busy at once, and its first prompt part still revives a retired pane and resets the turn caches. The TUI and `serve` still post it, and OpenCode 2's `run` loads no plugin. * chore(opencode): say where the plugin's opencodeMajor comes from The field is set when OpenCode's loader calls setup(), which only OpenCode 2 does; the plugin never reads OpenCode's version. Say so at the field. Comment only; the generated plugin is unchanged. * test(opencode): type the late-Done pane fixture without bare casts The new renderer test passed its fixtures with `as never`; use the checked fixture-tuple cast with a SAFETY note, like the sibling pty-connection tests. * fix(opencode): keep `opencode run` silent when OpenCode status is turned off The run producer ignored the status-hooks switch, so a user who turned status off globally or for OpenCode (#23667) still got a run's Working and Done. It now checks isAgentStatusHooksEnabledForAgent for the run's agent (opencode or opencode2), the same predicate the plugin install honours, before reading argv or posting anything. * test(opencode): read the late-Done row without an untyped property access The mock store types agentStatusByPaneKey values as unknown, so reading `.state` failed tc:web. Assert the row with toMatchObject instead. * perf(opencode): read a command's foreground only while it could become `opencode run` Two costs the run producer paid on every command in every local pane: - The retry ladder re-read the foreground (a process-table capture on the local provider) for any non-shell program still running, including other agents, editors and dev servers, up to three times. It now re-reads only while the foreground is still the shell (the command has not exec'd yet) or an unrecognised launcher that may still exec OpenCode (node, bun, bunx, npx, npm, pnpm, pnpx, yarn). Any other program is read once. - With OpenCode status turned off for both opencode and opencode2, it still set the timer and read the foreground only to discard the result. It now returns before any timer or read. The per-agent check after the name read stays for the case where only one of them is off. * fix(opencode): let a new hook turn end a pane's process-exit completion A confirmed agent process exit records a pane-wide completion identity that names only the agent. Hook Dones are matched against it by agent alone, and only a working title cleared it, so in a pane whose agent paints no working title (OpenCode) every later hook-reported Done was treated as already notified: no notification and no unread mark. An unreported exit (a status-off `opencode run`, or quitting an idle client) was enough to set it. A fresh hook Working now clears a process-exit identity, since a new turn cannot be a duplicate of an earlier exit. Hook identities stay, so same-turn and replay dedupe is unchanged. * refactor(opencode): share the foreground read schedule as one value The pane tracker's import of the two shared foreground-read delays took four lines where its old local constants took three, which put the file one line over max-lines once merged with main. Export the settle delay and retry ladder as one object so each consumer imports a single name. No behaviour change: same 350 ms settle and 1200/6000 ms retries. |
||
|
|
6194a7a1b6 |
test: drop private-internal and boundary-census tests with behavioral owners (#23941)
Fourth audit wave, cut short by a session restart, so this lands the verified subset rather than the full batch. Removes private-predicate cases whose behavior is already covered through the module's real entry point, and de-exports the seams they reached for. Also drops three whole files whose every case was a duplicate or a call-shape grep. The source-grep vein is close to exhausted. One auditor reviewed 15 remaining flagged files and deleted nothing: what is left is mostly legitimate architectural ratchets that no type checker and no behavioral test can reach — AST fences banning `as`/`any` in an RPC operation region, discovered-vs-listed set equality over subscription sites, count ceilings on unchecked reply readers, and assertions on generated WebView bundles (no CDN URL, no `</script` tokenizer escape, parses at the Chrome 74 floor). Those stay. |
||
|
|
31012aeb09 |
test: remove assertion-free probes, copied inventories and export-shape checks (#23816)
Second audit wave, targeting three more junk patterns: - assertion-free cases that run code and assert nothing, so they pass no matter what the code does; - inventory literals re-typed from a production declaration, where the only way the assertion can fail is someone editing one of the two copies; - export key-set and export-shape loops (`typeof x === 'function'` over every export) that restate what TypeScript already enforces. Yield is much smaller than wave 1 on purpose: the assertion-free scanner has a high false-positive rate, because many flagged blocks assert through a shared helper or their oracle is "this must not throw". Those were kept. `mobileWebCheckArgs` in `config/scripts/run-mobile-web-app-checks.mjs` is de-exported — after the inventory comparison went away, nothing outside the module read it. |
||
|
|
2ae7c00e84 |
fix(opencode): keep OpenCode 2 panes Working across plugin reloads (#23700)
* fix(opencode): keep OpenCode 2 panes Working across plugin reloads OpenCode 2 disposes and re-sets-up every plugin whenever its plugins dir changes, while sessions keep running. The status plugin published a final Idle on dispose, so a pane read Done mid-turn. Orca also rewrote the plugin file on every PTY spawn, so opening any terminal triggered that reload. Dispose now releases the factory's bookkeeping without publishing a verdict; the next lifecycle event settles the pane, and Orca's ended-process reconciliation still retires panes whose agent exited. The plugin file is written only when its bytes differ. * fix(opencode): skip rewriting an unchanged plugin in the SSH relay install too The relay's canonical-config install still unlinked and rewrote the status plugin on every OpenCode launch over SSH, which restarts every plugin in a remote OpenCode 2 server. Share one install-currency check (lstat + the existing byte comparison) between the local and relay writers, and pin write-if-changed with mtime so the tests also fail on filesystems that reuse a freed inode. * fix(opencode): keep the final Idle when OpenCode 1 tears its instance down OpenCode 1 disposes a plugin only when it tears the instance down, and that teardown cancels every running session, so the Idle published on dispose is true there; the cancelled run's own idle may never reach the plugin. Only OpenCode 2 disposes on a hot reload while turns keep running. The generated module serves both hosts, so the OpenCode 2 setup() entry point now tells the shared factory that sessions outlive disposal; the server() path keeps the previous disposal behaviour, including the hand-off to a surviving factory. * fix(opencode): compare a symlinked plugin by its target before rewriting OpenCode 2 loads plugins through file-level symlinks and reads the revision from the target's mtime, so a user whose Orca plugin file is a symlink (per-file dotfile managers) failed the regular-file check and got a write through the link, and a reload, on every spawn. The config-dir and relay installs now skip the write when the resolved target already has Orca's bytes; when stale they behave as before. Only the per-source overlay keeps the regular-file check, since a link there mirrors a user entry. Installers also skip the write inside a guarded block rather than returning early, so later install steps still run. * test(opencode): skip the plugin symlink tests on Windows like their neighbours Creating a file symlink on Windows needs Developer Mode or admin rights. * test(opencode): stub fetch without a type assertion in the dispose host test |
||
|
|
813aff8f8a |
fix(opencode): stop OpenCode 2 loading a stale plugin from the retired shared hooks dir (#23500)
* fix(opencode): stop OpenCode 2 loading a stale plugin from the retired shared hooks dir Before 1.4.209 Orca pointed OPENCODE_CONFIG_DIR at <userData>/opencode-hooks/shared and wrote a server()-only status plugin there. 1.4.209 moved the plugin to OpenCode's global config dir and 1.4.210 added the v2 setup() export, but nothing rewrote the old file. Shells, daemon-persisted panes and OpenCode 2 background services that still carry that OPENCODE_CONFIG_DIR load only that dir under OpenCode 2 (it replaces the global dir), so the v2 loader rejects the stale plugin with "Plugin must export a default definition with an id and an effect or setup function" and pane status dies. - Refresh the plugin in the retired shared dir (only when it already exists and its content differs) so OpenCode processes started later from old shells load the dual v1/v2 export. Runs on OpenCode pane spawns and on any spawn that inherits the retired dir, even with agent status hooks off. - Drop an inherited OPENCODE_CONFIG_DIR / ORCA_OPENCODE_* marker that points at the retired dir when building a new pane env, so new panes use global discovery. Limitation: an OpenCode 2 background service already running from an old pane keeps its cached copy of the stale module even after the file is rewritten (verified with opencode2 v2.0.18). It must be restarted (`opencode service restart`); a restart from a new Orca pane then picks up the global config because the env is stripped. * fix(opencode): harden legacy plugin repair and inherited config cleanup * fix(opencode): preserve daemon-owned user config during legacy cleanup * fix(opencode): sanitize inherited sources and repair unseen legacy copies * test(opencode): update shared PTY mocks for legacy repair * test(opencode): annotate shared repair mock signature --------- Co-authored-by: m4air <m4air@m4airs-Air.localdomain> |
||
|
|
ee6281ff79 |
fix: dispose OpenCode 2 hooks after prompt setup and cleanup failures (#23209)
* fix: dispose OpenCode 2 hooks after prompt setup and cleanup failures * test: pin reviewed OpenCode setup cleanup bytes * fix(i18n): restore diff note draft catalog entries * test: isolate historical hourly build version inputs * test: align historical package input with shared CI repair * test: inject hourly package version without module mocking * test: share the hourly package input contract across CI repairs * test(windows): verify NSIS policy with native PowerShell modules --------- Co-authored-by: m4air <m4air@m4airs-Air.localdomain> Co-authored-by: OrcaWin <alpha-eng@stably.ai> |
||
|
|
8ddad49ae5 |
fix(opencode): retire a subagent's blocker when the root turn ends (#22604)
A descendant's question is stored under its own sourceSessionID but displayed on the root session it rolls up to. clearAttentionForSession matches only the source, so the root's own idle could never retire it: the blocker outlived the turn that raised it and pinned the pane on "needs input" with nothing left to answer. The lead agent truthfully reports no pending question, because the blocker belongs to a child it does not know about (#22371). Add clearAttentionForTurnEnd, matching the source id or the rolled-up root, and use it only from the idle branch. Scoped to turn end deliberately: a live blocker must still outrank the root going Busy, since a subagent can be waiting on the user while the root keeps working — widening the existing clear broke exactly that test, which is what surfaced the right scope. Shared with the OpenCode family, so mimo-code gets the same fix; both pinned plugin digests move. New test fails without the change: after the root turn ends the last post is still AskUserQuestion instead of SessionIdle. |
||
|
|
57bf732a42 |
test(opencode): cover the opencode2 host-env branch and stop inheriting ORCA_OPENCODE_AGENT (#22547)
* test(opencode): pin per-major OpenCode overlay selection on WSL and the relay OpenCode 2 dies with "Duplicate plugin ID" when two plugin files share an id, so which overlay a guest or remote pane is pointed at decides whether the agent starts. Nothing failed if that selection regressed: - the WSL spawn path never asserted which major it asks the guest relay for, and the shared pty-ipc mock had no openCode2HookService at all, so no test could reach the opencode2 branch of buildPtyHostEnv; - requestGuestOpenCodeOverlayDir had no coverage for the v2 guest dir; - PluginOverlayManager had no case for a remote config root that still holds the other major's stale Orca plugin. Tests only; no behavior change. Each new case was mutation-checked against the production line it guards. * test(opencode): stop the plugin contract test inheriting ORCA_OPENCODE_AGENT The generated plugin self-disables when ORCA_OPENCODE_AGENT names a different major, and the contract test saved and restored that variable without ever setting it. Run from a shell that has it — which is any shell inside an Orca OpenCode pane, i.e. how this repo is usually developed — the plugin returned an empty hook set and the contract failed for the wrong reason. Delete it in beforeEach, the way the opencode2 setup test already pins it. Verified the file passes with the variable set to either major and unset; before this it failed for two of the three. |
||
|
|
b0ae7d18a0 |
fix(opencode2): resolve subagent session lineage so child work stops taking over the pane (#22444)
OpenCode 2's plugin adapter unwraps a single-property `{ data }` success schema,
so `ctx.session.get` resolves to the bare session record. The shared lineage
lookup only accepts `result?.data?.id === sessionID`, and OpenCode 2 has no
`session.list` fallback, so `resolveRootSessionID` returned null for every
session and `childState` was permanently null.
With unknown lineage `canFailOpen` is true for attention events, so a subagent's
`permission.asked`/`question.asked` fell through and pinned an un-evictable
blocker keyed to the child's own session id — publishing a subagent as if it
were a root. Observed in hook posts: SessionBusy for a child session id whose
`session_v2` row carries a parent.
Envelope the result in the OC2 client shim so the shared lineage module works
unchanged; OpenCode 1 already receives enveloped results and is untouched.
Also adds `opencode2` to the double-Escape interrupt list, extracted into one
shared helper so the server inference and renderer gate cannot drift. A single
Escape was inferring an interrupt, and Escape is how the Subagents dock closes.
7 of 11 new lineage tests fail without the shim.
|
||
|
|
8352752e54 |
fix(opencode): give the opencode2 status plugin a distinct id (#22544)
* fix(opencode): fail-open plugin setup and distinct opencode2 plugin id setup() threw when OpenCode 2 probed it without a full context, which the TUI reported as an 'orca-opencode-status' plugin failure. Both plugin files also shared one id while living in the same config dir. * fix(opencode): drop unused oxlint-disable in setup fail-open test * test(opencode): pin distinct plugin ids for the shared global config dir Orca installs both family plugins into one global plugins dir, so a shared plugin id makes OpenCode 2 fail the later one with 'Duplicate plugin ID'. |