* 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.
* fix(orchestration): stop retried failed worker starts from piling up live agents
A failed worker-start keeps the terminal it created (a pane the user typed
into is theirs), so a coordinator retrying a start that can never succeed
left one more live agent behind on every retry, with nothing to stop it.
- A --retry-of start now releases the failed attempt's still-owned terminal
through the normal worker-release path (output archived, user takeover
kept, recovery finishes it after a restart).
- A failed start now counts toward the Task's existing three-failure circuit
breaker, carried across retries; a retry of a circuit-broken Dispatch is
refused with a plain error.
Linear: STA-7113
* test(orchestration): type the failed-start retry fixtures without casts
* fix(orchestration): carry failed-start count only along --retry-of; plain breaker refusal
- Only a --retry-of start inherits its predecessor's failure count, so worker
exits across separate starts keep the old behaviour.
- The circuit-broken refusal carries failureCount and next steps that say to
stop retrying, read the last attempt, and that its pane stays open.
- A repeated failed report on a circuit-broken Dispatch is a duplicate.
Linear: STA-7113
* fix(orchestration): breaker refusal only points at a terminal that is still open
Linear: STA-7113
* fix(orchestration): log a retry's release that could not prove the old terminal closed
Linear: STA-7113
* Preserve worker diagnostics before terminal release and mask wrapped tokens
* wip: connect fails for hosts the managed server can't serve; remove relay session, deploy and relay UI
* wip: delete relay-only client modules left unreachable, prune their tests and gates
* wip: format
* wip: exhaustive unserved message
* wip: drop relay attestation test for the deleted ownership proof
* fix: prune stale baselines; type the test transport stub
* fix(ssh): census proves leftover relay leases; name the remedy for older terminals; translate every connect-failure surface
* test(ssh): leftover leases defer to the host census
* test(e2e): remove relay-path SSH e2e specs and their CI lanes
* test(e2e): drop the auto-convert case that built its relay era through a live relay
* test(e2e): mark relay-era browser and catalog journeys fixme until they seed a profile
* test(e2e): seed relay-era browser and catalog journeys from a saved profile
The three journeys built their old-version state through a connect without an orcad template, which now refuses the host. They now start from a seeded relay-era profile, so the first connect converts the host as an upgrading user's would.
* chore(ci): drop shard timings for the deleted relay tests
* test(e2e): carry a relay-era login cookie across the browser conversion; own every launch
* test(ci): drop the deleted SSH layout oracle from its route
* test(e2e): open the retained browser after conversion, retrying a pre-connect card
* test(e2e): bind the relay-era browser partition before seeding its cookie
A relay-era build always recorded the partition binding before a page stored
data in it. Seeding the cookie into an unbound partition made the binding store
refuse it (browser_route_partition_binding_store_invalid), so the retained tab
showed the generic routing card and Retry could never clear it.
Co-Authored-By: Claude <noreply@anthropic.com>
---------
Co-authored-by: m4air <m4air@Mac.localdomain>
Co-authored-by: Claude <noreply@anthropic.com>
Combine related chat, worker-release, and filesystem tests while retaining their contracts. Replace fixed Codex terminal waits with observed readiness and hook completion. Keep runtime database contracts under Node.
Local measurements and all CI lifecycle costs are documented in PR #27297.
* Keep clear in the same chat and reset its provider context
* Derive clear pauses only from journal membership and cover older rewind clients
* Point queued command rewind test at the structured hook
* Group queue consume errors and rewind replay assertions
* fix(claude): end a Claude start that never answers initialize after 120 s
* Read the Claude startup deadline inside startup; fix a stale test comment
* fix(claude): start a Claude chat on its initialize answer, not on a frame only a SessionStart hook sends
Startup waited for system/init or a SessionStart hook frame as well as the initialize answer.
Before the first turn only a SessionStart hook sends one, and Orca adds that hook only through
its optional status hooks, so with them off the first message was held forever. Startup now
lands on the initialize answer; a start frame already seen is still checked, and one naming
another session ends a started session. The deadline drops to 90 s so it fires inside the
host's 120 s start wait.
* fix(claude): time the Claude start by silence, and fail it at once on another session's frame
Claude answers initialize only after its SessionStart hooks finish, so a total-time deadline
would fail every start behind a slow hook. Each start frame now restarts the clock. A frame
naming another session fails a start still waiting on initialize at once, as before.
* test(claude): a real Claude chat starts and answers with every hook disabled
* Say what the start-frame re-arm covers, and check only start frames in the hook-less real test
* fix(native-chat): a Claude chat starts with its saved options and takes its first message at once
Saved model, effort, Fast and permission mode are passed as launch options, checked against the
account's cached model catalog, instead of restored by control requests after initialize. With
nothing left to restore, the host no longer holds a message until the CLI answers initialize, and
the 90 s startup deadline is gone. A Stop on a start that never answers ends the child and settles
what it was handed as stopped. A failed result that repeats the turn's own API error reply writes
no second row.
* fix(native-chat): a host stop of a Claude start fails the message it was handed, with one row
With no start-hold the delivery loop no longer sees a host stop of a start it waited on. The
child's end now rejects what it handed over with the host-stopped words and writes the one row,
as an exit of its own would; an idle start the host stops still goes quietly.
* test(native-chat): a Claude chat's first message is written before initialize answers
Rewrites the tests that encoded the start-hold, the startup deadline and the option restore to
the new contract, and adds: saved options at launch (catalog checks, bypass, fresh-session Fast),
a message written before initialize answers (adapter and runtime), Stop on a start that never
answers (stopped, child closed, nothing working), and an API error said once.
* revert(native-chat): keep a failed Claude turn's error row
The shared turn fold already shows a failed turn's error once after it settles, as an error;
dropping the row left the CLI's synthetic reply looking like something Claude said.
* fix(native-chat): pass saved Claude options unchecked, heal a retired model on the CLI's word, and never leave an unrun message in doubt
- Saved model, effort and Fast are launched as picked; only values no Claude can parse are left
out. The pre-spawn cache check is gone.
- A saved Fast on for a new conversation is applied once the settings readback shows no
per-session opt-in (dropped when there is one, or when the model is listed without Fast), with
nothing waiting on it; the record keeps the pick.
- Under an Agent Permissions bypass, a saved narrower mode launches with the allow flag so bypass
stays reachable.
- A turn whose reply is the CLI's model_not_found for the launched model drops that model from
the record; the launch's own row for it is kept out of the account model cache.
- A child that ends before it answered initialize, for any reason, settles every message it was
handed as not sent (cancelled for a Stop).
- A launched effort the CLI reports only as `applied.effort` is confirmed from there.
- The untimed-initialize comment is back to main's text.
- A real-CLI test for a message written before initialize answers, under saved options.
* fix(native-chat): type the close's ended event and the start-exit test fixtures
The close's ended event is typed as the adapter event so its optional startupUnanswered spread
fits exactOptionalPropertyTypes; two tests guard the fixture's optional generation, and the
hung-start fixture records initialize on the fake connection it holds.
* fix(native-chat): a Claude model heal keeps a later pick, a refused Fast is dropped, no allow flag
- `options-skipped` carries the retired value; the record drops it only while it still holds it.
- `started` carries the values a heal retired, and the record does not take them back from the
CLI's report of the same value.
- A saved Fast on a new conversation is applied before `started`: a refusal drops it and records
it skipped, as main's refused restore did; silence keeps it wanted and unconfirmed.
- A saved narrower mode under an Agent Permissions bypass launches without any bypass flag again:
the allow flag is one older CLIs reject at start. Kept as a known limit.
- The real-CLI test asserts the message was written before initialize answered.
- The fake reports a launch effort only under `applied`, and a misplaced doc comment moves back.
* fix(native-chat): a new Claude chat reports started before its saved Fast is applied
The Fast apply on a new conversation now runs after `started`, so a Stop interrupts a running
first turn and an option write is not refused while the round trip is out. A refusal drops the
pick through `options-skipped`, in order after `started`; silence keeps it unconfirmed. The
launch's unreachable skipped-model branch is gone.
* fix(native-chat): a healed Claude chat goes back to the default model live; comments match the no-hold design
When the CLI says the launched model does not exist, the live child is also put back on the CLI's
own default (set_model with no model, fire-and-forget), so later messages in the same chat run; a
user's pick sent after it wins, and a refused or unanswered reset only logs. Comments that still
described the start-hold or the option restore now describe the launch options and the
handed-over, never-echoed rule.
* fix(native-chat): a message handed to a Claude start that never answered is kept as main keeps an unsent one
A child that ended before it answered initialize ran nothing it was handed, the same fact as a
send accepted and never handed over. Its end now settles those sends exactly as the chat settles
a queued send for that end: a quit keeps a person's message as a held card (restart words), a
close keeps it as a held card (closed words), a person's Stop withdraws it as cancelled, and a host
stop fails the start with one row.
* fix(native-chat): a quit during a Claude start that never answered offers no resume for the message it keeps as a card
The restart snapshot now reads the same never-answered fact the exit does, so a message handed
to such a start counts as queued work, not as work to resume. The retired-model reset comment
names the default it really applies.
* refactor(native-chat): the saved permission-mode launch helpers live with the spawn options that use them
Keeps claude-structured-launch-resolution.ts within max-lines once merged with main, and names
the hung-start test envelope's field type.
* fix(native-chat): a Claude chat's saved options take precedence over the agent Arguments' own flags
Main now passes the saved agent Arguments to the Claude child, and the SDK writes them after its
own options. An Arguments --model or --effort therefore reached the CLI as a second flag after the
chat's saved pick (a commander CLI keeps the last), and a saved Fast's launch settings replaced an
Arguments --settings file outright. The saved model and effort now stand in for the Arguments'
flags, and a saved Fast beside an Arguments --settings is applied by the start instead of at
launch.
* test(native-chat): the hand-built Claude session in the options test carries fastModeAtStart
* test(native-chat): the queued rig's start spy carries a named Mock type
An unannotated vi.fn() inferred @vitest/spy's internal Procedure, which CI's typecheck cannot
name in the factories' inferred return types (TS2883).
* fix(ci): run the PR's SQLite-backed tests in the Node runtime project
Lists the hung-start Stop test, renames the send-during-startup entry from its old name, and
carries main's own two entries from #26010 so the boundary test passes before the next merge.
* Schedule unit tests across runtimes by measured duration
* Keep new SQLite fixture suites on Node after updating main
* Inject scheduling timings instead of mocking the module
* Keep agent-session database lifecycle contracts on Node