Commit Graph
10324 Commits
Author SHA1 Message Date
3bcb312043 Keep managed servers unverifiable until they answer (#26637)
* Require a server reply before treating a live managed process as serving

* Log the unanswered live managed server like the other unverifiable verdicts

---------

Co-authored-by: m4air <m4air@Mac.localdomain>
Co-authored-by: OrcaWin <alpha-eng@stably.ai>
2026-10-09 04:22:17 -07:00
c12c211804 Preserve retained desktop browser pages during SSH conversion (#26572)
* Preserve desktop browser page placement when SSH workspaces convert

* Place the browser-service-status page on the managed host explicitly

Converted SSH workspaces now keep retained desktop pages local, so the
spec opens its remote-placed page on the managed environment instead of
relying on the retained page moving there.

* test(e2e): refocus the managed browser page before retrying its Reconnect

The managed host can seed a terminal for the converted workspace and the
client mirrors it with focus, hiding the browser notice mid-test.

---------

Co-authored-by: m4air <m4air@Mac.localdomain>
Co-authored-by: OrcaWin <alpha-eng@stably.ai>
2026-10-09 04:22:13 -07:00
Neil 7b1242ba94 [CI] Reduce repeated test work and use pinned Bun for localization extraction (#26806)
* [CI] Reduce repeated test setup and retire source inventories

* [CI] Give consolidated floating panel suites distinct titles
2026-10-09 02:37:53 -07:00
Neilandm4air 1dab0d076e Scope file deletion saves and tab closing to the file owner (#26679)
Co-authored-by: m4air <m4air@Mac.localdomain>
2026-10-09 02:07:19 -07:00
Brennan Benson f16212a29c feat(acp): compact Grok, OpenCode and OMP chats (#26502)
* feat(acp): compact Grok, OpenCode and OMP chats

* test(acp): expect Grok compaction capability

* fix(chat): keep skipped compaction outcomes visible across history pages

* fix(acp): a signed-out /compact reports not signed in, as a send does

A /compact the agent refused for sign-in now records the same notSignedIn
fact as an ordinary send, so the chat shows sign-in guidance and the next
send starts a new agent that reads the new login.

* refactor(acp): keep the timeline translator under its line limit after main's growth

Main's OMP per-channel message identity grew acp-timeline-translator.ts past
max-lines with this PR's compaction handling. The running compaction's frame
folding moves beside its ending in acp-compaction-turn.ts, and the
compacting-prompt lookup into AcpPromptTurns; behaviour is unchanged.
2026-10-09 02:05:31 -07:00
Neil e43997c015 Roll back Windows hang watchdog activation (#26804) 2026-10-09 02:03:27 -07:00
Neilandm4air fd6bc7e96f Keep managed status warnings out of desktop gitignore writes (#26671)
Co-authored-by: m4air <m4air@m4airs-MacBook-Air.local>
2026-10-09 02:03:04 -07:00
Neil 3ca1d81458 fix(source-control): wait for the repo owner's editor saves before discard (#26677)
Single and bulk discard quiesced editor autosaves for the globally focused
runtime, so a managed repo's in-flight save could land after git restored the
file and bring the discarded edits back. Use the repo-owner-routed settings
the discard itself already uses.
2026-10-09 02:02:41 -07:00
Neil 1578716eb0 revert(runtime): temporarily restore the previous host Node minimum (#26823)
This reverts commit 46460d49c7 (#26171).

Pause the Node 24 requirement so existing SSH and WSL users are not
surprised by a host-runtime upgrade requirement in the next release.
This is a precautionary rollout hold requested to avoid near-term user
disruption, not a cancellation of the Node 24 transition.

Restore the prior Node 18 host minimum and companion build targets.
The previously shipped bundled Node 24 runtime and compatibility launcher
remain in place. Preserve the original change in a separate draft PR for
reintroduction after compatibility and rollout validation.
2026-10-09 02:00:57 -07:00
Brennan Benson 39a01f9c0b feat(agent-launch): create a folder workspace and start its agent in one host operation (#26083)
* feat(agent-launch): accept a create-folder-workspace target

agent.launch can now create a folder workspace and start its agent there,
the same operation it already runs for a new git worktree. The host
creates the folder workspace (metadata only, no startup terminal), resolves
it the way an existing target is resolved, then settles the mode and builds
the surface as for any existing folder workspace.

Hosts advertise agent.launch.create-folder-workspace.v1 so a client offers
the target only to a host that accepts it.

* fix(agent-launch): authorize, classify and navigate folder creates like their create method

A create-folder-workspace launch could do three things its two-call
equivalent never did:

- A phone could create folder workspaces. The mobile allowlist admits
  agent.launch but not folderWorkspace.create, and is checked by method
  name only. A create target now needs the authorization of the create
  method it stands for, read from the same allowlist, and a phone is
  refused with agent_launch_target_forbidden before admission, so no
  ledger row and no create. create-worktree maps to worktree.create,
  which phones already may call, so nothing changes for it.
- A create refused before anything was stored (group gone, folder
  missing, SSH host unreachable) replayed as
  agent_session_operation_unknown, and a retry under the same operation
  id was refused until the row expired. runtime.createFolderWorkspace now
  raises those refusals as FolderWorkspaceCreateRefusedError, with the
  same message text, and the launch records it as failed for any target.
  A failure after the create still replays unknown.
- A paired caller's folder create activated the new chat on the host
  window and did not select the caller's own tab. Only create-worktree,
  whose host activation runs setup, keeps the host navigation now; a
  folder create navigates like an existing workspace.

Tests cover the phone refusal, replay of a success, a refused create
settling failed and replaying that failure, an after-create failure
staying unknown, an SSH connection reaching the agent's view mode, paired
navigation, and an older host refusing the target at parse.

* fix(agent-launch): replay a refused folder create with its whole path

A folder create refused before anything was stored is recorded as the
launch's failure, and a retry under the same operation id answers from
that record. The record went through the 128-character cap every launch
failure code gets, and folder refusals carry the folder's absolute path,
so a retry could show the user a path cut short: a different folder.

The refusal is now recorded whole when it fits a path as long as Linux's
PATH_MAX after its code. Past that, which no folder a stat accepts
reaches, only the code before the path is kept, so the replay degrades to
the bare code instead of a partial path.

Also: the controller test asserts the exact refusal message, the
create-folder-workspace capability comment says a phone needs its own
gate, and the forbidden-target code says why it is not `forbidden`.

* test(folder-workspace): type the controller test's store stub without an assertion

The changed-code quality gate refuses the `as never` cast on the stub
store. It is now a RuntimeStore whose unreached members throw.

* test(agent-launch): run the folder-create launch tests in the Node runtime project

The operationId replay tests open the real SQLite record store, so the
file belongs with the other ledger-backed agent.launch tests.

* test(agent-launch): pin that a paired folder create's terminal stays off the host window

The folder-create test now asserts surfaceOwner: false on the terminal a
paired caller's launch creates, rather than matching any options. The
caller-selection header now says only an in-process worktree create keeps
worktree.create's navigation.

* test(agent-launch): drop the per-connection view-mode expectation main removed
2026-10-09 01:23:06 -07:00
Brennan Benson 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.
2026-10-09 01:20:52 -07:00
Neilandm4air 6ef42adfc0 Keep Git identity probes from blocking SSH host reconversion (#26578)
* Preserve host Git identity cache when reimporting repositories

* Keep the destination's automatic GitHub avatar when reimporting a renamed remote

---------

Co-authored-by: m4air <m4air@Mac.localdomain>
2026-10-09 01:08:37 -07:00
Neil df922d3709 Keep one local project identity after concurrent registration (#26789) 2026-10-09 00:57:04 -07:00
Brennan Benson 4eb6c0e5f7 test(native-chat): fix main typecheck after the batched journal commit (#26781) 2026-10-09 00:44:21 -07:00
Brennan Benson c586cce8cc fix(acp): start chats for agents that report no model (#26587)
* fix(acp): start chats for agents that report no model

* fix(acp): omit unknown models from resting session reads

* test(acp): expect absent models for resting Claude chats

* refactor(native-chat): move known agent discovery out of session host
2026-10-09 00:42:23 -07:00
Neilandm4air bcb7305fea Refresh SSH host status after a verified manual managed-server update (#26665)
Co-authored-by: m4air <m4air@Mac.localdomain>
2026-10-09 00:41:19 -07:00
Brennan Benson 418c601d20 Start orchestration workers and remote workers through the launch executor (#26724)
* Start worktree.create's agent through the launch executor

The host's worktree.create handler now starts a requested agent (startupAgent,
or a linked startupDraft) through executeAgentLaunch instead of calling the
create directly. Nothing on the wire changes: the request, the reply and
agentTerminalHandle are the same, the agent is still the workspace's startup
terminal whatever the chat default says, and the create still delivers the
text itself (folded into the command at any length, a follow-up for agents
that read it after start, and the draft pasted unsent).

Adds three host-internal executor inputs: terminalOnly, the legacy-host prompt
policy, and a shared entry (createWorktreeWithStartupAgent) that host
producers can call in place of createManagedWorktree.

* Keep a linked-draft create recording only the agent it actually started

Problem: after worktree.create moved onto the launch executor, a create from
a linked issue (phone "create from issue", or a raw worktree.create with
startupDraft) could differ from before in two narrow cases. When the draft's
agent came from the settings default or from detection and its launch command
could not be built (for example unbalanced quotes in the saved agent
arguments), the workspace was recorded as created with that agent even though
it never started, and a background create opened no terminal at all instead
of its plain shell. When no agent was found, detection ran twice (an extra SSH
round trip on remote repos).

User-facing change: none intended; both cases now behave exactly as before
the executor move.

Mechanism:
- The entry hands the draft's chosen agent to the create as a main-internal
  startupDraftAgent, which steers only the draft; createdWithAgent stays what
  the caller sent. A draft that resolves no agent is created without the
  draft, so the create does not detect again.
- AgentLaunchExecution is a discriminated union: every normal launch must
  bring its surface factory, and a legacy-host launch must be terminal-only,
  bring a workspace factory, and cannot bring surfaces. The runtime guards
  the types replace are gone; the target kind is checked before any create.
- Legacy-host receipts follow the delivery module's rule: a blank prompt is
  no prompt, a submit folded into the launch command is handed-to-terminal,
  a post-start send the create does not await is unconfirmed (host-internal),
  a draft is not-delivered.
- The no-agent error lives with the legacy-host policy and is caught only by
  the shared entry.
- Tests: a real-runtime parity test (stored agent, terminal spawns,
  detection count vs the create alone, including the unbuildable plan),
  exact-argument checks over every non-agent startup field, compile-time
  checks for the union, and direct tests of the draft agent resolver.

* Say which creates still bypass the shared agent entry

Two comments stated that headless automations and federation already go
through createWorktreeWithStartupAgent; both still call
createManagedWorktree directly. Reword them as the plan. Comment-only.

* Start orchestration workers and remote workers through the launch executor

A fresh worker (new worktree or existing, terminal or structured) and a
federated worker on its receiving host now start their agent through
executeAgentLaunch with the worker's own workspace and surface factories.
The worker hands in the mode it decided before its dispatch record existed
(decidedMode), so the host is asked once and the stored receipt is the one
that ran. Effects, setup receipt, stage rows and their order are unchanged;
a refused structured worker still fails rather than becoming a terminal.

* Tighten the worker launch contract and discard a worker session a later launch step strands

- decidedMode is now exclusive with terminalOnly and callerRendersStructured in the type, labelled temporary until one planner settles every launch.
- connectionId's doc says what is true: the executor carries it and nothing reads it yet.
- A worker launch that throws after its structured session exists discards that session itself, since the caller only tears down a returned placement.
- A federated create refuses a launch with no startup agent and narrows repo/name before recording its first stage.
- Tests: exact federated existing-path stages, a created-worktree host refusal that starts a worker terminal, and the stranded-session discard.

* Say exactly which inputs a decided launch mode refuses, and pin the refusal at compile time
2026-10-09 00:39:13 -07:00
Neilandm4air bfd9858242 Retry failed catalog reads when SSH hosts move to Remote (#26531)
* fix(remote): retry failed SSH conversion catalog loads

* fix(remote): keep the loaded catalog across ordinary disconnects

* fix(remote): replay a reconnect that lands while a failed catalog read is pending

---------

Co-authored-by: m4air <m4air@Mac.localdomain>
2026-10-09 00:38:14 -07:00
Brennan Benson cbad6412be feat(grok): show Grok's subagents as subagent rows (#26508)
* feat(grok): show Grok's subagents as subagent rows

* fix(grok): retain live subagent roster ownership

* fix(grok): retain child outcomes during shutdown

* fix(grok): show live subagents in running task surfaces

* fix(grok): settle owned child session completion notices

* refactor(grok): fold child-session guards under the translator line limit
2026-10-09 00:26:39 -07:00
Jinjing 69cf557a69 fix(sidebar): keep project headers from overlapping the pinned host (#26775)
The host header lays out as a 32px card in 4px top padding (36px), but
estimateRenderRowSize sized the row at 32px. The group tier's 36px pin
offset then fired 4px early, so a project header could pin while its
in-flow copy was still on screen — two headers at one coordinate.

Size the host row 36px so it matches the pinned slot, and resolve each
sticky handoff from the row's real start offset instead of only the
lagging range start, so a header pins exactly when it reaches the slot.
2026-10-09 00:09:06 -07:00
NeilandNaveen Chatlapalli cc2ad7b188 fix: dispatch Muse Windows hooks directly on safe profile paths (#26755)
Run Muse Windows hooks through the existing direct launcher on safe profile paths and refresh managed installations. Keep the PowerShell compatibility fallback for paths requiring special quoting. Verified on Windows; CI passed.

Adapted the direct-launcher change from community PR #26734.

Co-authored-by: Naveen Chatlapalli <naveen.aifanatic@gmail.com>
2026-10-09 00:05:58 -07:00
Neilandfanzzzd cf8cc09e25 [CI] Enable the existing Windows hang watchdog and preserve restart evidence (#26763)
Enable the independent hang watchdog on Windows and preserve the previous run's marker until the startup trace sink is ready. Retain local consent, heartbeat timing, worker cleanup and bounded marker validation. Operation tracking is deferred.

Adapt the startup-order regression coverage from community PR #26468.

Co-authored-by: fanzzzd <fanzzzd@outlook.com>
2026-10-08 23:58:04 -07:00
Neilandm4air f01bd93748 Keep managed workspace paths out of desktop editor launches (#26668)
* Guard desktop open actions with the target workspace owner

* Wait for the folder catalog only in the folder menu exercise

* Resolve the row owner only while its Open in menu is open

---------

Co-authored-by: m4air <m4air@Mac.localdomain>
2026-10-08 23:49:11 -07:00
Brennan Benson ded0144525 fix(native-chat): settle a dead agent's leftover work in one write that always fits (#26664)
* fix(native-chat): settle a dead agent's leftover work in one write that always fits

A dead generation's cleanup was several separately committed writes, and a
single leftover item larger than one batch row was rejected on every try, so
the chat stayed stuck. Plan every row first and commit them in one
transaction: bounded batch rows, an oversize item as an ordinary row with its
full body, and the settlement's own send verdicts. Readers see it only after
the commit. Settlement rows are rebuilt only from schema-known fields, address
saved items by their stored key, never invent an end time, and new writes
require safe revisions, so cleanup never builds a row it cannot save.

* fix(native-chat): settle Codex exits in one write and keep turn fields a newer build saved

- Codex's own exit cleanup hands the whole settlement to the journal's one-transaction planner instead of committing chunk by chunk.
- One shared planner rejects an item named twice, on both the direct and resolved paths.
- Rewriting a turn keeps fields this build does not know, as the journal schema promises; the legacy carrier's kind still cannot override it.
- Drop the write-only revision admission and the planner skip that worked around it: insert admission is the reader's again, and a saved revision that cannot advance is skipped by the reducer while the rest settles, as before.
- Remove two dead-generation journal members settlement no longer calls.

* fix(native-chat): advance repeated items across a settlement's rows and keep nested turn fields

Repeated Codex question ids in one write fold in order again instead of failing the write,
and a saved turn's context usage keeps fields a newer build stored inside it.

* test(native-chat): run the new Codex journal SQLite tests in the Node runtime project
2026-10-08 23:47:49 -07:00
Neil 46460d49c7 feat(runtime): require Node 24 for servers and remote hosts (#26171)
* feat(runtime): default to bundled Node 24 and require modern hosts

* Update SSH deployment expectations for Node 24 guidance

* Expect Node 24 in Windows runtime installation guidance
2026-10-08 23:35:11 -07:00
Brennan Benson d7e985caa7 Start worktree.create's agent through the launch executor (#26709)
* Start worktree.create's agent through the launch executor

The host's worktree.create handler now starts a requested agent (startupAgent,
or a linked startupDraft) through executeAgentLaunch instead of calling the
create directly. Nothing on the wire changes: the request, the reply and
agentTerminalHandle are the same, the agent is still the workspace's startup
terminal whatever the chat default says, and the create still delivers the
text itself (folded into the command at any length, a follow-up for agents
that read it after start, and the draft pasted unsent).

Adds three host-internal executor inputs: terminalOnly, the legacy-host prompt
policy, and a shared entry (createWorktreeWithStartupAgent) that host
producers can call in place of createManagedWorktree.

* Keep a linked-draft create recording only the agent it actually started

Problem: after worktree.create moved onto the launch executor, a create from
a linked issue (phone "create from issue", or a raw worktree.create with
startupDraft) could differ from before in two narrow cases. When the draft's
agent came from the settings default or from detection and its launch command
could not be built (for example unbalanced quotes in the saved agent
arguments), the workspace was recorded as created with that agent even though
it never started, and a background create opened no terminal at all instead
of its plain shell. When no agent was found, detection ran twice (an extra SSH
round trip on remote repos).

User-facing change: none intended; both cases now behave exactly as before
the executor move.

Mechanism:
- The entry hands the draft's chosen agent to the create as a main-internal
  startupDraftAgent, which steers only the draft; createdWithAgent stays what
  the caller sent. A draft that resolves no agent is created without the
  draft, so the create does not detect again.
- AgentLaunchExecution is a discriminated union: every normal launch must
  bring its surface factory, and a legacy-host launch must be terminal-only,
  bring a workspace factory, and cannot bring surfaces. The runtime guards
  the types replace are gone; the target kind is checked before any create.
- Legacy-host receipts follow the delivery module's rule: a blank prompt is
  no prompt, a submit folded into the launch command is handed-to-terminal,
  a post-start send the create does not await is unconfirmed (host-internal),
  a draft is not-delivered.
- The no-agent error lives with the legacy-host policy and is caught only by
  the shared entry.
- Tests: a real-runtime parity test (stored agent, terminal spawns,
  detection count vs the create alone, including the unbuildable plan),
  exact-argument checks over every non-agent startup field, compile-time
  checks for the union, and direct tests of the draft agent resolver.

* Say which creates still bypass the shared agent entry

Two comments stated that headless automations and federation already go
through createWorktreeWithStartupAgent; both still call
createManagedWorktree directly. Reword them as the plan. Comment-only.
2026-10-08 23:27:32 -07:00
Brennan Benson fc5eb16034 Send a headless automation's prompt to agents that read it after they start (#26703) 2026-10-08 23:26:51 -07:00
Brennan Benson 55d348d671 feat(native-chat): show an agent message's sender by its current title, clickable on the queued card too (#26357)
* feat(native-chat): name an agent's message by its current title, and open it from the queued card

The sender line on another agent's message and on its queued card now shows the
name Orca shows for that agent right now (its sidebar agent row or chat tab
name), falling back to the name recorded when it was sent. The queued card's
names open the sender, as the transcript row's already do.

* fix(orchestration): name a CLI agent sender by its tab's own name, not just its agent

A terminal agent's recorded sender name used only a manual tab rename, else the
agent type, so mail from CLI agents read as "Codex" or "Claude". The host now
names every sender the way Orca shows it: a chat by its rename or saved name, a
terminal agent by its sidebar agent row's name (rename, quick command, generated
or meaningful live title; a split pane only by its own title). The task title and
generic agent label are fallbacks. The renderer shows the live own name, else the
recorded one.

* fix(orchestration): preserve known sender names in message snapshots

* Preserve sender names across chat clears and terminal reuse

* fix: keep sender links on the recipient host across older runtimes

* test: type sender naming fixtures with their declared identities

* Fix sender ownership publication after clearing a chat

* Drop /clear sender lineage now that /clear keeps the same conversation

Main's #26579 keeps a native chat in the same session across /clear, so a
message's sender id still names the live chat. Remove the host's
orchestration-root projection, the optional status field, and the renderer's
lineage lookup and feed capability probe that only served it.
2026-10-08 23:25:08 -07:00
Brennan Benson d273d85606 fix(native-chat): underline a file path only once its workspace confirms it exists (#26675)
* fix(native-chat): underline a file path only once its workspace confirms it exists

Chat underlined any path-shaped text, so an example path an agent made up
(`src/app.ts`) looked clickable and a click said "File not found". The
terminal already checks a path on its host before underlining it; chat now
goes through that same code: the terminal's path finder, one shared
resolve-and-check step (terminal-file-link-target.ts), and the same
longest-match rule. The terminal's hover and click fallback use the shared
step too.

Fixes STA-9683

* fix(native-chat): recheck every shown file link when a turn ends or the host returns

Chat only forgot "missing" answers at turn end, so a file deleted or renamed
during a turn kept its underline, and a path the host could not answer
stayed plain until the message remounted.

Each answer now records the recheck generation it was asked in. A turn end,
or a reconnect / re-pair of the host answering for the workspace, starts a
new generation and re-asks only the paths shown by mounted messages; old
answers keep showing until the host replies, and only messages whose answer
changed re-render. A reply to an older question cannot overwrite a newer
answer. A host that cannot answer keeps the previous answer and is retried
three times with backoff, ending when no message shows the path or at the
next recheck. While an SSH workspace's connection is unresolved, paths are
treated as unknown instead of being checked on this machine, and the chat
starts over once the connection resolves.

* fix(native-chat): keep main's file-path shape rules and check existence on top

Chat detection goes back to main's rules: a candidate needs a folder plus a
rooted prefix, a line suffix or an extension, and prose spans are split into
tokens as before. Bare names and folders are not underlined, and ordinary
prose no longer sends word prefixes to the host. Existence is required on top,
so the underlined set only shrinks compared with main.

A rooted spaced span ("/Users/me/repo/src/x.ts to use src/app.ts") now also
offers its tokens, so both real paths link; a real spaced rooted path still
wins as the longest one that exists.

Tests: bare names and folders stay plain even when they exist, slash tokens
and versions stay plain when everything exists, `My Notes.md:7` is rejected by
shape, explicit links and fenced code never reach the existence check, plus
the rooted-then-relative and longest-span cases.

* fix(native-chat): catch up file links answered before subscribe; tighter recheck triggers

- A message renders (and asks) before it subscribes; an answer landing in
  between was never shown. Each message records the answer its render used,
  and on subscribe it re-renders if the answer differs and re-asks outdated
  paths. A failure for a path no message shows yet no longer arms a retry.
- A rooted path ending a sentence ("/Users/me/r/a.ts.") inside a longer span
  now links, with the punctuation kept outside the link. Unrooted spans are
  unchanged, matching main.
- The checker takes the workspace's SSH connection id and decides
  "connection not known yet" from it, so the provider memo needs no lint
  suppression.
- SSH reconnects recheck only when the connection reaches connected or gets
  a new generation, not on every intermediate status.

* fix(native-chat): never check a network share outside the workspace without a click

Chat checks every path in a finished reply on its own. On Windows, a local
check of a path like \\server\share\a.ts or //server/share/notes.md opens an
SMB connection to that server and sends the user's credentials, and agent
text can be steered by prompt injection. Such a path is now never asked
about and stays plain text, unless it is inside the workspace or is a known
workspace root. WSL paths (\\wsl$, \\wsl.localhost) and checks made by an SSH
or remote runtime host are unaffected. Terminal hover and click behaviour is
unchanged.

* chore: restore main's copy of structured-agent-session-queued-mutations.ts

The merge commit's format hook dropped one blank line in this unrelated file.
Committed without the hook so the file matches main byte for byte.

* refactor(native-chat): own the file-link checker in NativeChatView, not each chat view

The provider now sits where NativeChatView picks the chat view, and derives
the file-link context from the tab id. The two chat views keep only a
one-line turn-end hook, since only they know whether a turn is running.
Same lifetime as before: inside the session-keyed owner for structured
chats, and per resolution for terminal-backed ones. No behaviour change;
it keeps NativeChatResolvedView under its line limit after main's find-in-chat.

* fix(native-chat): refuse network shares on SSH hosts too; allow only the workspace's WSL distro

- The "no network share outside the workspace" rule now applies to SSH
  targets as well: a Windows SSH host's relay would otherwise stat
  //server/share/... and open SMB with no click. Remote runtime paths stay
  exempt, since they are always inside the worktree.
- WSL paths are allowed only for the workspace's own distro (from the pane
  runtime or the worktree path, via the same helper the path mapping uses),
  after `..` is normalized, so `//wsl.localhost/../x` or another distro is
  refused.
- Drop the \\?\UNC\ chat test case: the real detector never produces it.

* refactor(terminal): keep file-link host path helpers out of the open flow

terminal-file-link-target imported its pure path helpers from
terminal-file-open-routing, which also opens files and so loads worktree
activation, launch and the agent catalog. Since NativeChatView now hosts the
chat file-link checker, that chain reached tests that render NativeChatView
with a minimal i18n mock (native-chat-view-file-drop.test.tsx failed in CI).

getTerminalFileContext, mapTerminalFilePath, terminalLinkWslDistro and
terminalPathWslDistro move unchanged to terminal-file-path-mapping.ts;
terminal-file-open-routing imports and re-exports them, so its callers and
their mocks are unchanged.
2026-10-08 23:24:25 -07:00
Brennan Benson 4f126bc01a Show "Pi isn't signed in" in native chat before the first send (#26743)
* Show 'Pi isn't signed in' before the first send

A Pi chat whose agent started with no model to use now says so in the
existing availability notice, and the next send replaces that agent so
a sign-in made since takes effect.

* Re-read the sign-in notice when the chat's agent stops; ignore an exited Pi

A Pi's start verdict ends with that Pi: the notice now re-reads when the
host starts or stops the chat's agent, and a Pi whose process already
exited reports nothing.

* Keep one sign-in line after a refused send; re-read on every agent start or stop

The notice steps aside while the newest send was refused for the same
reason, and the chat re-reads its verdict whenever its agent starts or
stops, dropping an answer read before.

* Step the notice aside only while the refused send's own line is on screen

Uses the host's send order and the chat's rendered delivery lines, so a
deleted returned card or a trimmed row brings the sign-in notice back.

* Keep the sign-in notice when the refused message is no longer loaded
2026-10-08 23:24:00 -07:00
Neil f9d0aa2f67 Refresh open Markdown links when workspace files change (#26599)
* Refresh open Markdown document links after file changes

* Use the stable Markdown refresh callback in the watcher effect
2026-10-08 23:23:14 -07:00
Brennan Benson 6235f9a641 fix(acp): show the agent's own reason when a prompt fails (#26666)
Agents often answer a failed session/prompt with a generic JSON-RPC message ("Internal error") and keep the real reason in error.data. Only OMP and Grok had hooks that read it, so OpenCode and future ACP agents showed the bare message.

The prompt error detail now reads, for every ACP agent: the agent's hook, else string data, else data.details, else the message. Other structured data is metadata and is ignored. OMP's details hook and Grok's string-data branch are now covered by that rule and removed.
2026-10-08 23:22:55 -07:00
Brennan Benson 496a1712f2 fix(acp): keep OMP replies that follow its reasoning (#26658) 2026-10-08 23:22:27 -07:00
Neil d2d2d39cca Refresh rich Markdown links when document metadata changes (#26595)
* Refresh rich Markdown links when document metadata changes

* Type Markdown link storage for metadata refresh tests
2026-10-08 23:02:59 -07:00
Neilandm4air b79862dcc7 Describe unverifiable managed servers without claiming they stopped (#26663)
Co-authored-by: m4air <m4air@Mac.localdomain>
2026-10-08 23:01:35 -07:00
Neil 5264a7d65f Retire canceled managed startup checks before reconnecting (#26645) 2026-10-08 22:48:41 -07:00
Neil 45a969f83f Wait for retained editor ownership before listing Markdown documents (#26590) 2026-10-08 22:45:11 -07:00
Jinjing f3c0390d67 Fix Compact usage notice focus and older-host compatibility (#26747)
* Preserve usage-menu focus and polish the Compact notice

* Restart usage notice delay when selected notice changes
2026-10-08 22:38:31 -07:00
Neilandm4air 4e232f96c7 Preserve execution host identity throughout folder sidebar rows (#26634)
Folder rows used the bare folder ID for active state, DOM IDs, virtual keys
and activation, so same-ID folders on two hosts collided. Use the existing
host-qualified worktree identity, matching git worktree rows.

Co-authored-by: m4air <m4air@Mac.localdomain>
2026-10-08 22:34:43 -07:00
Neilandm4air 6a15cd8972 Preserve editor file identity during SSH host conversion (#26593)
* Preserve editor file identity during SSH host conversion

* Keep a dirty in-session editor tab on its file record during SSH conversion

A file opened over the relay in the current session carries route provenance,
so the no-host-evidence check repointed its tab to a fresh clean record and
hid the unsaved draft. Keep any retained record with no runtime owner instead.

---------

Co-authored-by: m4air <m4air@Mac.localdomain>
2026-10-08 22:18:51 -07:00
Neilandm4air b71e5382f6 Explain why stopping the selected Active Server is refused (#26584)
Co-authored-by: m4air <m4air@Mac.localdomain>
2026-10-08 21:47:10 -07:00
Neilandm4air 118de208a5 Keep open Remote Settings subscribed to the saved-server catalog (#26581)
Co-authored-by: m4air <m4air@Mac.localdomain>
2026-10-08 21:47:01 -07:00
Neil b9a7830036 Keep new browser tabs on SSH routing after managed conversion (#26570)
* Keep local browser routing on the deployment SSH host after conversion

* Seed published SSH connection state in browser lifecycle fixture
2026-10-08 21:30:46 -07:00
Neilandm4air 8167ddaa7f Distinguish remote browser failures from server connection failures (#26573)
* Distinguish remote browser service failures from server outages

* Use the shared browser_unavailable code in the stream failure classifier

---------

Co-authored-by: m4air <m4air@Mac.localdomain>
2026-10-08 21:07:48 -07:00
Brennan Benson 8d3c922176 refactor(tabs): reuse persistAgentLaunchTabOrder for copied tab-order logic (#26704)
Sleeping-session resume, AI vault resume and the plain quick-command
terminal each carried a line-for-line copy of persistAgentLaunchTabOrder.
Call the helper instead. No behavior change.
2026-10-08 21:04:21 -07:00
Brennan Benson 13c9f1fcc8 fix(floating-workspace): keep file links in their owning panel (#26655) 2026-10-08 21:03:03 -07:00
Brennan Benson 56cdd678fc fix(native-chat): read a queued message in full (#26004)
* fix(native-chat): read a queued card's whole text, and keep Edit from hiding it behind a prompt

A queued card showed one clipped line, so a long message (often another
agent's, never typed by the reader) could not be read before Steer or
Delete. A clipped card now opens in place to its whole text; the phone's
two-line card opens on tap.

Edit moved the card's text into the composer and deleted the card even
while a question or approval card stood in the composer's slot, so the
text vanished from the page. Edit now waits, saying why, until the
prompt is answered; the pane and the queue read one check for whether a
prompt holds the composer's slot.

* fix(native-chat): keep the full-text hover title on a folded queued card

* fix(native-chat): phone card opens only when it clips; test the composer-slot check

The phone card measured nothing and made every card a button; it now lays
the text out unseen at its width and offers Show more only past two lines.
The Edit reason is no longer dimmed twice, and the shared composer-slot
check and the desktop toggle's keyboard reach have their own tests.

* fix(mobile): the unseen measuring copy of a queued card takes no touches

* fix(native-chat): open a queued card's full text below its row, at the card's width

In a narrow chat the opened text sat beside the card's actions and wrapped
a few characters per line. It now opens under the row, indented to the
text, the way the transcript's awaiting-input row opens its question.

* fix(native-chat): the queued card's toggle names the opened text only while it exists

* refactor(native-chat): the queue reads pending prompts itself, keeping the session hook under its line cap

* refactor(native-chat): keep this PR to reading a queued message in full; Edit moves to in-place editing

* test(native-chat): queue list rig passes the pending-prompt flag main's controller takes
2026-10-08 20:54:59 -07:00
Neil 1cda323b6a Report browser placement support from the installed provider (#26559)
* Report browser placement capabilities from the installed provider

* Run managed browser capability checks in the template-building CI lane
2026-10-08 20:51:50 -07:00
Neilandm4air bdf0d66d7c Provide Orca CLI on managed Linux and macOS servers (#26539)
* fix(orcad): provide execution host CLI in Unix managed terminals

* fix(orcad): keep the server starting when the CLI launcher cannot be written

---------

Co-authored-by: m4air <m4air@Mac.localdomain>
2026-10-08 20:50:37 -07:00
Jinjing 04d2ccbfad Default status-bar usage to Compact and explain the change once (#26726)
* Default status bar usage to Compact

* Explain Compact usage to affected existing users once
2026-10-08 20:32:47 -07:00