Commit Graph
168 Commits
Author SHA1 Message Date
Brennan Benson 3aec926fe5 Chat renames are saved as the chat's name, so Session History shows them after the tab closes (#31602)
* Chat renames are saved as the chat's name, so Session History shows them after the tab closes

Renaming a native chat tab only changed that window's tab. Session History, phones and other windows read the host's saved name, so the rename vanished from History once the tab closed or Orca restarted.

A rename now goes to the host that owns the chat (agentSession.rename) and is saved as the chat's one name, the field History, tabs, sidebars and phones already read. The record remembers that the user chose it, so automatic naming never replaces it. A window's pre-upgrade tab rename is uploaded once and only fills a name Orca chose. Hosts without the new capability keep the old tab-only rename. Every native agent's History row now shows the chat's name once it has one.

* Keep saved chat names consistent across readers and reconnects

* Carry qualified chat names into cached readers

* Reuse the history recorder context across platforms

* Use recorded chat ownership for legacy name transfer

* Transfer legacy chat names only from known catalog ownership

* Correct recorder path matching and History subscription order

* Require complete evidence for bridged parity exclusions

* Preserve sparse array lengths in complete recording comparisons

* Initialize History observation before loading workspace context

* Integrate measured History recordings and scoped parity evidence

* fix chat naming retries and certify both replay modes

* fix ambiguous chat rename labels after navigation

* preserve chat rename intent across tab updates

* type dashboard snapshot fixtures without assertions

* fix: align chat name observation with store contracts

* test: update chat name consumer fixtures

* test: distinguish sender naming metadata from status

* Preserve saved Activity chat names across tab focus

* Clarify Activity tab projection identity

* Remove package-manager lockfile churn from main merge

* fix(runtime): validate tab property edits before saving chat names

* fix: refresh History ownership after conversation clear

* fix: commit History request identity before refresh effects

* Fix History scan scope typing and integration test placement

* test: cover saved chat name reader contracts

* test: make chat naming fixtures deterministic
2026-10-25 16:20:29 -07:00
Brennan Benson 883b768bc7 A chat never starts writing a conversation a terminal is still writing, and restored panes respect open chats (#30930)
* fix(native-chat): one ownership index with link roles, and a chat refuses to start while a terminal writes its conversation

Each chat link now carries its role (head or earlier) and key; Session History names
the chat continuing a conversation first, duplicate adoption checks only head links in
both the pre-commit check and the store transaction, and terminal resumes stay refused
exactly as before. The terminal-writer check now also runs inside acquireOwner before
the spawn, refusing with the typed pre-spawn reason conversationOpenInTerminal.

* fix(native-chat): hold a terminal start Orca issues until its terminal reports it

An in-memory registry of managed terminal starts per conversation, registered in the
same step as the resume admission (terminal.send with a resume command, createTerminal
with a command), bound to the PTY execution that runs it, and ended only when that
terminal's hooks report the conversation or the execution is gone. A chat's agent
start refuses while an entry may hold its conversation.

* fix(terminal): an automatic pane restore never resumes a conversation an open chat continues

Resume recipes built from a pane's saved facts (cold restore, deferred and late reattach,
sleeping wake, host mobile materialization) carry resumeOrigin 'saved'. The execution host
decides before any command is issued: while an open chat continues the conversation, or a
chat may be writing it, the pane starts its plain shell with the started-fresh notice and
its saved claim is forgotten; with unreadable records only a saved restore is refused and
its record kept. An admitted resume registers its managed start in the same step, so the
chat's agent start refuses until the terminal reports it or exits. Clearing the last saved
record now reaches disk.

* Fix saved terminal resume ownership and effective launch results

* Preserve conversation case and clear refused cold restore identity

* fix: publish effective terminal launch results on attachment

* fix: preserve terminal launch callback and candidate ownership

* fix: keep existing terminal attachments observational

* fix: consolidate terminal launch imports

* test: document snapshot runtime fixture boundary

* fix: preserve terminal launch identity types

* fix: accept reattach payload policy sessions

* fix: preserve terminal result validation contracts

* refactor: separate terminal create contract

* test: document partial terminal runtime fixtures

* test: align sleeping resume store fixture

* Fix terminal create type contracts

* fix: return the terminal main started to its waiting view

* fix: register reattached terminals before publishing their handles

* Check unavailable-resume test fixture types

* Complete sidebar resume test store fixture

* test: bound saved resume ownership and refusal work

* Settle refused saved resume claims from actual launch results

* Fix saved resume test type contracts

* test(e2e): capture exact terminal parking diagnostics

* test: verify parked terminal with fresh host inventory

* Consolidate agent session imports in remote terminal transport

* Clarify terminal conversation refusal guidance

* Admit note-only saved conversations without hiding live writers

