An explicit Update, Rollback, Recover or Restart resumed the server's
status before invalidating its transport, so the first probe could ride
the connection about to be dropped. The action now includes its own
invalidation and note clearing, and status resumes after both. A test
also covers the SSH host's Disconnect being lifted for a managed server.
Refs #38347
* feat(mobile): set, pause, resume and clear a chat's goal from the phone
The phone sent /goal to the agent as plain text and showed no goal. It now
has desktop's goal mode (Goal chip, goal placeholder) and a goal strip on the
composer's top edge that expands to Pause/Resume and Clear goal.
The goal logic desktop and mobile share moves to src/shared: the goal report
(journal rows over the options answer), status labels, elapsed format, the
goal-mode pick rule and the goal row filter. agentSession.threadGoal joins the
phone allowlist behind agent-session.thread-goal.phone.v1, so older hosts keep
showing the goal without offering changes.
* fix(mobile): open each new goal's strip collapsed
* fix(mobile): keep goal sends' delivery choice and scope goal locks per chat
* fix(mobile): offer goal changes only when the paired desktop relays them
* test(mobile): pin which goal changes the phone offers per host
* refactor(mobile): reuse the queued box style and keep the goal strip stable per chunk
* fix(mobile): set phone goals through the composer's changeThreadGoal
Checks sidebar edit/delete of PR conversation comments always used desktop
IPC, so server-owned repos and SSH workspaces behind a paired server edited
and deleted with this computer's GitHub credentials while reads and posts
used the server's. Add one owner-aware client for slug-addressed GitHub
operations and move every slug caller onto it, so ownership is resolved at
the client boundary instead of in each component. An unknown owner now
fails explicitly instead of falling back to desktop credentials, and a
result that lands after the panel moved on no longer touches the list.
Refs STA-13254. Reported by Jinjing.
Co-authored-by: Claude <noreply@anthropic.com>
* fix(new-workspace): restore right-click "Set as default" on the agent picker
The shared agent picker (#35802) replaced the combobox in the new-workspace
composer and dropped the context menu that set the default agent. Wire
defaultAgent/onSetDefault through AgentLaunchPicker so agent rows, the
Blank Terminal row, and the closed field offer it again.
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(agents): keep the "Set as default" menu open when the mouse moves
The context menu was non-modal, so hovering the agent rows beneath it
moved focus to them and dismissed the menu. Make it modal so focus stays
in the menu until the user picks or dismisses.
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(agents): keep the row highlighted while its "Set as default" menu is open
Row highlight follows focus, which moves into the context menu. Mark the
trigger with data-context-menu-open and style it like the hovered row.
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(agents): address review on the Set as default context menu
- Keep the context menu non-modal; prevent focus-outside dismissal instead,
so combobox callers keep one-click selection and scrolling.
- Drop the field-level context menu: it clobbered the dropdown trigger's
data-state/data-slot, ignored the disabled gate, and doubled up on long-press.
- Pass onSetDefault through unchanged so memoized rows stay stable.
- Reset the open marker when the menu unmounts while open.
- Stop asserting the CSS-only highlight marker in tests.
Co-Authored-By: Claude <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
Repeated clicks on the native chat composer + (Attach file) button opened
one OS file picker per click because shell:pickAttachments opened a
parentless (modeless) dialog with no in-flight guard. Route all shell:pick*
and repos:pick* handlers through a single-flight wrapper; overlapping
requests resolve as canceled, which every caller treats as a no-op.
* feat(mobile): offer Sign in on a signed-out chat via a host terminal login
* feat(mobile): say which host is out in the chat composer, with Reconnect for the desktop link
* refactor(mobile): fit the host outage line beside main's chat changes
* perf(native-chat): stop rescanning whole chat history on every status update and subagent label
Status re-derived after every journal write built a map of every item body in the
chat to find conversation-command turns; index only command entries instead.
Same-named sibling subagents restarted their ordinal search at 2 for each sibling
(quadratic); resume each label's search where it stopped. Output is unchanged.
* perf(native-chat): find command turns through an indexed read on each status update
Status re-derived after every journal write still walked the whole history to
learn which turns belong to conversation commands. The host now passes a by-key
read of the same status rows (same display state, presentation membership and
stop-note projection, limited to the current /clear context), and the request,
preview and interruption readers check only the turns their backward scans reach,
the way the usage-limit reader already does. Clients without an index keep the
scan. A contract test drives real journal writes at 10x history and pins both the
constant indexed reads and the remaining per-row work.
* test(native-chat): count status body reads without Reflect.get or casts
* chore: drop accidental lockfile churn
* perf(native-chat): read the status display state once and pin indexed status parity
The host's status lookup now reads the journal's display state once per projection.
The status contract test covers the /clear boundary, an interruption verdict behind
a skipped command turn, a revised command turn, and the journal's per-row render
budget; the context-rows test expects the returned /clear sequence.
* test(native-chat): bound status work on the write that records an Orca stop
* test(native-chat): status follows a turn that becomes a command turn
* fix(native-chat): take the status lookup from the status read itself
Status read the journal's display state directly, which journals without queued
messages (test stand-ins, older wiring) do not have. The status read now returns
the by-key lookup bound to the state it rendered; readers whose status read has
none keep the scan.
* Add collapse and expand all projects controls
Support a sidebar button, keyboard shortcut, and Alt-click on project
headers, with a contextual tip to introduce the feature.
* rm mockup file
* Clean up project header toggle handling and fixtures
* feat(mobile): continue an interrupted chat from the phone
Desktop offers Continue on a turn Orca's own stop cut off; the phone only
showed the words. The offer and failure rules move to
src/shared/native-chat/native-chat-interrupted-continue.ts, desktop uses
them unchanged, and the phone drives them from its session over
agentSession.continueInterrupted when the host advertises it.
* refactor(native-chat): type the Continue answer instead of casting it
* fix(mobile): let a phone call agentSession.continueInterrupted
The host refused the method from phones, so Continue on the phone always
failed. Allow it on the execution-host route and advertise
agent-session.continue-interrupted.phone.v1, which the phone gates on:
hosts that advertise only continue-interrupted.v1 still refuse phones.
* fix(mobile): hide Continue when an older paired desktop relays the chat
A newer page on a server routed through an older desktop would offer
Continue that the desktop refuses to forward. Gate it on the relaying
desktop too; the workspace server context moves to a react-native-free
module so session hooks can read it. Long failure text now shrinks so
the banner's dismiss stays on screen.
* refactor(mobile): reuse the chat's Try Again look and shared Continue wording
The Continue button takes the Try Again pill styles and common.continue,
the shared helper takes the RPC's answer instead of a call wrapper, and
the phone hook memoizes its result and keys late answers in one place.
* fix(i18n): keep the Continue failure copy inline so catalogs rebuild it
* refactor(mobile): keep the structured session hook under its line cap
* fix(native-chat): check the session's agent audience before continuing a turn
* Add child worktree creation to the context menu
Preselect the clicked parent and its owning host when opening the
workspace composer, including nested SSH worktrees.
* Fix child worktree creation across local and remote hosts
Chat links used the body text color, so a link only showed as a faint
underline and a theme's textLink.foreground had no effect. They now
paint --chat-link: the theme's link color, else the built-in note blue
for the chat's own scheme, mixed toward the chat foreground like the
other chat hues. Code and bold link labels follow the link color.
When the chat paints a terminal palette the app theme doesn't match,
the app theme's link color is cleared, as its bubble already is. The
theme contrast check now measures the chat link on the theme's user
bubble and drops only the bubble when it fails.
* refactor(native-chat): move chat link rules and the file-existence checker to shared
GitHub reference linking, the reference-repo rule, chat file-path linkification,
the per-chat existence checker and the path-existence batcher are platform-free.
Move them to src/shared so the phone's chat can use the same rules; desktop keeps
its host adapters and behaves the same.
* feat(mobile): link #123 refs, check file paths and wrap code in chat
* test(mobile): pin which chat paths and refs become links
* refactor(mobile): parse repo link fields instead of casting
* refactor(mobile): own chat link state in the chat overlay
* fix(mobile): keep unchecked chat paths as precise as before
* fix(mobile): only peek at known paths while reasoning streams
* test(mobile): leave #123 splitting rules to the shared transform's tests
* refactor(native-chat): tidy shared link helpers and skip separator-free text
* test(native-chat): import the existence type from its shared home
* style(mobile): fold the code-span link handler onto one line
* feat(chat): resume working chats after restart by default for new users
New profiles start with automatic resume on. Profiles saved before this
change that never chose keep it off, so existing users see no change.
* fix(chat): decide launch resume only after settings load
The launch reads the resume preference once. When held chats enabled it
before settings loaded, it read the preference as off and opened the
review dialog for the session. Also drop the web storage pin: the web
client never runs the launch resume.
* feat(mobile): paste images and long text into the chat composer
* test(mobile): pin which composer pastes attach
* fix(mobile): keep a long paste as text when no upload can start
* fix(mobile): re-encode pasted images agents can't read as PNG
* test(mobile): pin composer paste routing at the composer field
* refactor(mobile): fold composer paste routing into its hook
* fix(mobile): read pasted images in the uploader so the composer needs no shell bridge
* test(mobile): stub the context ring in the composer paste test
* feat(mobile): show the context usage ring in the chat composer
Desktop's ring reads the structured session's context facts; the phone
never read the host's contextUsage answer. The summary rules move to
src/shared so both read the same numbers, and the title card's anchored
card machinery is generalized so the ring's breakdown opens above the
composer without dropping the keyboard.
* refactor(native-chat): share the context ring's critical threshold
* test(mobile): stub the context ring where composer tests stub its siblings
* fix(mobile): keep the context card with the composer and the keyboard up
Touching the card no longer takes focus from the draft, the open card
moves with the composer as the keyboard or draft resizes it, and the
composer's own menu closes it.
* fix(mobile): close the context card when the composer hides, and let long titles wrap
* refactor(mobile): share the anchored card layer and the ring readout
* test(e2e): pass the new options arg in the permission publication test
* feat(native-chat): show the chat account usage for /usage
* fix(native-chat): derive usage card lifetime from its owning turn
* test(native-chat): keep runtime manifest coverage additive
* fix(native-chat): advertise usage from the host options reader
* fix(native-chat): read cold chat usage through the chat's own Codex launch
- Cold /usage starts a short-lived app-server the way the chat launches
(configured command, args, environment, pinned home), as Codex rename does;
the status-bar probe returns to its original path.
- Report each reset outcome inline after the card refreshes.
- Send the chat's account as a typed kind plus email; the client owns copy.
- API-key accounts are recognised by a typed error, not message text.
- Keep the caret after kept text; drop unreachable WSL matching.
* refactor(native-chat): share the at-rest Codex app-server launch between rename and usage
* fix(native-chat): keep a reset's outcome when the usage refresh fails; label accounts by kind
* fix(native-chat): drop stale usage replies by request id instead of a render-time ref
* test(native-chat): keep the usage card test inside the web project
* fix(native-chat): clear the usage card when a waiting prompt changes; retire its callbacks on unmount
* chore: drop accidental pnpm lockfile churn from the main merge
* fix(native-chat): put the caret after kept text on /usage; offer the reset once the host's first Codex read lands
* fix(native-chat): type the usage snapshot wait and its test state
* fix(native-chat): re-read the usage offer when the chat host's Codex state changes, instead of forcing a host read
* fix(native-chat): watch the usage host from the card's opening and keep one background re-read at a time
* fix(native-chat): bind the usage card's background re-read to its opening
* feat(native-chat): open /usage as a modal like /resume
The chat's Codex usage card now opens in a root-hosted dialog with the
same surface, width and dismissal as /resume's Session History, instead of
a full-width card docked above the composer. Closing the modal retires its
reads; docked-only clearing on turn, prompt and session changes is gone.
* fix(native-chat): give the /usage modal /resume's fixed height and drop a replayed mount's first read
* fix(native-chat): size the /usage modal to its card and keep the card's layout while loading
* fix(native-chat): pin the /usage modal's top where /resume's sits so loading never moves its title
* chore(native-chat): describe the /usage loading placeholder accurately
* feat(mobile): dock the chat's plan above the composer
Desktop docks the latest plan above the composer; the phone only drew it
inline in the transcript. Move desktop's docked-plan rule to src/shared and
draw it on the phone as a collapsible strip in the background-tasks box.
* perf(native-chat): keep the docked plan's identity until the plan changes
* refactor(native-chat): read the docked plan from the newest message back
* refactor(mobile): drop a cast in the docked plan's open memory
* test(mobile): pass the tray's new taskList in the recovery test
* feat(mobile): name and link agent senders on queued chat messages
Queued cards and the queued-message editor drew the sender from the saved snapshot name as plain text. They now share the transcript card's sender row, so a queued message names its sender's chat by its current title and taps through to it, as desktop does.
* fix(mobile): keep the queued sender line on one row
* refactor(mobile): tidy the queued sender row and editor heading
* fix(mobile): bound the queued sender row to its container width
Cmd+J and the new-tab search built open-tab rows only for a hand-kept
list of tab kinds, which never included native chats. Workspace tab
kinds are now every kind except browser and simulator (which have their
own rows). Chat rows use the tab strip's chat name and provider icon,
and selecting one activates it the way clicking its tab does, including
dismissing a paired-device page placeholder over it. The new-tab search
reads only its own workspace's chat names.
Tracked in STA-13638.
* fix(tabs): show the agent session's name on terminal tabs
The tab strip rebuilt each terminal tab without its session title and then re-ranked it, so the first-prompt title won over the session's own name. Carry the session title through the projection, and give the tab strip, unified tab label and sidebar agent row one shared name order so they cannot drift.
Co-authored-by: AJIADb9 <ajiadb9@bgd.team>
* fix(tabs): paired desktops take the host's session name for mirrored terminal tabs
The host projection now forwards the window's session title to paired clients, and the client's rebuild adopts it (older hosts omit it, so the client keeps its own). Without it a mirrored tab re-ranked to the client's first-prompt title.
* fix(tabs): one writer per session for a mirrored tab's session name
The paired client's own title sync keeps a session it already tracks current; the host's name only fills a session the client doesn't know, so a stale host snapshot can't revert a rename the client already read.
* fix(tabs): a paired host is the only writer of a session name it publishes
* fix(tabs): only a host window that resolves session names speaks for them
* fix(tabs): derive session title authority from live publishers
* fix(tabs): keep the session title publisher check concrete for runtime subclasses
* fix(tabs): one publication when a closing window drops browsers and title authority
* test(tabs): follow main's runtime test helper layout
* fix(tabs): paired desktops keep a quick-command name ahead of the session name
* fix(tabs): a name the host cleared doesn't come back as the idle pane's title
* test(tabs): build the idle-pane fixture as its own surface variant
* fix(tabs): refresh live session names from their authoritative source
* fix(tabs): preserve title recovery and bound refresh retries
* fix(tabs): preserve Codex index names at EOF
* fix(tabs): track the observed Codex index end
* fix(tabs): point the moved session-title projection at its new shared path
---------
Co-authored-by: AJIADb9 <ajiadb9@bgd.team>
* feat(mobile): show background task rows in the chat transcript
* fix(mobile): truncate the task output path like desktop
* fix(mobile): scale and select task rows like the rest of the transcript
* fix(mobile): translate the task output line
* refactor(mobile): read task rows from the shared row content
* test(mobile): restore the text reader for task row tests
* fix(native-chat): show defaults for chats that have never started
* fix(native-chat): share default projection across controls and picks
* test(native-chat): prove controls recover from cached discovery
* test(native-chat): type deferred catalog as its wire result
* test(native-chat): preserve the catalog origin literal
* test(native-chat): remove duplicate RPC work-count cases
* Show source scan notices accurately in mobile Agent History
Reuse the existing desktop presentation policy in shared code, keep host failures and source notices separate from skipped transcript files, and retain their scanner-authored messages.
Adapted from the remaining mobile presentation in original PR #15671. Rebased onto main: the classifier now lives in src/shared/ai-vault/scanning/, desktop keeps its translated skip reasons, and mobile reuses its existing skipped-transcript notice.
Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com>
* refactor(ai-vault): share the scan issue row key between desktop and mobile
---------
Co-authored-by: Neil <neil@stably.ai>
Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com>
* fix(tabs): show the agent session's name on terminal tabs
The tab strip rebuilt each terminal tab without its session title and then re-ranked it, so the first-prompt title won over the session's own name. Carry the session title through the projection, and give the tab strip, unified tab label and sidebar agent row one shared name order so they cannot drift.
Co-authored-by: AJIADb9 <ajiadb9@bgd.team>
* fix(tabs): paired desktops take the host's session name for mirrored terminal tabs
The host projection now forwards the window's session title to paired clients, and the client's rebuild adopts it (older hosts omit it, so the client keeps its own). Without it a mirrored tab re-ranked to the client's first-prompt title.
* fix(tabs): one writer per session for a mirrored tab's session name
The paired client's own title sync keeps a session it already tracks current; the host's name only fills a session the client doesn't know, so a stale host snapshot can't revert a rename the client already read.
* fix(tabs): a paired host is the only writer of a session name it publishes
* fix(tabs): only a host window that resolves session names speaks for them
* fix(tabs): derive session title authority from live publishers
* fix(tabs): keep the session title publisher check concrete for runtime subclasses
* fix(tabs): one publication when a closing window drops browsers and title authority
* test(tabs): follow main's runtime test helper layout
* fix(tabs): paired desktops keep a quick-command name ahead of the session name
* fix(tabs): a name the host cleared doesn't come back as the idle pane's title
* test(tabs): build the idle-pane fixture as its own surface variant
* fix(tabs): point the moved session-title projection at its new shared path
---------
Co-authored-by: AJIADb9 <ajiadb9@bgd.team>
* Show background completion mail as its command source
* Use minted sender handles in identity regression fixtures
* Verify command source labels in translated chat views
* Preserve command-only queued source regression coverage
* Use a minted terminal identity in host sender coverage
* Reuse command sender labels across unchanged message and queue reads
* Keep sender performance contract within the shared typecheck boundary
* Keep command reports in the background surface and preserve observed sender identity
* Use the shipped phone background report translation namespace
* Settle the prior dispatch before testing terminal reuse
* Keep live worker status tied to its recorded agent invocation
* Withdraw worker invocation proof when identity refresh fails
* Drop unproved worker associations at the failed refresh boundary
* Cover ordinary and remote sender rows during failed refresh
* Assert preserved session and peer content on failed sender reads
* Align sender navigation and relay tests with observed provenance
* Keep background report sources neutral and bound sender snapshot caches
* Construct readonly phone report fixture without mutation
* Keep callback-free federated transport assertions exact
* Keep orchestration sender identity separate from endpoint activity
* Validate mailbox receipt fixture shapes without casts
* Keep current Crew endpoint status separate from saved message activity
* Keep sender activity fixtures within lint contracts
* Avoid repeated serialization for fitting relay pages
* Use the valid workspace sidebar value in scale coverage
* Keep operation-count SQL expectation in sync with main
* fix(native-chat): make file links in terminal-backed plan approvals actionable
Each chat view now provides its link handler through one per-chat
context that link-drawing components read themselves, replacing the
onLinkClick/allowFileUriLinks props threaded by hand through rows and
cards. The terminal-backed approval card was the surface that never got
them; any card inside a chat view now gets links without wiring.
Tracked in STA-13722.
* native-chat: correct link context comment
* native-chat: fixture for a terminal-backed plan approval with file links
* native-chat: drop stray blank line in turn-diff rollup
* fix(agents): tell Codex subagent rows apart by task name instead of role
A Codex subagent's task name only reached the sidebar when one early read of its rollout succeeded; otherwise every child fell back to its role, so two workers both read "worker". The child poll now re-reads an unnamed child's own rollout, the first-line read allows the existing 256 KiB line cap, and a child with no task name or nickname carries the tail of its id, as Codex's own agent list does. Labels keep the path below the root so nested children with the same leaf stay distinct.
* fix(agents): retry an unnamed Codex child's own rollout without a parent transcript
A relay restart can leave a child hook as the only evidence, sent before the child's rollout was readable. Keep that path as pending identity work and retry it through the existing transcript poll, which now stays scheduled while a child still owes its name.
* fix(agents): carry the id tail to every child-row surface and bound identity retries
- Full-mode sidebar rows and dashboard subagent cards use the same suffixed label as compact rows.
- A role a host publishes as the name (Claude's subagent_type) no longer counts as a name.
- A final identity miss no longer re-arms polling; retries take turns, eight children per poll.
- Replace an error cast with a type guard.
* fix(agents): keep every identity read inside the per-poll budget
The parent-rollout poll now queues an unnamed child for the bounded, rotating identity retries instead of reading it directly, and full-mode rows keep the id tail for a child with no name or role.
* fix(agents): keep plain names on task-only rosters and guard the queued rollout path
A host that publishes only a task roster carries no role, so its names can't be told from roles and get no id tail. Update the sidebar lineage test for a role-only child's id tail.
* fix(agents): tell role-named tasks apart on task-only rosters
A host that publishes only a task roster sends Claude's subagent_type as the task name, so two such tasks read identically. A named agent task with no description now carries its id tail; an unnamed one keeps its kind label.
* fix(agents): a task path found meanwhile wins over a queued header read
Placeholder labels (e.g. unknown) count as unnamed when queueing, and a child the parent rollout named before its turn drops its pending read instead of being overwritten by the header's nickname.