* Retain terminal writer proofs through final admission
2026-10-25 14:39:05 -07:00
OrcaWinandOrcaWin 7d6603aa17 [comment audit] Complete repository-wide comment cleanup (#33249)
* [comment audit] Align legacy failure-read counts with cached display

* [comment audit] Condense shared provider lifecycle and browser recovery comments [skip ci]

* [comment audit] Condense shared generation, path and preview comments [skip ci]

* [comment audit] Condense host, file and Git contract rationale [skip ci]

* [comment audit] Tighten Git lifecycle and provider contract comments [skip ci]

* [comment audit] Clarify shared host authority and launch isolation [skip ci]

* [comment audit] simplify local access mobile and native chat contracts [skip ci]

* [comment audit] simplify native chat presentation and state contracts [skip ci]

* [comment audit] simplify native runtime and provider contracts [skip ci]

* [comment audit] simplify migration and orchestration contracts [skip ci]

* [comment audit] simplify plugin trust and process capture rationale [skip ci]

* [comment audit] simplify terminal delivery and remote compatibility rationale [skip ci]

* [comment audit] clarify remote recovery and launch wire rationale [skip ci]

* [comment audit] shorten shared RPC compatibility and runtime evidence comments [skip ci]

* [comment audit] simplify secure storage shell startup and sidebar rationale [skip ci]

* [comment audit] clarify sidebar, skill and SSH invariants [skip ci]

* [comment audit] condense structured chat delivery and timing rationale [skip ci]

* [comment audit] condense terminal replay and telemetry invariants [skip ci]

* [comment audit] clarify launch permission and terminal delivery rationale [skip ci]

* [comment audit] clarify workspace ownership and layout contracts [skip ci]

* [comment audit] wait for fitted and painted SSH terminal links

* [comment audit] clarify layout persistence and session ownership [skip ci]

* [comment audit] clarify host routing and workspace lifecycle contracts [skip ci]

* [comment audit] clarify startup persistence and app lifetime comments [skip ci]

* [comment audit] clarify recovery and activity boundaries [skip ci]

* [comment audit] condense activity and artifact invariants [skip ci]

* [comment audit] condense automation authority contracts [skip ci]

* [comment audit] simplify automation scheduling and history contracts [skip ci]

* [comment audit] clarify browser chrome and annotation lifecycles [skip ci]

* [comment audit] clarify browser routing and retained guest ownership [skip ci]

* [comment audit] clarify browser recovery and document ownership comments [skip ci]

* [comment audit] restore hosted runners required by native IME fixtures

* [comment audit] shorten preview, palette and dashboard invariants [skip ci]

* [comment audit] clarify terminal preview, dashboard and retention comments [skip ci]

* [comment audit] clarify diff-note recovery and editor lifecycle comments [skip ci]

* [comment audit] shorten rich-text and combined-diff ownership comments [skip ci]

* [comment audit] shorten autosave, draft recovery and model ownership comments [skip ci]

* [comment audit] shorten save-queue, notebook and Markdown constraints [skip ci]

* [comment audit] shorten Monaco, PDF and rich Markdown safeguards [skip ci]

* [comment audit] clarify rich Markdown source and selection safeguards [skip ci]

* [comment audit] clarify editor loading and emulator stream lifetimes [skip ci]

* [comment audit] trim emulator and feature tour narration [skip ci]

* [comment audit] clarify floating workspace and GitHub cache ownership [skip ci]

* [comment audit] trim provider controls and Linear filter explanations [skip ci]

* [comment audit] clarify pairing and chat resume lifetimes [skip ci]

* [comment audit] clarify chat delivery and transcript lifetimes [skip ci]

* [comment audit] clarify session options and host-owned status [skip ci]

* [comment audit] clarify draft durability and live-status precedence [skip ci]

* [comment audit] Trim chat send and model ownership explanations [skip ci]

* [comment audit] Shorten transcript ownership and send recovery comments [skip ci]

* [comment audit] Simplify native chat composer and transcript hook comments [skip ci]

* [comment audit] Shorten native chat scroll and structured session explanations [skip ci]

* [comment audit] Trim workspace picker and onboarding commentary [skip ci]

* [comment audit] Simplify pet rendering, review cache and quick open comments [skip ci]

* [comment audit] Trim sidebar and session history explanations [skip ci]

* [comment audit] Condense session resume and review checks comments [skip ci]

* [comment audit] Shorten checks state and file explorer refresh comments [skip ci]

* [comment audit] Trim plugin bridge and source-control action rationale [skip ci]

* [comment audit] Trim source-control listing and review rationale [skip ci]

* [comment audit] Shorten sidebar lifecycle and explorer safeguards [skip ci]

* [comment audit] Condense Settings account and appearance rationale [skip ci]

* [comment audit] Trim pairing and repository Settings explanations [skip ci]

* [comment audit] Condense Settings search SSH and terminal safeguards [skip ci]

* [comment audit] Shorten Settings voice and provider ownership comments [skip ci]

* [comment audit] Condense Settings draft persistence and host-state rationale [skip ci]

* [comment audit] Trim Settings hooks and sidebar setup rationale [skip ci]

* [comment audit] Condense sidebar deletion and board-filter safeguards [skip ci]

* [comment audit] Condense worktree cards, host ownership and Markdown contracts [skip ci]

* [comment audit] Condense sidebar drag, folder and filtering contracts [skip ci]

* [comment audit] Condense sidebar parity, sleep and host ownership contracts [skip ci]

* [comment audit] Prune focus-read baseline after comment cleanup

* [comment audit] Clarify sidebar visibility, board drops and agent evidence [skip ci]

* [comment audit] Tighten sidebar listing, drag and reveal contracts [skip ci]

* [comment audit] Clarify viewport geometry and skill update safeguards [skip ci]

* [comment audit] Tighten skill inventory, usage and host status comments [skip ci]

* [comment audit] Clarify usage ownership and resource kill safeguards [skip ci]

* [comment audit] Tighten tab selection, rename and search contracts [skip ci]

* [comment audit] Tighten tab-strip geometry and drag ownership comments [skip ci]

* [comment audit] Tighten Tasks recovery, pagination and mutation comments [skip ci]

* [comment audit] Tighten provider loading and terminal recovery comments [skip ci]

* [comment audit] Tighten completion, cadence and PTY ownership comments [skip ci]

* [comment audit] Clarify terminal restore and parked-pane boundaries [skip ci]

* [comment audit] Tighten PTY startup and replay explanations [skip ci]

* [comment audit] Clarify PTY identity and delivery ownership [skip ci]

* [comment audit] Shorten remote reconnect and input replay rationale [skip ci]

* [comment audit] Condense terminal parking clipboard and file link comments [skip ci]

* [comment audit] Clarify IME ownership and trim trace history [skip ci]

* [comment audit] Shorten pane recovery and parked watcher explanations [skip ci]

* [comment audit] Condense snapshot replay and visibility recovery rationale [skip ci]

* [comment audit] Shorten terminal parking and keyboard ownership rationale [skip ci]

* [comment audit] Trim renderer lifecycle and menu rationale [skip ci]

* [comment audit] Condense workspace cleanup ownership and settlement notes [skip ci]

* [comment audit] Clarify renderer hook evidence and source ownership [skip ci]

* [comment audit] Tighten IPC ownership and reconnect explanations [skip ci]

* [comment audit] Trim renderer focus, paste and attention comments [skip ci]

* [comment audit] Shorten scroll, locale and agent lifecycle comments [skip ci]

* [comment audit] Trim agent launch, resume and ownership commentary [skip ci]

* [comment audit] Shorten account, browser and editor comments [skip ci]

* [comment audit] Clarify host-mirror waits, previews and input ownership [skip ci]

* [comment audit] Condense launch recovery and editor tokenization comments [skip ci]

* [comment audit] Condense workspace selection and terminal pane rationale [skip ci]

* [comment audit] Condense terminal fitting and rendering recovery comments [skip ci]

* [comment audit] Condense terminal input and host routing explanations [skip ci]

* [comment audit] Condense ownership and sleeping-session recovery rationale [skip ci]

* [comment audit] Condense structured launch and terminal link explanations [skip ci]

* [comment audit] Condense terminal ownership and workspace file routing rationale [skip ci]

* [comment audit] Condense session persistence and workspace activation rationale [skip ci]

* [comment audit] Condense workspace routing and browser ownership rationale [skip ci]

* [comment audit] Condense mirror recovery and layout authority rationale [skip ci]

* [comment audit] Condense runtime transport and file safety rationale [skip ci]

* [comment audit] Condense runtime graph and structured session rationale [skip ci]

* [comment audit] Condense browser creation and remote tab recovery rationale [skip ci]

* [comment audit] Condense snapshot authority and publisher recovery rationale [skip ci]

* [comment audit] Condense frame, startup and provider cache rationale [skip ci]

* [comment audit] Condense host twin and agent lifecycle rationale [skip ci]

* [comment audit] Condense browser authority and editor cleanup rationale [skip ci]

* [comment audit] Condense editor buffer identity and review cache rationale [skip ci]

* [comment audit] Condense provider lifetime and repository ownership rationale [skip ci]

* [comment audit] Condense reconnect, hydration and terminal retirement rationale [skip ci]

* [comment audit] Condense recovery, reconciliation and persistence rationale [skip ci]

* [comment audit] Condense cleanup ownership and listing admission rationale [skip ci]

* [comment audit] Condense teardown ordering and terminal ownership rationale [skip ci]

* [comment audit] Condense recovery ledger and web host-boundary rationale [skip ci]

* [comment audit] Condense web reconnect and credential lifecycle rationale [skip ci]

* [comment audit] Condense cloud rollout and assignment safety rationale [skip ci]

* [comment audit] Condense relay session and database recovery rationale [skip ci]

* [comment audit] Condense relay placement and protocol compatibility rationale [skip ci]

* [comment audit] Condense build and packaging constraints [skip ci]

* [comment audit] Condense CI gates and validation isolation rationale [skip ci]

* [comment audit] Condense catalog identity and browser validation rationale [skip ci]

* [comment audit] Condense mobile browser and frame-budget rationale [skip ci]

* [comment audit] Shorten mobile route and browser test explanations [skip ci]

* [comment audit] Condense editor and session seam test rationale [skip ci]

* [comment audit] Trim session and terminal closure history [skip ci]

* [comment audit] Shorten terminal browser and census explanations [skip ci]

* [comment audit] Preserve root-only E2E diagnostic baseline

* [comment audit] Condense mobile seam and HTML preview test rationale [skip ci]

* [comment audit] Condense preview instrumentation and render harness rationale [skip ci]

* [comment audit] Condense bundle contracts and native-build constraints [skip ci]

* [comment audit] Shorten native packaging and CI routing rationale [skip ci]

* [comment audit] Condense release tooling and benchmark constraints [skip ci]

* [comment audit] Condense packaging and platform gate rationale [skip ci]

* [comment audit] Shorten mobile route and history rationale [skip ci]

* [comment audit] Condense mobile browser and component rationale [skip ci]

* [comment audit] Shorten drawer and workspace-form rationale [skip ci]

* [comment audit] Trim mobile review and editor-document explanations [skip ci]

* [comment audit] Condense editor lifecycle and Markdown explanations [skip ci]

* [comment audit] Trim mobile file, diagnostics and dictation commentary [skip ci]

* [comment audit] Simplify mobile host-list and workspace-view explanations [skip ci]

* [comment audit] Clarify mobile shell lifetime and bridge delivery constraints [skip ci]

* [comment audit] Condense bridge stream, native request and audio rationale [skip ci]

* [comment audit] Reduce bridge schema and page-client commentary [skip ci]

* [comment audit] Tighten mobile page bridge contracts [skip ci]

* [comment audit] Clarify mobile cache and session constraints [skip ci]

* [comment audit] Condense mobile route and lifecycle rationale [skip ci]

* [comment audit] Tighten mobile shell hook timing rationale [skip ci]

* [comment audit] Clarify route handoff and notification compatibility [skip ci]

* [comment audit] Clarify push registration and dictation cleanup constraints [skip ci]

* [comment audit] Condense keyboard geometry and native media ownership rationale [skip ci]

* [comment audit] Tighten mobile chat scroll and composer concurrency rationale [skip ci]

* [comment audit] Condense mobile reply compatibility and upload ownership rationale [skip ci]

* [comment audit] Condense optimistic echo and scroll ownership rationale [skip ci]

* [comment audit] Tighten stream replay and queued session ownership rationale [skip ci]

* [comment audit] Condense mobile composer and reply-schema constraints [skip ci]

* [comment audit] Clarify mobile transcript recovery and session ownership [skip ci]

* [comment audit] Clarify mobile queue and source-control compatibility [skip ci]

* [comment audit] Condense storage and task launch constraints [skip ci]

* [comment audit] Clarify task schemas and terminal document ownership [skip ci]

* [comment audit] Tighten terminal input and viewport rationale [skip ci]

* [comment audit] Shorten terminal and RPC recorder contracts [skip ci]

* [comment audit] Condense recorder engine and connection contracts [skip ci]

* [comment audit] Shorten host credential and relay recovery comments [skip ci]

* [comment audit] Tighten pairing recovery and bundle transport rationale [skip ci]

* [comment audit] Clarify RPC delivery and workspace catalog boundaries [skip ci]

* [comment audit] Condense catalog and relay entry contracts [skip ci]

* [comment audit] Shorten shutdown and SSH relay delivery contracts [skip ci]

* [comment audit] Condense WSL relay and E2E regression rationale [skip ci]

* [comment audit] Shorten cross-version harness and renderer fixture explanations [skip ci]

* [comment audit] Condense paired-host and E2E lifecycle rationale [skip ci]

* [comment audit] Shorten layout oracle and regression explanations [skip ci]

* [comment audit] Condense paired recovery and ownership regression contracts [skip ci]

* [comment audit] Shorten restart, sidebar and SSH lifecycle contracts [skip ci]

* [comment audit] Condense terminal rendering and IME regression rationale [skip ci]

* [comment audit] Shorten terminal recovery and benchmark contracts [skip ci]

* [comment audit] Condense tooling and Windows lifecycle proof rationale [skip ci]

* [comment audit] Condense workflow routing and release rationale [skip ci]

* [comment audit] Condense mobile security and installer contracts [skip ci]

* [comment audit] Shorten native rationale and stylesheet workarounds [skip ci]

* [comment audit] Shorten Windows visibility probe rationale [skip ci]

* [comment audit] Condense embedded release and signing rationale [skip ci]

* [comment audit] Retain current main test behavior in final reconciliation [skip ci]

* [comment audit] Complete final comment review across current source inventory

* [comment audit] Handle consolidated audit reports before final CI

* [comment audit] Preserve credential storage census detail [skip ci]

* [comment audit] Scale broad changed E2E selections with verified timing shards

* [comment audit] Align E2E assertions with browser close and settled session metadata

* [comment audit] Match existing E2Es to current resource and windowing contracts

* [comment audit] Observe the shared server outage notice in reconnect coverage

* [comment audit] Make existing renderer probes measure settled tab and PTY work

* [comment audit] Align palette E2E selector with task search

* [comment audit] Refresh remaining palette search selectors

* [comment audit] Keep selector correction within selected audit specs

* [comment audit] Await emoji fixture settings inside renderer evaluation

* [comment audit] Cancel fixture fit retries between terminal tests

---------

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-10-25 04:05:46 -07:00
Neil aeb039938f refactor(jira): colocate shared contracts and capabilities (#32927)
* refactor(jira): colocate shared contracts and capabilities

* test(jira): validate title width against panel budget
2026-10-23 14:31:05 -07:00
Brennan Benson 5c666d2974 fix(native-chat): retry a failed chat name, show the failure on the tab's hover card, and add Regenerate name (#30646)
* fix(native-chat): name a chat from its first message at once; the generated name replaces it

A native chat's one stored name was written only by a best-effort, one-shot
generation, so any failure left every surface on the 'Codex Chat' placeholder.
The first prompt now names the chat when its first turn starts, and the
generated name replaces it only while the field still holds that seed.

* fix(native-chat): the chat names setting says a chat shows its first message until it is named

* fix(native-chat): retry a failed first chat name twice before keeping the first message

A failed name generation was final for the chat's lifetime. The first name now
gets two retries, after 2 and 4 seconds, and stops early once the
first-message name was replaced.

* fix(native-chat): show a failed chat name on the tab hover card, with Regenerate name

A chat whose name generation failed after every retry gave no sign of it, and
nothing could run it again. The host now keeps the last failure in memory and
publishes it with the chat's status while the chat still has the name it failed
to replace; the tab hover card shows it with the error dialog, and a new
agentSession.regenerateName (behind a capability) makes one more try.

* fix: derive chat naming progress from host jobs

* fix(native-chat): merge duplicate import CI caught

* fix(native-chat): declare agentSession.regenerateName in the cross-version surface manifest

* fix(native-chat): stub naming-failure hook in SortableTab rename shortcut test
2026-10-20 18:34:16 -07:00
Brennan Benson c93514db5b refactor(protocol): each area owns its published runtime capability list (#30641)
* refactor(protocol): each area owns its published runtime capability list

RUNTIME_CAPABILITIES now spreads one array per area instead of a flat
hand-appended list, and runtime-capability-constants.ts is split into
the areas that own each constant. The published set is unchanged and
pinned by an exact sorted test.

* test: preserve capability baseline while allowing additions
2026-10-20 17:12:19 -07:00
Brennan Benson 6c7a6ddd54 fix(native-chat): save every chat reply and tool output whole; bound only the copy sent to clients (#29982)
* fix(native-chat): save every chat reply and tool output whole; bound only the copy sent to clients

Orca cut Codex replies, every agent's reasoning, plans, prompts and all tool
output to a 16 KB head when it saved them, and threw the rest away. The
limit existed so a reconnecting client is not replayed a huge row, but it
was applied at save time, so every transport limit became permanent data
loss.

The journal now keeps what the agent said. One outbound form, applied to
every history page, live batch and catch-up, sends a client the same 16 KB
head of each tool output and diff it always got, and marks the item
`shortened`. A new capability-gated `agentSession.item` RPC returns the
whole item, and the desktop and phone show "Show full output" / "Show full
message" when the copy was cut. Items past half a page are shortened and
marked instead of replaced with "too large to show".

Two bounds stay, both named: a 4 MiB ceiling per text (head and tail),
applied where every provider's rows enter the write queue so one write can
never stall a chat, and a preview on Grok/OpenCode/OMP/Pi calls while they
run, whose closing row is built from the whole value the translator held.

* test(native-chat): an over-budget history item is shortened and marked, not replaced

* fix(native-chat): format, keep the sink and projection under the line limit, update method count and tests

* fix(chat): preserve complete writes and keep detail current

* test(chat): align final tail reservation with captured content

* Prototype source-batch journal ownership for Claude

* Pin the resolved iterator promise in ownership experiment

* Revert "Pin the resolved iterator promise in ownership experiment"

This reverts commit bdb49ab738.

* Revert "Prototype source-batch journal ownership for Claude"

This reverts commit e5ed387f0c.

* Preserve large received chat content while pacing input

* Verify received chat writes wait for journal persistence

* Close test journal databases before removing temporary roots

* Exercise item detail in the cross-version chat wire manifest

* Pin complete saved output and fitted client publication

* Exercise Pi exit retention with real journal write pressure

* Settle terminal web test unmount before DOM teardown

* Reuse existing readers and contract support in session tests

* Reuse measured chat item sizes in history pages

* fix(chat): derive page form type from reader contract

* fix(chat): open phone user messages while loading full text

* test(native-chat): reuse imperative context provider construction

* test(native-chat): format full-content disclosure fixture

* ci: preserve unit shard diagnostics after a timeout

* ci: retain unit shard output when tests time out

* ci: expose unit shard watchdog and output stages

* Revert "ci: expose unit shard watchdog and output stages"

This reverts commit 0b4991b144.

* Revert "ci: retain unit shard output when tests time out"

This reverts commit 87eec4c04f.

* Revert "ci: preserve unit shard diagnostics after a timeout"

This reverts commit 1888c656bb.

* test: verify complete Claude tool output without large failure diffs

* Simplify reply extraction in cross-version session test

* Restore full message recovery for grouped phone thoughts

* Target recovery button in grouped thought fixture

* test(native-chat): count agentSession.item alongside main's new structured method

* fix(chat): reuse strict session subscription schema

* test(chat): open Codex before checking received content
2026-10-20 11:13:48 -07:00
Brennan Benson d406e0c8ab feat(native-chat): edit a queued message's images on desktop: remove, paste, drop or attach (#30815)
* feat(native-chat): a queued message's Save can replace its images (host, wire and capability)

* feat(native-chat): edit a queued message's images on desktop: remove, attach, paste or drop

* refactor(native-chat): share the queued edit's image rules and drop the rarely-shown Save reasons

* fix(native-chat): a queued edit's refusal says the edit wasn't saved, and its wire takes a full image set

An expired upload refused on Save read "The command didn't run": the queued
message update fell through to the command write kind. It now has its own
kind whose sentence is "Your edit wasn't saved." The images param caps at
MAX_BLOCKS, matching the shared edit rule that counts them with the text.

* fix(native-chat): a queued edit keeps image markers right and owns drops and pastes on its card

- Removing a kept image drops its [Image #k] marker from the edit's text and
  renumbers the later ones (shared rule, reused by the phone), so the agent
  never reads a marker that names the wrong image.
- A file dragged from Orca's own tree onto the editing card goes to the edit,
  like a Finder/Explorer drop; the chat box overlay no longer covers the card.
- Cmd/Ctrl+V with focus on the card's buttons goes to the edit, not the chat box.
- A "changed" answer says "Your edit is in the chat box" only when something
  went there; otherwise it says the edit wasn't saved.
- The empty hint counts an uploading image as content and is not shown for
  agents without image input; the list grows only while thumbnails show.
- Every hand-off releases the edit's preview URLs.

* fix(native-chat): an in-app drop on the editing card goes through the chat box's owner check

The edit no longer runs its own copy of the in-app drop check, which resolved
the target host through a path that can read the Active Server setting. The
chat box's claim checks the drop and routes it by the chat's owner as before,
then hands the files to the editing card's sink when the drop landed on it.
Also: the kept-image id list is readonly where it is read, and the marker
rule looks up the remaining images in a Set.

* fix(native-chat): only an edit that takes images keeps its card's pastes, and no drop joins a Save in flight

The pane paste bridge no longer skips every paste inside a queued editor: the
card's own paste handler already claims image and file pastes, which the bridge
respects. A menu paste is left to the card only when the edit takes images (the
card is a drop owner), so an older host or an agent without image input behaves
as before, and text pasted with focus on the card's buttons goes to the chat box.
An in-app drop handed to the card while its Save is in flight adds nothing.

* fix(native-chat): a failed capability re-check keeps the host's last answer, and an edit takes no files before its lease

A re-check after regained contact that failed wrote "unknown" over a known
answer for the same host, so features the host had reported (such as editing
a queued message's images) vanished on a connection blip, mid-edit. A failed
check now keeps the last answer; a different host still starts at unknown.
Paste, Attach and OS or in-app drops into a queued edit are now blocked while
it acquires its lease, as typing already is.

* fix(native-chat): an image change is never saved as text alone when the host stops reading as taking images

Reverts the shared capability hook to keep main's behavior: a failed re-check
reads as unknown. Instead the queued edit derives from live state that an edit
whose image set changed can't be saved while the host doesn't read as taking
images, so a connection blip never saves renumbered markers over the old
images or drops added ones. Save comes back once the host answers again.

* fix(native-chat): keep queued image markers and drop feedback in sync

* fix(native-chat): derive queued image markers without changing edit text

* test(native-chat): the pane drop test's intake append returns the paths it didn't take

* fix(native-chat): route menu paste to the focused queued edit

* fix(native-chat): honor queued edit paste ownership before focus redirection

* fix(native-chat): preserve native paste without clipboard access

* fix(native-chat): keep paste in the owning queued edit
2026-10-20 10:35:09 -07:00
Neil cffcef7be0 fix(projects): setup edits and removals name the host, so the twin on another host is never touched (#30664)
* fix(projects): setup edits and removals name the host, so a twin on another host is never touched

Repo-backed project host setups take the repo id, so a project on two hosts has
two setups with one id. Update and delete took the first match by id, so editing
or removing the SSH copy could rename or remove the local one. The args gain an
optional hostId (older peers strip it and keep the first match).

* fix(projects): tolerate only the legacy runtime stamp when a named host misses an independent setup

* fix(projects): refuse a twin edit on a server that would act on the wrong twin; read legacy runtime stamps as local

* fix(cli): refuse setup --host on a server that would ignore it

* refactor(projects): exact host match only; name the host on the wire only for twins on one route

* fix(projects): a named host the catalog lacks refuses; settings remove stays by id

* refactor(projects): name hosts on the wire only for this computer's catalog; paired servers keep the by-id request
2026-10-19 03:47:23 -07:00
Neil 699b9a0637 feat(files): guarded file writes that refuse to overwrite a changed file (#30605)
* feat(files): guarded file writes that refuse to overwrite a changed file

Adds an optional disk precondition to file writes: the writer compares the file
on disk with an expected signature inside the write, serialized per file (path
then inode), and refuses with a typed changed/missing result instead of writing.
Remote hosts serve it as files.writeIfUnchanged behind a capability; older
hosts get a client-side compare that fails closed, pinned to the probed runtime
and connection. Undo of a delete and the MCP starter config use it to write only
into a missing or identical file. Groundwork for guarding editor saves.

* fix(files): pin guarded writes on the sending socket; keep refused undo; narrow not-found and pinning

* fix(files): pin a guarded save to one pairing; keep only refused undo steps

* fix(files): ENOENT by code only; resolve symlink chains fully; pin lost-reply recovery to the save's pairing
2026-10-19 01:27:53 -07:00
Brennan Benson 503d919e64 feat(native-chat): + menu shows chat, disabled chat and CLI rows (#29074)
* feat(native-chat): choose which agents open as a chat

Settings → Chat gets a Providers list. Each agent the host can open as a chat
has a switch; off opens that agent in a terminal instead. Only choices the user
makes are saved. Unchosen: Claude and Codex on, others on when their CLI is
installed on this computer.

The renderer's route and the host's launch-mode decision both read one rule, so
the tab menu, shortcuts, CLI, orchestration and relayed phone launches agree.

* fix: keep chat provider settings with the other chat settings types

* test: route input carries this computer's detected agents

* feat(native-chat): the + menu shows chat, disabled chat and CLI rows

With Chat UI on, each agent whose chat provider is on gets a chat row, disabled
with the reason where the workspace's machine can't run that chat (not
installed, SSH, WSL, not on this host). Every other installed agent gets a row
marked (CLI) that opens its terminal. With Chat UI off, or on an older server
that opens no chats, the menu is unchanged.

* fix: new tab menu rows use a launch variant instead of restyling the menu item

* fix: name every route blocker the menu rows map

* fix: menu row subscriptions render on the server too

* feat(native-chat): open a chat agent's CLI from beside its + menu row

A chat row whose CLI is installed on the workspace's machine gets a › beside
it. Its side menu offers '<Agent> (CLI)', which opens that agent's terminal for
this launch only; nothing is saved and the row itself still opens a chat.

* fix: keep new tab agent surfaces authoritative

* fix: handle unknown host support reasons

* fix(chat): resolve provider choices from their owning computer

* fix: include chat launch feedback translations

* test(chat): use checked native inventory fixtures

* chore: retrigger PR checks

* test: pin pending chat row behavior

* chore: retrigger PR checks after cancellation

* Fix launch preparation lifetime and execution host startup

* fix: keep agent menu choices bound to their launch

* Simplify agent menu support reads and preserve explicit surfaces

* Keep focused menu behavior coverage within its budget

* Keep menu host fixtures typed as execution hosts

* Assert notes payload on the current launch action

* Combine agent session launch type imports

* fix(chat): keep unchosen providers on without discovery

* fix(chat): use workspace installation for menu defaults

* test(chat): type provider routing fixtures against store contracts

* fix(tab-bar): keep + menu chat rows clickable while the host answers

Each menu open discarded the host's answers, so every open showed the chat
rows disabled with 'Checking chat support' until the host replied. Answers
now outlive the menu and refresh in the background, rows use this client's
facts until the host answers, and a slow host no longer reads as a refusal.

* test(chat): align shared launch fixture with private settings reader

* fix(tab-bar): keep known + menu refusals and never treat a silent host as one

Closing the menu dropped the workspace's agent list, which changed which agents
the menu asks about and threw away the host's earlier answers, so a reopen showed
a refused chat as clickable. A support read that could not answer was stored as
"No chat on this host", disabling chats the client knows are supported.

The menu now keeps the stored agent list while closed (only fetching waits for
it to open) and only a real host answer relabels a row. The workspace's
detection target is resolved where main already resolves it (QuickLaunchButton
and the tab-bar runtime model) and passed in, so no new focus read is added.
Tab-bar test mocks now stub the QuickLaunchButton exports the menu imports.

* fix: sync runtime English catalog for agent menu

* test(chat): give the structured-session RPC fixture the private settings reader

The host now reads saved chat choices through requireStore().getSettings(); the
shared RPC fixture only had getClientSettings, so the launch-mode reader saw no
settings and create support refused Claude. The fixture now follows each test's
client settings.

* test(chat): pin create support's options argument and kept decline reason

* fix(native-chat): + menu chat rows wait disabled for the host and share its answer across tab groups

A chat the host refuses showed clickable for up to the 10s host read, then flipped to disabled,
once per tab group. Rows now stay quietly disabled until their host answers (or the read times
out), and every tab group on the same workspace host reads the same answers.

* fix(native-chat): + menu rows also wait before the host's chat list loads, and on every reopen of a host that could not answer

Asking the host no longer waits for its chat list, so Claude and Codex no longer show clickable,
then disabled, while it loads. A read that times out releases rows for that open only, so the
next open waits again instead of flipping on a late refusal. Agents not installed skip the read.

* fix(native-chat): typed + menu search puts a launchable row ahead of a disabled chat

In a WSL workspace, typing an agent's name and pressing Enter highlighted its disabled chat row,
so nothing launched where main opened the CLI. Search now ranks rows that can launch first.

* fix: preserve provider choice and cached native inventory

* feat(native-chat): hosts publish each workspace's chat availability as one stream

A new negotiated stream, agentSession.subscribeAvailability, advertised as
agent-session.structured.availability.v1. The host keeps one in-memory resource per
workspace, shared by every reader, that asks the existing createSupport check for every
registered agent in that workspace and publishes only complete generations. It answers
again when a launch setting changes, after an explicit agent refresh, and (for answers
that could not be checked) when a workspace appears; the last reader leaving retires it.
A check that could not answer is unknown, never a refusal. agentSession.agents stays
static and unchanged.

* feat(native-chat): + menus read a publishing host's workspace availability

The renderer holds the workspace's availability stream while its tab bar is mounted
(not while a menu is open), shared by every tab group, with runtime and contact fences.
It exposes a readiness model, { ready: false } | { ready: true; byAgent }, from
useHostStructuredAgentAvailability and the menu hook. On a host advertising the
capability the menu reads its answers from that model and makes no per-agent
createSupport reads; an older host keeps the per-agent reads, answer cache and deadline
unchanged. Row building and presentation are unchanged.

* fix(native-chat): workspace availability checks only what the shown workspace needs

Only the shown worktree (or an open menu) holds its host's availability stream, and only with Chat
UI on. Display checks no longer force a fresh login shell for an agent that isn't installed, so
Claude and Codex answer without waiting on it. A change to the Windows runtime default or a project's
WSL choice re-checks, and a workspace still on its first check answers again when one appears.

* fix: keep main's bytes for the create-support file the merge reformatted

The pre-commit formatter rewrapped main's unformatted lines during the merge and pushed the file past its line limit.

* test: type the + menu rows hook mocks without assertions

* test: match the availability stream's types in three tests

* test(cross-version): declare the workspace availability stream in the agent-session surface

* feat(native-chat): + menu holds a placeholder until its agents are known, and learns them while the workspace is shown

* feat(native-chat): + menu reads one agent health snapshot per machine

The + menu no longer checks chat availability per workspace. Each Orca
runtime keeps one agent health snapshot: every agent's location rule on
this machine plus a typed observation of its installed release, probed
in the server's own home. It is taken on first use and replaced only on
a launch setting change, an explicit agent Refresh (after a fresh shell
capture), or a restart, and kept without readers.

- The version probe reports supported, unsupported or inconclusive; a
  launch still refuses an inconclusive run, while the snapshot reads it
  as unknown rather than a refusal.
- agentSession.subscribeAvailability takes no params and publishes that
  snapshot; a replay that closes the transport releases its reader.
- The renderer keeps the snapshot per host, runtime and pairing, through
  lost contact and across workspace switches on the same host.
- The menu hook drops the per-open createSupport display reads, their
  deadline and answer cache; older hosts show client facts and the
  click's admission, unchanged, states any refusal.

* fix: a failed shell recapture still publishes the agent health snapshot

* fix(native-chat): capture fresh shell facts for every health snapshot

* fix(tab-menu): share settled agent actions across menu surfaces

* fix(tab-menu): preserve loading row line height

* feat(native-chat): the whole chat row opens its CLI submenu

Hovering a chat row in the + menu, or pressing ArrowRight on it, now opens
the "<Agent> (CLI)" submenu to the right instead of needing a separate
chevron button. Clicking the row, or Enter/Space, still starts the chat
and closes the menu; hosts pass closeMenu since a submenu trigger never
closes the menu on its own.

* fix(tab-menu): line the CLI submenu up with its chat row

* feat(native-chat): a chat row's terminal button opens its CLI

Clicking a chat row in the + menu starts a chat. Hovering the row (or
pressing ArrowRight on it) reveals a terminal button at its right edge that
opens the agent in a terminal instead. This replaces the submenu, whose
chevron made the row look like it only opened a submenu. The button's click
bubbles into the row, so the menu closes as for any item and the hosts no
longer need a closeMenu callback.

* fix(native-chat): the chat row's terminal button handles drags, arrows and screen readers

A press on + dragged onto a chat row's terminal button and released there
now opens the CLI: the row reads whether the pointer or focus is on its
button instead of the click target, which a drag-release never sets.
Arrow, Home and End keys pressed on the focused button move between rows
as they do on the row. The row is announced as "Start a <Agent> chat"
instead of including the button's label.

* fix(native-chat): chat rows read "Start a chat with <Agent>"

"Start a OpenCode chat" was ungrammatical for agents whose name starts with a vowel.
2026-10-19 00:27:17 -07:00
Neil e3c9370da4 feat(agent-status): read OSC 7501 program status beside hooks (#30261)
Programs can tell Orca what they are doing through the Program Status
Protocol (OSC 7501); Claude Code, Codex, Grok, pi and oh-my-pi already do.
Orca answers the protocol's feature-detection probe (visible xterm and the
hidden-pane emulator), parses reports from PTY output on the execution host
(src/shared/program-status/), and keeps two lanes:

- Lifecycle state keeps one owner per pane. Hooks win; OSC 7501 drives the
  row when hooks are off, missing, or silent on a state the program has
  left (server/program-status/hook-precedence).
- The program's own report (progress, its one-line message, blocks such as
  sign-in) is a live-only `programStatus` facet kept per pane
  (server/program-status/facet-store), stitched onto rows at publish time
  and carried to the renderer store, mobile, paired clients and
  `worktree ps`. Facet-only changes patch just the facet.

Title-driven readers (idle edges, delivery, tui-idle, exit inference)
classify by the program's OSC 7501 activity while it reports one and fall
back to title glyphs only for agents that report nothing explicit, so they
keep working when Claude stops painting its title glyph after the probe.
2026-10-18 22:12:37 -07:00
Kelvin Amoaba cf7423c8f4 feat(mobile): redesign the chat composer with file attachments and inline skills (#30359)
* refactor(native-chat): move the slash picker builder and upload chunking to shared

Mobile builds the same skill and command list as desktop.

* feat(mobile): redesign the chat composer with file attachments and inline skills

* fix(mobile): the composer field owns its input style

The page's font-size census cannot follow a style passed in as a prop.
2026-10-18 20:03:26 +00:00
Brennan Benson 6fd2172b3f feat(runtime): remove an agent's downloaded runtime only once nothing on the host can still run it (#29139)
* feat(runtime): remove an agent's downloaded runtime only once nothing on the host can still run it

managedRuntime.remove {agent} (host-admin, capability managed-runtime.remove.v1)
deletes the agent's releases inside the launch gate. Before anything is deleted the
gate aborts starts under way (chat starts and conversation copies), stops the
agent's loaded chats on this host, and proves every retained child gone: failed
chat starts and sign-in children whose close was never proven. Every close settles
before the gate releases; if any owner stays unproven, Remove fails and keeps the
files. Stopping chats by agent is now one generic host helper that sign-in shares.
Install refuses while a removal runs, Remove refuses while an install runs, and a
removal cannot be cancelled.

* fix(runtime): status says whether a copy is there to remove, damaged copies included

canRemove is set whenever Orca's copy is present, whole or damaged, so Settings offers Remove for a
damaged copy too. The failed-start retain is dropped: the launch gate already holds every child it
admitted until it exits.
2026-10-18 01:15:11 -07:00
Brennan Benson 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.
2026-10-16 21:14:03 -07:00
Brennan Benson 634ca45fbb feat(acp): hosts install a managed runtime over RPC, and mark agents whose program Orca provides (#29024)
* feat(acp): hosts install a managed runtime over RPC, and mark agents whose program Orca provides

managedRuntime.status / managedRuntime.install / managedRuntime.cancel, negotiated by
managed-runtime.install.v1, act on the host that receives them, the one that runs the agent's
chats, with no workspace needed. A status read naming the revision it shows is a long-poll on its
own call-queue lane, so every client sees another's start, progress and outcome.
agentSession.agents marks an agent whose program Orca provides on that host.

* fix(runtime): install status says a copy outside Orca's runs only when a start would really run it

* fix(runtime): install status is pushed to subscribers instead of long-polled, and Cancel answers with the outcome

managedRuntime.subscribe {agent} sends a full snapshot on subscribe, then one
snapshot per install state change, and closes with the transport. It replaces
the managedRuntime.status long-poll and its slot registrations.
2026-10-16 18:47:12 -07:00
Brennan Benson 8c1ade9e44 Keep pending chat decisions available outside loaded history (#29121)
* fix(native-chat): retire acknowledged send echoes after catch-up

* test: follow phone lint rules in reconnect regression

* test: format reconnect message expectation

* Keep current chat requests independent of loaded history

* Read current chat requests in bounded cursor pieces

* Fix current request subscription static checks

* Match generated request schema import ordering

* fix(native-chat): correct current request type boundaries

* Exercise current request reads in wire compatibility inventory

* Align current request fixtures with provider commit and RPC surface

* test(native-chat): include current request browser capability

* Keep the mobile chat session hook within its line budget

* Format mobile current request readers

* fix(native-chat): reopen once for a current-request version whose read keeps failing

A deterministic read failure reopened the subscription, whose first frame re-read the same version and reset the backoff, so the pane resubscribed every 750 ms forever.

* test(native-chat): type the current-request snapshot fixture

* test(native-chat): wait for the hydrated open before driving the subscription
2026-10-16 16:01:58 -07:00
Neil 6ae1386f98 fix(native-chat): never run a chat on an older host with more access than Ask for approval allows (#29442)
* fix(native-chat): refuse to start or send on a host that ignores Ask for approval

* fix(native-chat): hide Continue on a host held for its permission mode

* fix(native-chat): hold queued cards and goal turns on a held host; re-check on reconnect

* fix(native-chat): hold goal objectives and start retries on a held host; re-ask while held

* test(native-chat): give the remote Continue test a host that applies permission modes
2026-10-15 23:23:38 -07:00
Brennan BensonandOrcaWin 7c23f79622 fix(quick-commands): save agent prompts whole, bounded by what a chat message carries (#24488)
* fix(quick-commands): save agent prompts whole, bounded by what a chat message carries

Agent quick-command prompts were silently cut to 6,000 characters on save:
the editor accepted a long paste, settings normalization trimmed it, and
reopening showed the shortened copy. The 6,000 was a Windows launch speed
threshold, not a limit on prompts.

- Storage no longer trims. A save is refused, never trimmed, when a prompt's
  chat text block is over 256 KiB of JSON (measured on its real escaped size,
  the measure the chat send schema applies) or the list is too large for one
  paired reply. Host and desktop store both check before writing.
- The editor shows the size against the limit and blocks Save past it, and
  stays open with the text when the host refuses the save.
- terminal.quick-commands.long-prompts.v1: hosts withhold prompts over 6,000
  characters from clients that do not advertise it (older clients reject a
  whole list holding one); editors keep an older host's 6,000 cap.
- The phone editor no longer drops pasted text past 6,000 via maxLength.

Launch delivery of long prompts is left to the launch-prompt series
(#24257, #23962, #24080); this branch's earlier paste-after-ready delivery
duplicated it and is removed.

* fix(quick-commands): drop a stale duplicate doc comment

* fix(quick-commands): advertise long prompts to this build's own host too

* fix(quick-commands): keep submitted editor immutable until save settles

* test(quick-commands): keep storage and save lifecycle coverage focused

* fix(quick-commands): keep portalled choices within pending form ownership

* Preserve retained credentials when settings validation refuses a save

---------

Co-authored-by: OrcaWin <alpha-eng@stably.ai>
2026-10-15 18:37:41 -07:00
Brennan Benson 1b1d09cfb5 Open a native chat copy in the CLI without waiting for History (#29126)
* Prepare native chat CLI copies from the host record

* Apply staged-file lint and formatting checks

* Cover pairing replacement before queued preparation dispatch

* Reuse CLI failure feedback and complete the pairing test response

* Isolate Codex materialization test user data

* Preserve native chat CLI copy completion and preparation ownership

* test: consolidate native conversation fork contracts

* fix(native-chat): refuse a Codex chat fork whose saved account home has no CODEX_HOME path
2026-10-15 14:59:26 -07:00
Jinjing e5e6189f96 Split Jira issue workspace hooks and IPC param checks (#28759)
* refactor(jira): split issue workspace hooks and IPC param checks

- Move IPC argument normalization into jira-ipc-params.ts so the
  jira:* handlers and its tests share one validator. Issue edits are
  now also checked for labelOperations, which the host reads, so
  malformed label edits are rejected before they reach Jira.
- Move issue hydration and remote detail state out of
  JiraIssueWorkspace.tsx into use-jira-issue-workspace-data.ts, and
  comment submission into use-jira-issue-comment-submission.ts, so the
  component stays under the file-size limit.
- Split the reopened-issue and rollback optimistic-edit tests into their
  own files to keep each test file focused.
- Move runtime capability constants into runtime-capability-constants.ts
  and re-export them from protocol-version.ts, so the values keep the
  same names and exports for existing importers.

* refactor(protocol): use named imports for runtime capability constants

Import the runtime capability constants by name instead of destructuring
them from a namespace object. The bindings and values are unchanged.
Named imports make the dependency explicit to readers and tooling, and
the protocol version comment now sits next to the constants it describes.
2026-10-14 10:34:33 -07:00
Neil a0c46b405f feat(jira): load attachment images outside issue Markdown (#28631)
Load Jira attachment images separately from issue Markdown so descriptions and comments stay small while screenshots load lazily and open in the existing lightbox. Downloads begin when an image mounts, capture its owning host, require negotiated image support, and use bounded authenticated byte streams and caches. Older clients retain their inline-image handlers.

Keep legacy and cancellable downloads in separate in-flight domains so cancelling a new image reader cannot abort a legacy reader; completed bytes share the bounded cache. Preserve the landed assignee, board, transition/comment queue and request lifetimes. Move unchanged project metadata IPC handlers into their domain module and tighten two inherited focus-check ceilings without changing behavior.

Validation:410 tests/54 suites; all changed-source quality stages, lint, format, routing and contract checks; exact final node/web types; independent source review;48/48 fresh hidden Electron checks with226 stable source hashes and nine inspected screenshots. Synthetic transports exercise production clients and UI. Current CI static checks, mobile bundle, wire compatibility, Linux/Windows packaging and Windows serve pass; five test shards were pending at review.

Extends the existing attachment work credited to Frank (@lifefloating) and Brennan Benson in PR #8938; existing contribution credits and media remain in this PR.
2026-10-14 05:33:30 -07:00
624c4148cc View Jira work on a board and filter the current sprint (#28633)
Show Jira issues as a board alongside the existing list and add the current-sprint preset. Status moves use the existing owner-scoped mutation queue, collect required workflow fields/comments, retain rejected drafts, and reload the mounted comment timeline once after success. Source/session fences keep older detail failures from undoing newer board moves.

Preserve statuses plus sprint in query preview and Edit-as-JQL. Older hosts must advertise sprint support; unsupported peers fail clearly. Existing capability definitions and test fixtures are split without changing public exports, wire strings, or coverage to meet current lint limits.

Validation:351 tests/42 suites,all46 changed sources full-file lint/anti-slop and7quality stages,localization/RPC checks,and independent source review passed. Node types passed on runtime-identical99d and web types passed on final602d. Fresh hidden Electron54/54 checks passed on99d; final two test-only changes were hash and emitted-runtime audited. The inherited IPC301-line failure is byte-identical main and being corrected separately by the image PR.

Adapted Eugenio Valeiras (@EugeValeiras)'s board contribution from PR #13054.

Co-authored-by: Eugenio Valeiras <eugenio.valeiras@gmail.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-10-14 05:10:29 -07:00
NeilandWooseong Kim eb1ee8f908 fix(jira): collect required fields before changing status (#28588)
Collect required Jira transition fields through the shared issue save queue, preserve entered fields after rejected saves, and refresh the comment timeline after successful transition comments. Keep captured-host and older-peer compatibility.

Co-authored-by: Wooseong Kim <innocarpe@gmail.com>
2026-10-14 03:57:00 -07:00
NeilandBrennan Benson 702777e2fe Filter Jira preset views by multiple workflow statuses (#28623)
Combine remembered Jira status filters with presets and saved JQL on the connection host, protecting obsolete owner actions and keeping incompatible controls hidden in linked mode.

Co-authored-by: Brennan Benson <brennankbenson@gmail.com>
2026-10-14 03:47:08 -07:00
Neilandno-support d7315af456 Save and reuse named Jira JQL filters on the execution host (#28600)
Save and apply named Jira queries on their connection host. Reject obsolete owner actions and exit linked mode only after a saved query is actually applied.

Co-authored-by: no-support <jwan0131@gmail.com>
2026-10-14 03:37:52 -07:00
NeilandKazuki Hanai 51173e45d4 feat(jira): generate issue titles on the connection host (#28625)
Generate Jira issue titles through the selected execution host, protecting cancelled requests and treating Jira content as quoted prompt data.

Co-authored-by: Kazuki Hanai <kazukihanai@sakana.ai>
2026-10-14 03:16:50 -07:00
7ac2dcc7d4 feat(cli): let agents read and update Jira tickets safely (#28592)
Expose host-owned verified Jira reads and single-attempt writes through the CLI. Preserve site ambiguity checks, old-host capability refusal, atomic label changes, and uncertainty after failed writes.

Refs #11986 and #26055.

Co-authored-by: Dan Cieslak <dcieslak19973@users.noreply.github.com>
Co-authored-by: kokbee <kok2209@gmail.com>
2026-10-14 03:03:50 -07:00
Jinwoo HongandClaude 5f134d8f01 feat(mobile): show and use the desktop's server workspaces on the phone (#26501)
* test(e2e): add a paired phone client helper and phone-mirror baseline specs

Later phone-mirror slices need one oracle that drives a real mobile-scoped device with the
phone's own capabilities against a serve host or a paired desktop. Pins #26022 across a serve
restart and today's gap: a desktop's phone does not list the desktop's server workspaces.

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb

* feat(mobile): show SSH host health on the workspace row host badge

Rows on an SSH host the desktop cannot reach looked healthy on the phone. The badge now
adds the desktop sidebar's own word ("devbox · Disconnected"), derived by the same shared
function from the connection status ssh.listTargetSummaries already sends; old desktops keep today's badge.

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb

* test(e2e): compose the phone-mirror topology and check the cold snapshot

Slices need one composer for host, desktop and phone with a single teardown, so specs stop
copying setup. The serve-restart spec now reads the first tab-stream snapshot after the cold
start, which is what the phone builds its tab strip from.

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb

* feat(mobile): name an unhealthy SSH host even in a single-host list

The desktop card shows SSH status on any host count, so the phone now reads target summaries
whenever a repo is on SSH and badges an unhealthy host alone too. Labels and health share one
target-to-host keying, and a hook-level test pins the reply-to-row wiring.

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb

* test(e2e): keep phone-mirror teardown errors from masking test failures

Teardown failures become a test attachment instead of replacing the real failure, and the
cold-snapshot poll reports stream errors together with the PTY list.

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb

* fix(mobile): clear stale SSH host badges and announce them without the dot

A catalog that no longer spans hosts or holds an SSH repo now clears the health and label maps,
so a host that left cannot keep badging rows. The badge gets a screen-reader label ("devbox,
Disconnected") instead of reading the middle dot.

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb

* feat(runtime): let a paired desktop register its phones as delegated mobile devices

A desktop paired to this runtime can now call pairing.delegatedMobileDevice.sync to make each of
its phones an ordinary mobile-scope device it owns, so the phone mirrors that host through the
desktop. Children are never pending, inherit the parent's reach, and die with the parent's grant.

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb

* refactor(runtime): move the mobile device revoke primitive below dispatch

Dispatch binds the caller-bound delegated-device context, so the revoke path it needs now lives in
a layer under it as a pure move; this drops the abstract method seam. No behaviour change.

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb

* fix(runtime): keep a runtime grant until its delegated phones are revoked

A failed child cleanup now leaves the parent in place so a retry can still find the children, and
revokeRuntimeAccess lives beside the revoke it cascades to. Adds restart, rename and bounds coverage,
shares credential minting, and drops the timing-dependent census.

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb

* fix(runtime): fail a delegated sync whose dropped phone cannot be revoked

Matches the parent revoke: a child whose cleanup could not be saved is kept and the sync errors
before minting anything, so the desktop retries instead of believing the server matches its list.

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb

* feat(mobile): relay a paired phone's server-workspace requests through the desktop

A phone mirrors its desktop, including workspaces on the desktop's configured servers. The desktop
now forwards a request that names `executionHost: runtime:<env>` to that server over one socket per
(phone, server), signed in as the phone's delegated device; replies pass through untouched except
stream ids. Hidden until the phone client ships: the relay capability is not advertised yet.

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb

* test(mobile): type the single-host health fixtures inline so the test tsc passes

Fixtures hoisted into consts widened their health maps to string, which the now-required
hostHealthById rejects under the tests-typecheck ratchet; passing them inline keeps the union.

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb

* docs(mobile): state the desktop relay contract in its entry module

main dropped docs/reference, so the relay's contract lives beside the code. Also pins that a
server whose delegated sync fails (e.g. delegated_device_revoke_failed) is unavailable until a
later request syncs it again.

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb

* test(mobile): pin the relay's configured-server adapter

The production hosts adapter was only typechecked; this pins that it resolves configured servers
by exact id only (never a name or a removed server), fences by pairing revision and runtime
identity, and stops reporting retirements once unsubscribed.

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb

* test(mobile): re-record the repo-metadata goldens for SSH host health

The settled metadata state now carries hostHealthById, and an SSH-only catalog now reads the
target summaries so a lone unhealthy host can be badged. Scoped re-record via rpc-recording.mts.

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb

* refactor(mobile): tighten the desktop relay after elegance review

Reuses the shared E2EE socket opener, folds relay state into fewer fields, never sends a request
whose link ended while opening, passes failure codes through, relays only this desktop's own
phones, and drops the unused host-state reader until S2 needs one.

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb

* fix(mobile): bound relay frames and stop sticky relay verdicts

Restores the 8 MiB frame cap on every remote runtime socket, re-checks an update-needed server on
each new link so an in-place upgrade relays, answers a phone the server just declined without
syncing again, and ends a link whose mirrored capability update cannot be queued.

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb

* fix(mobile): run targeted desktop-owned calls on the desktop

The phone tags every call on a workspace screen with its execution host, so a
paired-desktop method carrying `runtime:<env>` now runs locally instead of
failing 'forbidden'. The Mac stays the only reader of the routing table.

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb

* feat(mobile): list the desktop's configured servers and their last workspace rows for the phone

The phone needs the same host list the desktop shows, including servers that are offline or
too old to relay to. mobileRelay.hosts.list/worktrees answer from the desktop's own status and
its own worktree.ps fetches, cached per server and fenced by pairing, never from relayed replies.

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb

* refactor(mobile): list server rows through one desktop fetch, with no side effect on listing

The phone now always lists a server's rows through mobileRelay.hosts.worktrees, so the
desktop's own fetch is both the live source and the cache, and stamping stays in main.

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb

* fix(mobile): fetch a server's full workspace list within a bound, and forget removed servers

The desktop now asks for the phone's full-catalog limit, answers within 5s with the last rows
when a server hangs, drops a server's rows when it is removed or re-paired, and checks the
server's identity after the fetch so a reply from before a re-pair is never served.

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb

* feat(mobile): name a server workspace's host on the phone's calls

A phone reaches a server's workspace through its desktop only if each call names the host; the
direct, cloud-relay and shell-bridge transports now carry `executionHost`, streams keep it for
replay and unsubscribe, and an older shell (no `executionHost` grant) refuses rather than strips it.

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb

* fix(mobile): list managed servers as the desktop sidebar does, and hide rows mid-delete

A server Orca manages on an SSH host now lists once under that host's name, or not at all while
the host is reached over the SSH relay, using the sidebar's own folding. Rows the server is
deleting are left out at reply time, as a phone asking the server itself would see.

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb

* fix(mobile): keep a disconnected server's last rows, and never join a re-paired server's fetch

Retirement also fires on a manual disconnect or managed-server update, which wrongly emptied
the phone's list; rows are now pruned only once a server is no longer configured. Fetches are
shared per server identity, so a poll after a re-pair asks the new server right away.

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb

* feat(mobile): list the paired desktop's server workspaces on the phone

Fixes the phone missing a desktop's `orca serve` workspaces: rows come from the desktop's
mobileRelay.hosts.* with each server's health ("Update needed", "Disconnected"), open through the
relay, follow the desktop's hidden hosts, and the desktop now advertises the relay capability.

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb

* test(mobile): pin that a desktop failing the hosts list leaves only its own rows

The page asks a relaying desktop for its servers; a refusal such as method_not_found must leave
today's list with no server rows and no catalog error.

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb

* fix(mobile): mirror the desktop's shown hosts exactly, one host included

Jinwoo's call: the phone follows visibleWorkspaceHostIds as the desktop sidebar does, so a
one-host list narrows the phone too instead of being read as a single-host focus.

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb

* fix(mobile): scope a server workspace at its route, never the desktop

Every screen under a server workspace's route now reads one context and
gets that server's client and a router that keeps pushes on it, refusing
to fall back to the desktop; pins write to the row's owner like the Mac.

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb

* fix(mobile): never open an unreachable server workspace, and prove the relay in e2e

A server workspace the phone can't reach now gets one explicit screen at its route
and no row tap or action-sheet entry opens it; native routes read the route's host,
a census pins every workspace route, and the e2e reads files and git on the server.

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb

* feat(mobile): gate a server workspace on that server's own features

A targeted status.get now relays to the server, so a server workspace's route
provides that server's status to every screen under it and its action sheet,
while whether the phone can reach the server stays the desktop's answer.

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb

* fix(ci): run the remote-runtime WebSocket opener test on Node

Under the bun vitest project, `ws` resolves to Bun's built-in client, which
ignores maxPayload, so the frame-cap test hung. The opener only ships on
Node/Electron, so run its test in the Node runtime project like its siblings.

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb

* fix(mobile): give the file preview the workspace router, back to 128 page assets

The preview was the one workspace screen on the plain router, so the scoped router
was reached by a route set of its own and esbuild split it into an extra chunk
(129 assets); every workspace screen now uses it and it shares the route's chunk.

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb

* refactor(mobile-relay): derive the phone allowlist from the routing table

Adding a phone method now takes one edit (its route). A server-targeted call on a
legacy non-E2EE socket is refused instead of running locally, and only
execution-host methods enter the relay.

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb

* refactor(mobile-relay): drop unread freshness fields from server workspace lists

The phone reads only the rows, so stale, fetchedAt, totalCount and truncated
were dead wire weight. The desktop still serves a server's last rows while
the server is unreachable.

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb

* refactor(mobile): read only the rows of a server's workspace list

The desktop no longer sends a freshness flag, so the phone's schema drops it.
The relay capability list names the delegated-devices constant instead of
repeating its literal, which also retires a now-circular type check.

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb

* fix(mobile): show a server workspace as disconnected while its server is down

The phone read the desktop's connection state for a server workspace, so a stopped
server kept a green dot and "Loading tabs" spun forever. The workspace route now
narrows that state by the server's health from mobileRelay.hosts.list, reusing the
existing disconnected session UI and refetching tabs when the server returns.

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb

* refactor(mobile): share one desktop server-list feed between the host list and workspaces

A server workspace polled mobileRelay.hosts.list beside the host list's own read, so
the phone could ask the desktop twice per tick and the row badge and the screen could
disagree. One feed per desktop client now owns that read; both readers join it.

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb

* test(e2e): read a server's reachability from the hosts list, not a dropped stale flag

eff410fe30 dropped `stale` from mobileRelay.hosts.worktrees, so the mirror spec's
schema never parsed and its first poll timed out. It now waits on the server's
relay/health from mobileRelay.hosts.list, the field the phone itself reads.

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb

* build(mobile): re-measure the page's script sweep for server workspaces

Main already sat on its 78-script ceiling (re-swept: 78, table: 74); the workspace
route's shared modules split importer sets three ways more, reaching 81. Re-swept per
route prefix on this head, as #24257 did for its feature; the spread is now 1 to 11.

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb

* refactor(mobile): export the server-list feed's React reader beside the feed

useRelayHosts moves from workspace-server.ts into relay-hosts-feed.ts, so every reader (workspace screens now, the new-workspace sheet next) subscribes through the feed's own module. No behaviour change.

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb

* fix(mobile): keep polling the server list and read a server's reachability from relay

A hosts-list read slower than the poll stopped the feed for good, since the next tick was
scheduled when the read started; it is now scheduled from the finish. A server workspace now
reads connected only when the desktop relays to it, so a server with no status yet is not shown up.

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb

* fix(mobile): show a reached server as connecting and a too-old one as disconnected

A server the desktop has reached but whose status is not answered yet is moments from relaying,
so it reads connecting; a server too old to relay to never will, so it reads disconnected.

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb

* refactor(shared): move the account capabilities into their own group module

Main's merge put protocol-version.ts at 301 lines, over the 300 cap. The account group moves out
like the relay and skill groups did, same strings in the same order; the page's script sweep is
re-measured because the split changed its chunking (81 to 79 at 16 routes).

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb

* refactor(mobile): name a server workspace through the request envelope

The server name now rides the generic request envelope from #27815 instead of
its own executionHost option, bridge grant and per-transport plumbing. Server
rows show only where the transport carries an envelope (native, or a shell
that grants rpcEnvelope), so an older app keeps today's list.

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb

* test(mobile): pin the server-call wire and the no-envelope deep link

A server workspace call still reaches the desktop as the same top-level
executionHost frame, on the direct and relay wires, and a deep link into a
server workspace under an app without rpcEnvelope shows the unreachable screen.

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb

* refactor(mobile): read a relay stream's envelope slot in sorted key order

Equal envelopes name one cleanup slot whatever their key order, the scoped view
passes its client's envelope answer through as it does the generation, and the
direct wire harness is shared instead of rebuilt.

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb

* fix(mobile): a phone's new chat in a server workspace follows the desktop's chat setting

A relayed agent.launch ran on the server, which picked chat vs terminal from its
own setting. The desktop relay now stamps its own default as nativeChatDefault;
new servers use it in place of their setting (feasibility still decides), older
servers strip it. Outside the replay fingerprint.

Co-Authored-By: Claude <noreply@anthropic.com>

* refactor(mobile): move the relay's delegated phone grant cache into its own module

Keeps mobile-desktop-relay.ts under the line limit once #27749's page relay lands on it.

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(mobile): show the early terminal tab from the relaying desktop's chat default

The early-tab preflight read the server's own chat setting while the launch
used the desktop's stamped nativeChatDefault, so on a windowed server with
chats off a relayed chat launch flashed a terminal tab. Both now read the
settings through readAgentLaunchModeSettings(runtime, nativeChatDefault).

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-10-13 23:35:33 -04:00
Jinwoo HongandClaude b8e49222a3 fix(remote-runtime): wait for the desktop's terminal list before opening a terminal (#28154)
* fix(remote-runtime): wait for the host's terminal list before opening a terminal

A paired client opening a workspace whose terminals it had not heard about
yet read "no answer from the host" as "zero terminals" and asked the host for
one. When the host's list then arrived with terminals, the workspace gained an
extra terminal; when it arrived empty, the first-frame bootstrap created a
second one.

The client now treats an unknown host count as unknown. Activation without
explicit startup work waits, and the active subscription's first real answer
seeds the workspace if it is empty. An "ask me later" frame from a host that
has not published yet no longer records a count of zero, and the host's later
answer (sent as an update) is accepted as the first answer for seeding.

* fix(remote-runtime): count an unknown host as empty unless it answers later

Hosts before #25736 answer an empty workspace with "ask me later" and
never follow it with a real answer, so waiting for that answer left a
blank workspace with no terminal against them. Wait only on hosts that
advertise session-tabs.empty-worktree-answer.v1; older hosts keep
main's behaviour. Workspaces this client just created are known empty
and open their first terminal without waiting.

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(remote-runtime): log a failed delayed terminal open; drop an inert flag

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-10-13 14:39:37 -04:00
OrcaWinandm4air fee083fa47 feat(runtime): session-only browser keys and opt-in for pairing from plain-http pages (host) (#27540)
* feat(runtime): session-only browser keys and an opt-in for pairing from plain-http pages (host)

- pairing.registerDeviceKey accepts ephemeral: true (advertised as
  pairing.device-key-ephemeral.v1). Such keyed rows are dropped when the host
  restarts and after 12 hours with no live socket; a sweep removes them and cuts
  any socket still bound to them, so later key auth gets device_revoked.
- The WebSocket upgrade Origin is recorded per socket. A first pairing (a
  never-used pairing code) from an http:// page on another machine is refused
  with a sealed insecure_origin error unless the operator sets
  ORCA_ALLOW_INSECURE_WEB_PAIRING=1. Already-paired browsers, https and loopback
  pages, and native clients are unchanged.
- Registry file reading moved to device-registry-file-read.ts to keep the
  registry under the size cap.

* fix(runtime): gate first pairings only for the marked web client, never phones

React Native sends an http Origin on every LAN WebSocket, so the Origin alone
refused first-time QR pairing from released phones. The web bundle now marks
its upgrade with ?orca-client=web; only a marked socket from an http page on
another machine is gated. Released hosts ignore the query string.

---------

Co-authored-by: m4air <m4air@Mac.localdomain>
2026-10-11 14:40:36 -07:00
OrcaWinandm4air 47b94ef64a fix(daemon): keep tweetnacl out of the relocated Windows daemon-host graph; guard daemon externals at build (#27504)
Co-authored-by: m4air <m4air@Mac.localdomain>
2026-10-11 09:04:55 -07:00
OrcaWinandm4air 6ffbdcd875 feat(runtime): device-key authentication and per-installation key migration (host) (#27493)
* feat(runtime): device-key authentication and per-installation key migration (host)

* fix(runtime): corrupt registry is never a revocation verdict; keyed sockets dispatch without a token; forks keep their Relay binding

* fix(runtime): keep an unparseable device registry aside; an unlisted key is never 'revoked' while it exists

* fix(runtime): a set-aside corrupt registry keeps verdicts doubtful even when the registry file is missing

* test(runtime): registerDeviceKey reaches the bound socket through the dispatcher for runtime and mobile rows

---------

Co-authored-by: m4air <m4air@Mac.localdomain>
2026-10-11 07:51:33 -07:00
OrcaWinandm4air 08b65edc5c feat(serve): let the owner resume paused automatic restores (#27459)
* feat(serve): let the owner resume paused automatic restores

The terminal daemon now answers automaticWorkStatus and
resumeAutomaticWork. Resume clears a recorded same-boot crash pause or
a pressure pause, writes the cleared state durably, and wakes restores
that were waiting. The runtime exposes runtime.automaticWork.status and
runtime.automaticWork.resume (host-admin; advertised as
runtime.automatic-work.v1), the CLI adds 'orca serve restore resume',
and a sleeping pane paused by its host offers 'Resume automatic
restores'. A daemon that predates the requests reports no gate.

* fix(serve): a stale daemon cannot resume, and an old one says so

Resume now re-reads endpoint ownership right before it runs, so a
daemon replaced by a successor cannot rewrite the successor's run
marker. A gate-bearing daemon from before resume existed is reported as
'terminal host predates resume' instead of 'no gate'; the CLI says to
run the command again after the terminal host restarts, and a sleeping
pane still resumes when the user asks for it.

---------

Co-authored-by: m4air <m4air@Mac.localdomain>
2026-10-11 06:31:43 -07:00
OrcaWinandm4air 10d5108671 feat(runtime): accept a declared spawn intent from paired clients (#27452)
* feat(runtime): accept a declared spawn intent from paired clients

Paired clients can now say whether a terminal restore is the pane the
user is looking at (user-resume) or a hidden one (automatic-restore).
The host forwards the intent to its fork gate, advertises
terminal.spawn-intent.v1 so clients only send it to hosts that accept
it (terminal.ensureAgentSession params are strict), and reports a
deferred fork as terminal_spawn_deferred with its reason.

No client sends the field yet; that lands next with the sleeping-pane
handling.

* fix(daemon): a claimed agent resume cannot declare itself interactive

Interactive forks skip the gate, and paired clients can now declare
intent, so the host keeps inferring from the claim when a request
says interactive.

* fix(runtime): clients may only declare restore intents

A daemon kept running across an update still lets a declared
interactive request skip its gate, even for a claimed resume. The RPC
and local IPC now accept only user-resume and automatic-restore, and
the daemon request boundary turns a claimed interactive into an
automatic restore.

---------

Co-authored-by: m4air <m4air@Mac.localdomain>
2026-10-11 05:34:23 -07:00
Jinwoo Hong de00a4f03c feat(runtime): let a paired desktop register its phones as phone devices (#26427)
* feat(runtime): let a paired desktop register its phones as delegated mobile devices

A desktop paired to this runtime can now call pairing.delegatedMobileDevice.sync to make each of
its phones an ordinary mobile-scope device it owns, so the phone mirrors that host through the
desktop. Children are never pending, inherit the parent's reach, and die with the parent's grant.

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb

* refactor(runtime): move the mobile device revoke primitive below dispatch

Dispatch binds the caller-bound delegated-device context, so the revoke path it needs now lives in
a layer under it as a pure move; this drops the abstract method seam. No behaviour change.

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb

* fix(runtime): keep a runtime grant until its delegated phones are revoked

A failed child cleanup now leaves the parent in place so a retry can still find the children, and
revokeRuntimeAccess lives beside the revoke it cascades to. Adds restart, rename and bounds coverage,
shares credential minting, and drops the timing-dependent census.

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb

* fix(runtime): fail a delegated sync whose dropped phone cannot be revoked

Matches the parent revoke: a child whose cleanup could not be saved is kept and the sync errors
before minting anything, so the desktop retries instead of believing the server matches its list.

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb

* fix(runtime): refuse pairing admin to phones delegated through a paired desktop

A delegated child phone could call the mobile-allowlisted pairing-admin methods
(push registration, relay provisioning) that its parent desktop is never granted.
The caller scope now marks delegated phones and denies them that permission.

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
2026-10-11 02:54:09 -04:00
OrcaWinandm4air bcc50483bb fix(vm): a recipe VM's SSH root runs on a managed Orca server (#27366)
* fix(vm): a recipe VM's SSH root runs on a managed Orca server

A recipe VM's hidden SSH target was counted as owned by another runtime, so
every connect refused to deploy a managed server and fell back to the relay.
Its runtime owner only records when the VM is created and destroyed, so it no
longer blocks the deploy. The VM's workspace then lives on that server: the
server adopts the provisioned root as its main worktree, and removing the
project destroys the VM and forgets its server.

* fix(vm): release the server of a relay-era VM that converted on connect

* fix(vm): resume reconnects an SSH VM's server; removal stays on its own VM

* fix(vm): removal and the remove dialog find a VM's server before the catalog loads

* fix(vm): a catalog refresh failure never blocks a VM create

---------

Co-authored-by: m4air <m4air@Mac.localdomain>
2026-10-10 21:39:23 -07:00
OrcaWinandm4air e6af89741f feat(ssh): a paired client can manage the server's own SSH hosts (ssh.target-management.v1) (#27329)
* feat(ssh): manage a paired server's own SSH targets over RPC (ssh.target-management.v1)

* fix(ssh): keep a browsed remote path untrimmed

---------

Co-authored-by: m4air <m4air@Mac.localdomain>
2026-10-10 18:25:54 -07:00
OrcaWinandm4air f361455c73 fix(files): links to this computer in a remote terminal open on the client with trusted provenance (#27234)
* fix(files): a link to this computer in a paired-server terminal opens here when the host disowns it

* fix(files): keep client-local link tabs on this computer; gate the fallback on host support and native clients

---------

Co-authored-by: m4air <m4air@Mac.localdomain>
2026-10-10 05:20:26 -07:00
OrcaWinandm4air fff0bb0d06 feat(runtime): the host refuses a request meant for another runtime (#27224)
A CLI launched from an Orca terminal now sends, with each request, the
runtime it verified before sending (expectedRuntimeSource: the launching
profile's installation id plus the verified runtime id). The host checks
it before parsing params or running any handler and refuses a mismatch
with runtime_source_mismatch, so a runtime that took over the endpoint
between the CLI's check and its send cannot apply the request.

Optional field: an old CLI sends none and an old host ignores it (the
CLI's pre-send probe stays the fence there). Hosts advertise
runtime-source-fence.v1.

Co-authored-by: m4air <m4air@Mac.localdomain>
2026-10-10 03:50:26 -07:00
OrcaWinandm4air 0ba67e1181 fix(mobile): agents for workspaces another runtime owns show why (#27196)
* fix(mobile): agents for workspaces another runtime owns show why

The paired host no longer lists its own agents for a workspace another Orca
server owns: preflight.detectAgents refuses with workspace_on_other_runtime,
and the phone shows 'Agents on Another Server - Pair that server directly'.
The phone also recognises an unambiguous runtime-owned repo row itself, so an
older host is never asked.

* fix(mobile): a shared repo id with a runtime-owned row defers to a refusing host

Rows on several hosts can share a repo id, and the first row's SSH connection
could name the paired host's own target. When any row is runtime-owned the phone
now asks a host that advertises preflight.other-runtime-refusal.v1 to decide by
worktree, and shows the other-server row on an older host.

* fix(mobile): keep the agent-loader change inside the web bundle budget

Move the refusal code beside its capability in protocol-version, read the
capability in the loader, and test the runtime: prefix directly: importing
execution-host into the loader split a web bundle chunk (129 > 128 assets).

---------

Co-authored-by: m4air <m4air@Mac.localdomain>
2026-10-10 02:09:10 -07:00
OrcaWinandm4air 180e8c186f feat(ports): host-scoped port scan and stop on the server (#27188)
* feat(ports): host-scoped port scan and stop on the server

Add workspacePorts.scanHost/killHost, addressed by workspace and resolved on the
server to the workspace's own execution host (this machine or one of its SSH
hosts). Stop is bound to the host its row was scanned on and is refused on SSH
hosts, where ownership cannot be proven. Advertised as
workspace-ports.host-scoped.v1.

* fix(ports): only this host's workspaces authorize Stop; a failed relay scan is not empty

Port ownership probes now come from host routing instead of the repo row's
connectionId, so an SSH repo that carries only executionHostId (or a runtime-
hosted repo) can no longer make a local listener look workspace-owned.

The relay's ports.detect now fails when it could not read the socket table
(unreadable /proc/net/tcp, or neither Windows scanner answering) instead of
answering an empty list, so a host scan reports unavailable rather than none.

---------

Co-authored-by: m4air <m4air@Mac.localdomain>
2026-10-10 01:27:49 -07:00
d3e3b173e6 fix(agents): one host-routed agent-detection entry point (#27054)
* fix(agents): one host-routed agent-detection entry point

preflight.detectAgents / refreshAgents take an optional { worktreeId } and the
host resolves that workspace's project runtime (WSL included) or SSH target.
Gated by preflight.workspace-scoped.v1; old peers keep the host default.

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(agents): route detection by the worktree's own host record

A repo id shared by local and SSH rows made the first row pick the detection
host. Resolve the worktree from the host's list and apply the shared
execution-host routing rule; ambiguous rows refuse. Dashboard falls back to the
host-default list until a workspace's own list loads.

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(agents): refuse an ambiguous workspace instead of probing the host

Only selector_not_found keeps the host default; selector_ambiguous now fails
closed. Adds runtime-level coverage of the lookup.

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(agents): refuse an ambiguously owned folder workspace

Co-Authored-By: Claude <noreply@anthropic.com>

* test(agents): quick-launch runtime target carries the workspace

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: m4air <m4air@m4airs-MacBook-Air.local>
Co-authored-by: Claude <noreply@anthropic.com>
2026-10-09 18:39:13 -07:00
Brennan Benson 59631b8b26 fix(native-chat): edit a queued message in place, even while a question is open (#26886)
* feat(native-chat): edit a queued message in place on the host

A queued card's Edit used to copy its text into the composer and delete the
card, so the message lost its place, an agent-mail card lost its sender, and
while a question or approval replaced the composer the text vanished.

The execution host now edits the card itself:
- agentSession.queuedMessageUpdate replaces only the card's text, as a
  compare-and-set on the body fingerprint the client last saw. It keeps the
  id, position, sender, kind, send mode and attachments; a command card is not
  editable. A resent Save answers from the card as it stands (unchanged or
  changed), so a lost answer never overwrites newer text.
- agentSession.queuedMessageEditHold acquires, renews and releases an
  in-memory edit lease per caller and edit. While one is live, automatic
  delivery stops at the card (earlier cards still send, later ones wait); an
  explicit Send still overrides. Leases expire after two minutes on their own,
  re-derive validity from the card on every read, and die with the
  conversation. Release uses cleanup admission.
- The published card carries an optional editHeld marker, never `paused`.
- Both methods sit behind the new agent-session.queued-message-edit.v1
  capability, independent of the dark mid-turn queue capability, and are on
  the mobile allowlist for the phone follow-up.

* feat(native-chat): edit a queued card inline on desktop

Edit message now opens an editor inside the queued card instead of moving the
text into the composer. The card keeps its place and sender, and the editor
stays usable while a question or approval holds the composer's slot (it does
not lose focus to the prompt card).

- Enter or Cmd/Ctrl+Enter saves, Shift+Enter is a newline, Escape cancels;
  IME composition keys never save or cancel. Save and Cancel replace the
  card's own actions while it is edited; one editor per pane.
- The editor holds the host's edit lease, renewing every 30 seconds and taking
  it again after a lapse. Lease failures are only logged: they never block
  typing, Save or Cancel.
- If the card is sent or removed elsewhere mid-edit, a clean edit just
  closes; changed text goes into the chat box after any existing draft, with
  one toast. A Save that lands and then sends counts as success.
- Against a host without agent-session.queued-message-edit.v1 the card offers
  no Edit (no copy-and-delete fallback); an empty overflow menu is hidden.

* fix(native-chat): name every queued-edit notice in its switch

* fix(native-chat): keep queued-edit typing safe and stop disabling Send elsewhere

- Save with an unreadable answer (dropped for a moved runtime fence, or lost)
  now reads the published card: saved text shown -> close; card gone -> the
  sent-as-saved / chat-box path; otherwise keep the editor quietly.
- Send and Steer stay enabled on a card edited on another device; the
  published editHeld marker is gone (no reader left). Automatic delivery
  still stops at an edited card on the host.
- Save answering changed moves the typing to the chat box and reopens the
  editor on the newer text. Notices say when the chat box is hidden by a
  question or approval.
- Closing the pane or switching chats with unsaved typing appends it to that
  chat's box quietly.
- Lease renewal no longer publishes, so an open editor is not idle-sweep
  activity.
- The consume compare-and-set also matches the body fingerprint.
- Saving unchanged text keeps the original blocks.

* fix(native-chat): keep Agent in the Japanese queued-edit notices, as the catalog does

* fix(native-chat): close a queued edit its own Save already landed, wake the queue for a pruned lease, keep typing across a reload

A Save whose answer was lost now closes once the card shows the saved text,
and a card that went out with exactly the typed text closes quietly instead
of copying it to the chat box. A renew that prunes another editor's lapsed
lease wakes the queue in place of the timer it cancels. A reload, which
unmounts nothing, hands unsaved typing to the chat box like closing the pane.

* refactor(native-chat): move the queued-edit notices to their own module

* fix(native-chat): unref the edit-lease timer in a way the phone's typecheck accepts

The lease module reaches the mobile test program through journal-store, where
setTimeout answers a number, so `timer.unref?.()` failed mobile's tests
typecheck ratchet.

* test(cross-version-wire): call the queued-edit methods in both skew directions

The manifest must name every agentSession method; the two edit methods
reach their host methods on a new build and are only called by clients
on a host advertising agent-session.queued-message-edit.v1.

* test(rpc): count the two queued-edit methods on the structured surface

* fix(native-chat): lay out the inline queued-message editor as a header, field and Save footer

While a card is edited, its row shows a pencil, "Editing message", the sender and an X that
cancels; the field at full width; and a footer with the key hint and Save. The card's queue icon
and its status line are hidden until editing ends.

* fix(native-chat): show the Enter key on the queued-message editor's Save button

The editor footer's muted key hint is gone. Save now carries the same inline
key badge as the new-workspace composer's Create button, extracted into a
shared ButtonKeyHint so both buttons render one badge.
2026-10-09 17:31:28 -07:00
Brennan Benson 573ff0bca3 Save a new chat's first message during create (#26332)
* fix(native-chat): the host owns the send queue; the window keeps no saved outbox

The desktop kept each structured chat's unsent messages in localStorage and
sent them in order, so one message whose fate was unknown froze every later
send, Retry dropped it silently, failed sends could not be discarded, and an
offline chat could send hours later. The host already records every message
and owns the queue; the window now only sends.

- One in-memory sender for composer, launch prompts and messages sent from
  outside the chat. One send in flight per chat; a transport failure resends
  the same id for up to 30 s; a refusal that proves nothing was recorded puts
  the text back in the composer with the reason; a send that went out and was
  never answered shows an in-doubt line with Send again and holds nothing up.
- The host's "unknown" rows get the same in-doubt line and Send again, from
  the journal, in every window.
- A message an older build left in localStorage is never sent: the host's
  conversation outline decides what goes back to the composer, and the copy is
  deleted once that is saved.

* fix(native-chat): send through the structured chat RPC wrapper, with its timeouts

The sender called the runtime RPC directly, skipping the per-method timeouts
every other structured chat call gets. Only a remote host's request skips the
compatibility check the sender already ran.

* fix(native-chat): an unconfirmed send goes back to the composer, with no new row line

The common pattern draws nothing extra on a message whose delivery is in
doubt once its turn is over, and puts a failed send's text back in the
composer with the reason. So a send nobody answered in time, or one a host
answers in a way that proves nothing, goes back to the composer worded as
unconfirmed, and a host "unknown" row shows nothing extra. Removes the in-doubt
phase, Send again, and its strings.

A host's made-up row for an id its journal lost now reads as unconfirmed, not
as recorded, so that message comes back instead of vanishing.

* test(native-chat): pin that nothing resends a send given back as unconfirmed

* fix(native-chat): sends survive a tab close, never resend after a Stop, and keep remote images

- A normal tab close lets sends on their way settle; what the host never took
  goes back to the conversation's draft. Only a cancelled launch or a worktree
  purge drops them.
- After any Stop, a send already on its way is never sent again under its id:
  a doubtful answer, or a resend that was due, hands its text back worded as
  unconfirmed.
- A returned image keeps the SSH connection it lives on; the connection never
  goes to the host.
- An older build's saved message the host recorded and then rejected is left
  to the host's own row, never handed back.

* fix(native-chat): a Stop or a tab close never resends a send already out, and a kept card is the card's

A Stop that landed while a same-id resend was being readied (its timer
fired, its request not out yet) let that resend go out after the Stop.
The sender now tracks whether a request is awaiting its answer: a Stop
hands back every send between attempts as unconfirmed, lets one whose
request is out settle from its answer, and never issues a request after
it. A normal tab close withdraws the same way instead of doing nothing,
so nothing more goes out and nothing is dropped.

A send the host rejected but kept as a card (keptAsQueuedMessageId) is
the card's, from its reply or the journal, and never goes back to the
composer.

Adds the freeze tests: a send whose fate is unknown holds later sends
no longer than its deadline, and one the host can neither confirm nor
deny releases the next at once.

* fix(native-chat): read a resend's turned-away call or reused id as unproven

Ports the send-answer proof contract. A call the host turned away before
running it (method_not_found, invalid_argument, unauthorized) proves only
that this request wrote nothing, so it reads as never sent on a first
attempt only; on a resend an earlier attempt may have landed, and it goes
again under the same id. A resent id the host says was already used for
other content (messageIdReused) proves nothing either, like an expired or
conflicting id.

Pins the rest of the contract: any row the host returns is its own, a
thrown error is no answer whatever its code, and an older build's
refused, held or outlived-Stop copy is handed back once and never sent.

* refactor(native-chat): type the structured composer's send with its attachment type

Keeps NativeChatStructuredSession.tsx within the file length limit.

* refactor(native-chat): drop the kept-card guards the sender never needed

A kept send's row is a rejection that was never a Stop's, so the sender
already reads it as recorded, from its reply or the journal. The test
that pins it stays; the two extra checks only covered a kept row that is
also a withdrawal, which the host never writes.

* test(native-chat): route launch tests' sends through the client wrapper the sender calls

The sender sends through callStructuredAgentSession, but these launch
tests replaced that module with a factory that answered nothing (and still
named a probe that no longer exists), so every launch prompt resent until
its 30 s deadline and the tests timed out. Each factory now hands sends to
the runtime RPC mock the tests already answer, and expectations of a local
send no longer ask for the remote-only compatibility option.

* fix(native-chat): hand a message back without importing the composer's attachment hook

The worktree purge reaches the launch prompt, which hands text back, and
the attachment hook's imports reach the store. A test that builds the
real store behind a mocked one then waited on itself and hung. Handing
back now writes images to the draft store directly, as the hook's helper
did, and a test pins that each returned image keeps its SSH connection.

* fix(native-chat): one send per chat, with no line of sends behind it

A chat with a send out took further messages into an in-memory line and
sent them one by one. A message typed behind one in doubt then hit its
own 30 s deadline and came back as not sent without ever going out.

Now, as the common pattern does, a chat takes one send at a time: while
it is out, Send is disabled and Enter leaves the text in the box. The
sender refuses a second send instead of lining it up, so the 30 s
deadline always runs from the send itself. A Stop or a tab close stops
the one send: settled from its answer if its request is out, handed back
as unconfirmed between attempts, or silently if it never went out.

Notes sent from outside the chat while its send is out stay with their
sender (not ready); a launch prompt that meets the person's own first
message waits in the composer instead of being lost.

* fix(native-chat): give a Stop-withdrawn note back to its chat once its notes were cleared

Notes sent from outside a chat clear once their message is recorded. A
message the host recorded as pending and a Stop then withdrew came back
only to its sender, which had already let go of it, so the text was
lost. The chat's draft now takes it, as an earlier build's outbox did.

* test(native-chat): type the composer-actions probe without a cast

* chore(native-chat): drop outbox wording left in comments and an empty locale group

* fix(native-chat): pace failed checks, keep the host's reason, and hold the chat for its launch prompt

- A send whose checks failed before its request went out (an unreachable
  or incompatible host, an unreadable history) was tried again at once,
  about a thousand times a second for 30 s. Attempts are now paced by
  the attempts made, whether or not their request went out.
- A host that refuses every resend by throwing (native chat turned off,
  a journal that won't open) gave back only "couldn't confirm". The
  host's reason now comes first, still without claiming not sent.
- A launch's prompt now holds the chat's one send from the click, drawn
  as sending, so a message typed while the chat starts can't overtake
  it; it goes out once the chat exists, and a cancelled launch frees it.
- Notes whose send nobody could confirm say so instead of "did not
  accept", and notes launched into a new chat let go of their text once
  that chat's composer holds it.
- An open chat keeps drawing a recorded send until its row arrives, so a
  reply that beats the history no longer makes the message flicker out.

* fix(native-chat): hold a chat's sends until it has started, and send a failed chat's message with its restart

A message sent to a chat still starting went out at once to a host that
had no record of the chat yet, read for a fence it could not get, and
came back as not sent. A message sent to a chat whose start failed
restarted it but no longer went with the restart.

While a chat starts, Send stays off and Enter leaves the text in the box,
as with a send already out. A text message sent to a chat whose start
failed restarts it and goes as the restart's first message, through the
same staged-prompt path a launch prompt takes: it holds the chat's one
send slot, drawn as sending, until the chat exists, and comes back to
the composer if the restart fails again. Notes wait while a chat starts
and ride a failed chat's restart, keeping their text until it is sent.

* chore(native-chat): test the queue request, rejection words and card hand-offs; drop an unused clear

- Pin which sends ask the host to queue, the moved rejection wording,
  and that a queue send whose card was handed off and then refused or
  withdrawn, or whose replay names a withdrawn card, is never handed
  back.
- The send-at-most-once gate now says what the desktop promises after a
  reload: it never sends the id again, and hands the text back.
- The legacy read names when it goes, and drops the notice clear nothing
  called.

* fix(native-chat): keep a sent launch prompt's entry, and give back at once what can't go out

- Cleaning up a launch prompt released its send slot even after the
  prompt had gone out, which deleted the entry the sender keeps once the
  host records it. A launch prompt recorded as pending and then
  withdrawn by a Stop was lost from both the chat and the box, and an
  open chat dropped the new chat's first message before its row arrived.
  The slot now gives back only a reservation that was never sent.
- A send stopped before its request went out by something trying again
  won't clear (this client and the server can't talk, or the host
  refused the history read) kept Send off for 30 s and then said Orca
  couldn't reach the agent. It now comes back at once with its own
  cause: not sent, since nothing went out, or unconfirmed if an earlier
  attempt did. Transport errors keep the paced retries.

* fix(native-chat): notes keep their own text through a new agent's launch

Notes sent to a New agent whose start failed came back to the notes and
also sat in the new chat's composer, so sending both delivered the text
twice. The notes keep their text until it goes out (they hold it from
the click), so the launch now says so and no composer gets a copy, on a
failed start or a refused prompt alike. The tests that asserted the copy
in the composer pinned the old double ownership and now assert the
notes are its only owner.

Notes that rode a failed chat's restart and ended unconfirmed now say
Orca couldn't confirm them, as notes sent directly do, instead of that
the agent did not accept them.

* chore(native-chat): the send-once gate says what the desktop keeps across a reload

The desktop keeps a send's id in memory only: a send still unsettled at a
reload or crash is not resent and not handed back. The coverage notes no
longer credit the renderer tests with durable identity across a remount.

* fix(native-chat): say once why notes sent to a new agent did not go

Since notes keep their own text through a new agent's launch, a prompt
the host refused, nobody could confirm, or that found the chat's send
taken came back to the notes with nothing said anywhere: the chat shows
no notice for text its caller keeps. The notes menu now reports it once,
with the toast a send to an existing chat already uses: not accepted,
couldn't confirm, or not ready. A start that failed still says so in the
new chat instead.

* fix(native-chat): retry a history refusal the host says clears, and name it at the deadline

A send stopped before its request went out came back at once for any
refusal of the history read, including ones the host names as clearing
(its journal briefly unavailable, a chat detached while the host quits),
so the automatic retry was lost. Only a version mismatch and refusals
that won't clear come back at once now; the rest go again on the paced
schedule, and if the deadline still finds nothing sent, the words are
the host's refusal rather than Orca couldn't reach the agent.

* feat(native-chat): a send makes one request, and nothing ever sends it again

The desktop resent a message under its own id for up to 30 s when the
answer was lost. A send now makes exactly one request, as the common
pattern's clients do:
- An answer that is lost, dropped or proves nothing hands the text back
  at once with "couldn't confirm… check the chat".
- A failure before the request goes out (the environment check, the
  history read for the fence, a refused connection) means nothing went
  out: the text comes back at once with its own reason, or as not sent.
- The 30 s cap stays on the one request, so a host that never answers
  can't hold Send.

Nothing ever resends, so nothing can go out after a Stop: a Stop only
takes back a send still in its pre-send checks. The resend state goes
with it (tries, generation, awaiting, stopped, the resend timer, the
send-answers-proof probe, and the first-attempt/resend split in the
evidence), and the send-once gate says the desktop never resends.

* fix(native-chat): notes keep the chat's line, and a send held behind a rewind says it was not sent

- Only a send whose text belongs to the chat's composer clears the chat's line. Notes sent from
  outside the chat no longer wipe a "couldn't confirm... Check the chat" line that explains text
  already back in the box.
- A send the host turns away behind a rewind it could not confirm went back as "not sent" but said
  "couldn't confirm what happened. Check the chat". It now gives the rewind's reason and says the
  message was not sent.
- Reliability gate names the single-request test and records a fresh evidence run; the sender
  test drops its leftover resend mocks and a duplicate Stop test.

* fix(native-chat): a send ends even when putting its text back fails

If writing the returned text into the chat's draft threw, the send never settled: it stayed
"sending", the chat refused every later send until a reload. The send now always ends after a
hand-back, the failure is logged, and the chat's line adds "Couldn't save your message."

* fix(native-chat): a message typed during /clear stays in the box and follows the chat

A /clear moves the chat to a new conversation, and its host refuses any send while it runs. A
message sent then went out, came back refused into the old conversation's draft with its line, and
vanished when the chat moved on: the box and the line now belong to the new conversation.

- A /clear holds the chat's one send slot while it runs: Enter does nothing, Send shows busy and
  the text stays in the box, as for a send that is out. Notes sent from outside get "busy".
- Once it moves the chat, the old conversation keeps taking no send until the view leaves it, and
  what is left of its draft moves into the new conversation's draft, after anything there: when the
  composer's /clear settles, and again when the view moves.

* fix(native-chat): no "Send message?" or queue clear while the chat's send is out

While a send is out (or a /clear runs) the chat takes no message, yet Enter over a held queue still
opened "Send message?", and Clear queue deleted every card before its message was refused. Enter now
does nothing there and the text stays; Clear queue re-checks and deletes nothing if a send went out
after the question opened.

* refactor(native-chat): take the /clear draft carry out of this change

Moving the old conversation's draft into the one a /clear replaces it with fixes a bug main has too
(text left in the box during a /clear stays with the old conversation), so it goes in its own change.
Kept here: a /clear holds the chat's send slot while it runs, and the conversation it moved away from
takes no send until the view leaves it.

* fix(native-chat): a /clear's hold on sends always ends

- A view that unmounted while a /clear that moves the chat was out left the old conversation
  holding its sends until a reload: the late reply kept the hold for a view that was gone. The
  reply now releases it.
- The local call for a conversation command has no deadline of its own, so a /clear that never
  answers kept Send off for good. The hold now also ends at the command's deadline (195 s, the one
  the remote call already uses), whichever comes first.

* test(native-chat): opening a chat an older build left stuck; hand back its copy in send order

Pins, through the real chat hook, sends, legacy recovery and draft store,
what opening such a chat does: the queued messages behind a message the
host recorded in doubt come back to the composer once, in order, with the
"not sent" or "couldn't confirm" wording; the chat is free to send under
its read's fence; nothing happens while the chat has no fence here.

The legacy reader now hands entries back in the order they were sent
(queuedAt), as the older build's reader did, instead of array order.

* fix(native-chat): a send this window turned away for a re-paired server comes back as not sent

A managed server's update rotates its pairing, and this window's main
process then answers the next call itself, before forwarding anything,
with runtime_environment_changed. The send read that thrown answer as
proving nothing, so the person was told Orca couldn't confirm a message
that never left. It is now handed back as not sent, with the reason.
Every other thrown answer still reads as unconfirmed once the request
may have gone out.

* Save a new chat's first message during create

* Align opening-message types with the user-message wire

* Keep chat publication and cleanup outside store initialization

* Complete the create refusal fixture ledger

* test(native-chat): prove a created first message waits for the agent's start and is handed over once

* refactor(native-chat): keep the host and session hook under the line limit after the main sync

* test(native-chat): a chat created with its first message and no pick still teaches its agent's default

After the main sync, a created chat's agent starts from the delivery loop rather than the create
call. Grok (through the real ACP adapter and model catalog) and Claude (through the real adapter)
each still report the model their own config resolved to once the chat reads its options.
2026-10-09 13:05:35 -07:00
Jinwoo Hong 8b2a5e45f1 Re-land Claude per-account folders (#24434, #26405) with the upgrade and proxy fixes (#26801)
* Revert "Revert Claude per-account folders (#24434, #26405) before release (#26638)"

This reverts commit 9ca9b4e36b.

* Move the terminal daemon protocol to 44 for the re-landed claude function

v43 daemons (the revert) lack the claude account function, so new terminals must not reuse them. v42 and v43 stay attachable.

* Move the local build compatibility contract to daemon protocol 44

* Point the old-terminal test at daemon protocol 44

* Claude accounts re-land: never sign anyone out, keep proxies working, accounts mirror ~/.claude (#26769)

* Run a Claude account on System default until it has its own login

An account saved before per-account folders has no login in its folder. While System default (~/.claude, or the user's own CLAUDE_CONFIG_DIR) is signed in to the same email, the router sends every launch there and writes that choice into the pointer file, so nobody is logged out by the update. Once the account signs in to its own folder, the next launch uses it. The WSL router follows the same rule with the guest's ~/.claude.

* Refresh each Claude account folder from ~/.claude before every launch

~/.claude (or the user's own CLAUDE_CONFIG_DIR) is now the master copy. Each account folder gets its whole settings.json, including the proxy address and its key, and every .claude.json key except the login, Claude's account caches and install ids, so first-run questions are not asked again. Every other top-level entry is linked (folders) or copied (files), except Claude's per-folder daemon, jobs, live sessions, login files and throwaways. sessions is no longer shared, and an earlier link is undone.

The refresh runs before every Claude Orca starts, for each new terminal and on account switch. Orca never writes back to ~/.claude. The ledger that tracked selective sharing is gone.

* Route chat auth and unselected-account usage through the Claude router

Chats decided whether to drop inherited Anthropic auth from the selection alone, so an account running on System default lost the user's own key. They now ask the router, as terminals, AI commit messages, automations and usage already do. Usage for an unselected account is read where its launches would run. A test reads every entry point through the fallback and fails if a launch path outside the router reads the Claude selection or builds an account folder. The router's two launch errors move to their own file to keep it under the line limit.

* Show Claude account sign-in prompts only when an account cannot run

With the fallback, an account System default is signed in to already works, so:
- The one-time "Finish setting up your Claude accounts" toast is gone.
- Accounts System default covers no longer need a sign-in: no Sign in in the status bar menu or Settings, and they can be selected.
- The old-terminal banner shows only when claude in that terminal would run a different account than the selected one, and asks again after a switch. Its wording is unchanged.

The retired toast's saved flag stays in the paired-client schema so an older client's write is still accepted. The email comparison moves beside the login reader to keep the router under the line limit.

* Keep the user's shell Anthropic auth on Claude account launches

A signed-in account's launches used to drop ANTHROPIC_API_KEY, ANTHROPIC_AUTH_TOKEN and CLAUDE_CODE_OAUTH_TOKEN from the inherited environment while ANTHROPIC_BASE_URL passed through, so a proxy set in the shell got the subscription login and answered 401. Host launches on an account now keep the shell's auth with its address, as System default does. Chats still name the account kind in a failed sign-in, now from the router rather than from whether auth was dropped.

* Share from ~/.claude only what Claude itself would seed into a new folder

The deny list missed much of Claude's runtime state, so tokens, locks and live files such as .session_ingress_token, server-sessions.json and remote-control were copied or linked over each account's own every launch. The list now mirrors the one Claude uses when it seeds a fresh config folder from ~/.claude: its runtime entries, every hidden entry, daemon files and agent memory. teams, ide, downloads and scratch are on it, so they stay per folder.

* Merge trusted folders and MCP servers into an account instead of replacing them

Each refresh replaced the account's projects and mcpServers with ~/.claude's, so folder trust and allowed tools recorded only in the account were lost, and a refresh landing between Orca's folder-trust write and Claude's start brought the trust dialog back. Both are now merged per folder path or server name: ~/.claude wins for entries it has, the account keeps the rest, and a folder trusted in the account stays trusted. Both writers take Claude's lock on the file, so the order no longer matters.

* Require the same organization before System default stands in for an account

The fallback compared emails only, so one email saved for two organizations made both accounts run on whichever organization System default was signed in to. When the saved account and System default's login both name an organization, it must now match too, on the host and in WSL, and the old-terminal banner uses the same comparison. The saved-account lookup the three checks repeated is now one helper.

* Treat v42 terminals as having the claude account function

v42 daemons shipped the same claude function and pointer path as v44, so claude in those terminals already follows the selected account. The old-terminal banner now shows only for daemons before v42 and for v43, the revert, which dropped the function.

* Fail the routing test when a new module resolves the claude binary

The census only caught code that read the Claude selection, not a new launcher that runs claude without asking the router. A second check now lists every module that resolves the claude binary or loads the Agent SDK, each with why it is routed, and fails on any new one, or on a listed one that no longer matches.

* Skip the account refresh on launches that run on System default

A launch that falls back to System default never uses the account folder, so starting a setup for it on every launch was wasted work. Account switches still set the folder up for a later sign-in.

* Give a terminal opened before the login shell's env only the account pointer

At startup, a pane could be routed before the login shell reported the user's own CLAUDE_CONFIG_DIR, so the fallback compared against ~/.claude.json instead. Until that env arrives a pane gets only the pointer, and the pointer is rewritten once it does, so claude typed there reads the right folder. The recent claude --version answer moves beside the version probe to keep the router under the line limit.

* Drop the chat-only Anthropic auth stripping and override refusal

Chats no longer drop the shell's Anthropic auth on any account, so the chat launch's override refusal and the policy's stripAuthEnv could only be reached from test fixtures. The structured auth policy is now just which login a failed sign-in names. Terminals keep their refusal, which still guards WSL launches; the failure reason stays in the shared vocabulary for older peers.

* Publish a new account state file whole

The first .claude.json write went straight to the target, so a crash mid-write could leave Claude a truncated state file. It is now written to a staged file and linked into place without replacing one Claude created meanwhile.

* Read an unselected WSL account's usage where the WSL router would run it

Usage for an unselected WSL account read its saved folder directly, so an account the guest's ~/.claude covers showed no usage, and the read bypassed the router. It now asks the WSL router, after the same check that the distro is already running.

* Keep the shell's Anthropic auth when the claude function runs an account

The claude shell function (POSIX, fish and PowerShell) unset ANTHROPIC_API_KEY, ANTHROPIC_AUTH_TOKEN, CLAUDE_CODE_OAUTH_TOKEN, AWS_BEARER_TOKEN_BEDROCK and auth-like ANTHROPIC_CUSTOM_HEADERS before running Claude in an account folder, so a proxy set in the shell lost its key while keeping its address. It now only sets CLAUDE_CONFIG_DIR and Orca's marker, as on System default. Protocol 44 is unreleased, so no new bump; the shell-wrapper snapshots are regenerated.

* Never drop the user's Anthropic auth from a Claude launch

Orca's launches stripped ANTHROPIC_API_KEY, ANTHROPIC_AUTH_TOKEN, CLAUDE_CODE_OAUTH_TOKEN, AWS_BEARER_TOKEN_BEDROCK and auth-like ANTHROPIC_CUSTOM_HEADERS for WSL terminals, terminal splits and through the preparation's stripAuthEnv flag, and refused a launch whose agent env set them. If the user's shell overrides the login, that is their setup: every launch now keeps it, on accounts, System default and WSL alike. The flag, the strip paths and the override refusal are gone; the refusal's reason stays in the shared vocabulary so older peers still decode it.

Orca's own process env and the claude --version probe still drop these variables: they are Orca's, not the user's launch.

* Leave out of an account's state everything Claude resets when it signs out

The .claude.json copy left out the login and keys matching cache patterns, but still carried account state such as additionalModelOptionsAnsweredAt, artifactRosterDenied, lastSeenOrgDefaultUpdatedAt and the subscription notices. The copy now leaves out the exact keys Claude resets on logout, beside the install ids, Console API key and first-token date; the cache patterns stay for account caches logout does not reset. Onboarding is still copied.

* Judge an old terminal's Claude banner by the pane's own runtime

The banner compared every old pane with the host selection and showed whenever only a WSL account was selected, so old host panes showed it with nothing to say and old WSL panes were judged by the host's account. The pane now names its runtime: a host pane is compared with the host selection, a WSL pane with its distro's selection through the WSL router, and a pane whose runtime is unknown, or with no account selected for it, stays hidden.

* Leave an account's settings file alone when only Orca's hooks differ from the default home's

* Reread a WSL account's Claude state only when the file changes

* Move tests merged from main onto the account-only Claude auth policy
2026-10-09 15:57:47 -04:00
Jinjing 55eb48c137 Let automations pass extra agent arguments on each run (#26659)
* Let automations pass extra agent arguments on each run

Automations could only use the host's default agent Arguments, so a
per-automation choice such as a different model needed a settings change
that affected every run. Automations can now save extra arguments that
are appended to the host's defaults on each fresh run. Extras are
allowlisted to model, effort, and Claude's --add-dir, and are refused for
permission, config, prompt, and session options.

- Add the field through the CLI (`automations create/edit
  --extra-agent-args`), the RPC contract, the automation store, and the
  editor's Advanced section, with validation at save and at launch.
- Merge extras over the defaults by replacing a model or effort option
  the defaults already set, then re-tokenize to confirm the launch argv
  matches what was validated.
- Gate writes behind a new runtime capability, and refuse to save or
  migrate extras to a host that would silently drop them.
- Require a fresh session, since reused sessions cannot take new
  arguments.

* Simplify automation argument validation and launch handling

* improve the extra args display

* fix translation

* Validate automation extra args during ORCA migration

Dormant automations imported from the migration manifest skipped the extra-args check. A disallowed flag, or Reuse combined with extra args, could persist and then fail every later run. The manifest parser now rejects those rows.

Also move the automation runtime capability constants into `automation-runtime-capabilities.ts`. `protocol-version.ts` re-exports them, so existing imports keep working.

* add more demo with effort
2026-10-08 13:41:11 -07:00
Brennan Benson 4735ecefac fix(native-chat): a /compact sent while the agent works waits in line instead of being refused (#25704)
* fix(native-chat): a /compact sent while the agent works waits in line instead of being refused

* test(native-chat): build the admission test's submissions without a type assertion

* fix(native-chat): a /clear refused while the agent works says so plainly; a /compact behind an unsent message waits for it quietly

* test(native-chat): name the admission test's cases for the sends they model

* fix(native-chat): a /compact refused while the agent works reads plainly too; a command card offers no Edit

* test(native-chat): a refused /compact reads the plain sentence

* fix(native-chat): a refused command card is spent and said once; a command card offers Send while the agent is idle; /compact behind an unsent message is not taken

* test(native-chat): keep the queued receipt's narrowing inside the wait

* fix(native-chat): spend a command card only when its own turn says why; refuse a queued command without a hand-off, with its reason; a queue send on its way reads as sending

* fix(native-chat): a recorded send is no sending card; queue lists land at once; a spent card's wait says to run it again; only /compact or /clear give way to returned text

* refactor(native-chat): derive the sending cards inside the queued-messages hook

* fix(native-chat): keep protocol-version and the agent-session params under the line limit; the send capabilities get their own module

* fix(native-chat): with the agent idle, a command behind this window's own unsent message says it is still being sent

* fix(native-chat): the queued-commands capability stays a plain constant beside the others; main's send capabilities back to its text

* test(native-chat): keep the rewind suite under its line limit after main's growth

* fix(native-chat): one import per module in the command send; a rewind on its way holds commands, tested

* test(native-chat): type the rewind module mock with a type import

* test(native-chat): a command card cut short by a restart waits or is kept like any card; kept-card captions name only controls shown

* fix(native-chat): a held command card, which the queue skips, no longer forces later sends to queue

* fix(native-chat): a command's refusal line goes once what it waits on is gone

A refusal that names something the chat shows (the agent working, a pending prompt, background
tasks, an unsent message) is said only while that still holds, derived each render, and dropped
once it ends. Other composer errors are unchanged.

* fix(mobile): a command's refusal banner goes once what it waits on is gone

The phone's banner keeps a refusal naming the agent working or a pending prompt only while the
chat still shows it, dropped each render once it ends. Background tasks, which the phone can't
see, and generic errors behave as before. The host-reason mapping is shared with desktop.

* fix(native-chat): a refusal behind an earlier message names whether it is being sent or offers Retry

Each line now goes when its own state ends: a send that fails stops being sent, and a resend
stops offering Retry.

* test(native-chat): a Stop behind a typed /compact keeps the taken-back message on screen; the in-doubt case says so

* fix(native-chat): a command refused while the queue is about to send reads working, as the pane does

* Update held command fixture for the merged queue model

* Restart one host at a time in the recovery ordering test

* fix: preserve person queue reserve for chat commands
2026-10-08 13:32:20 -07:00
Jinwoo Hong 9ca9b4e36b Revert Claude per-account folders (#24434, #26405) before release (#26638)
Users with a saved Claude account were signed out of every Claude feature in Orca until they signed in again, and proxy setups (API key plus ANTHROPIC_BASE_URL) got 401s after signing in. Reverting before release to rework the upgrade path. The terminal daemon protocol moves forward to 43 so dev builds' v42 daemons with the claude function are not reused; v41 and v42 stay attachable. Nothing is deleted: per-account folders are left unused and the old saved-account store was never touched.
2026-10-08 14:37:17 -04:00
Neil b41e2730db Persist multiple workspace references with atomic edits 2026-10-07 22:44:25 -07:00