* feat(cli): set the title of one terminal pane
Adds `orca terminal set-pane-title --terminal <handle> --title <text>`; `--title ""`
clears it. The runtime resolves the live handle to its saved leaf and asks the
desktop window to name that pane through a new optional notifier hook
(`ui:setPaneTitle`). Mounted panes apply it through the same local setter the GUI
uses; parked panes get it in the saved layout their next mount reads. The tab's
own name is never touched.
Rebuilt on current main from #24755.
Co-authored-by: stevelliu <stevelliu@tencent.com>
* fix(renderer): a main-owned layout takes a CLI pane title before the window shows it
* fix(renderer): a CLI pane title reaches a never-mounted tab with a rootless layout
---------
Co-authored-by: stevelliu <stevelliu@tencent.com>
* refactor(ssh): move the ssh-connection and ssh-target cluster into ssh/connection/ and ssh/targets/
Pure git mv plus import and path-literal rewrites. ssh-connection-utils stays put; the next PR splits it.
Co-Authored-By: Claude <noreply@anthropic.com>
* test(e2e): keep the release-checkout import at the v1.4.218 path
Co-Authored-By: Claude <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
Thirteen read-routing callers (#4, #5, #10-#20 of the round-3 caller list) read the owner through
runtimeEnvironmentIdWithLegacyFallbackUnlessTwin. A workspace two hosts publish is no longer
searched on either host. The focus-read ratchet now counts the legacy-fallback adapter, which
reads the focused runtime inside an object literal its discovery skipped; the five F1 entries
that blind spot pruned come back. Deprecated importers 53 -> 40.
* fix(layout): editor file records keyed by path and owner, so two hosts' files at one path stay apart (layout-owner build only)
Opening a runtime-environment file at a path a local file already held replaced the local record,
the stored-session patch and unsaved drafts matched by path alone, a move took every owner's record
of the path, and closing a bare-path tab removed both owners' records.
* fix(mobile): a windowless host's editor tab under its owned id shows as active, and retiring it clears the active file (layout-owner build only)
* fix(layout): a layout change's tab and pane holds end 15 s after it was sent when main never answers (layout-owner build only)
* fix(layout): main's view reads and prunes keep an active file under its owned id (layout-owner build only)
* refactor(orcad): move startOrcad's option and handle types into their own module
Move-only: frees room in orcad-entry.ts for the layout-owner load repair.
* fix(orcad): run main's duplicate-terminal pane repair at orcad startup (layout-owner build only)
orcad builds its own runtime and never called the load-time repair, so an
orcad host kept a terminal drawn in two panes.
* test(orcad): type the hoisted test state without an assertion
* fix(orcad): await the startup pane repair so it lands before reconcile and RPC
* fix(startup): desktop awaits the duplicate-terminal repair before the window and RPC start (layout-owner build only)
Open-in owner, execution-host platform facts, doc-preview grants and the activation PTY inventory
read the owner through resolveOwnerWithLegacyFallback. Open in now refuses a twin instead of
launching it as this desktop's checkout; the others keep reading a twin as not server-owned. A row
whose only repo row is a server proxying its SSH target routes through that server. The
creation-action policy stays until browser creation folds with it. Deprecated importers 57 -> 53.
* fix(browser): switch client-hosted profiles with staged guest replacement
Squashed from #30351 by @oliver-mee onto the current main.
Fixes#30350
* fix(browser): fence staged profile guests, release switch grants, carry host profile metadata
- A fence during automation retirement now revokes and stops the staged guest.
- A failed or late-settling switch releases its execution-host grant so the page can be recreated.
- Staging cleanup failures before handover stay visible to executor close.
- createPage/restorePage carry host-owned profile metadata to profile-switch-v1 desktops,
so recovery and restore can rebuild a switched page.
* fix(browser): roll back a switch whose replacement died, and report a lost old guest
* fix(browser): resolve client-hosted pages before the server-local target path when switching profiles
* fix(browser): a staged profile-switch guest owns downloads it starts while loading
* fix(browser): keep the original placement until a timed-out switch settles
* fix(browser): rebind the original guest before releasing a rolled-back staged guest
* test(browser): type the harness guest release mock
* fix(browser): release the replaced generation's download transfers when a switch commits
* fix(browser): admit file transfers from a staged profile-switch guest while its switch is pending
* fix(browser): release only the replaced generation's download transfers after a switch
* refactor(shared): import BrowserNetworkExecutionHost from its own module, no re-export
* fix(browser): adopt a profile switch the client finished after the caller timed out
---------
Co-authored-by: Oliver Mee <102673257+oliver-mee@users.noreply.github.com>
* refactor(renderer): one owner adapter for the deprecated-resolver folds, pinned by an acceptance table
resolveOwnerWithLegacyFallback answers as resolveOwner for a named host and for rows that carry a
host stamp, and keeps the deprecated answer (single focused runtime included) for missing and
unstamped rows. A full-owner acceptance table pins all matrix cases and asserts the only changes
from the deprecated resolver are the designed ones: twins and contested SSH hosts become ambiguous,
and a stale hub stamp routes through the server that names the target.
* refactor(renderer): the fold adapter shares resolveOwner's named-host rule
Clean-code pass: one namedHostForOwnerRef for both resolvers, floating counts as host evidence,
and the acceptance test drops non-null assertions.
* fix(windows): codex and claude get a prompt's double quotes in Windows PowerShell
Windows PowerShell 5.1 (and pwsh before 7.3) does not escape an embedded
`"` when it builds a native program's command line, so every quote in a
launch prompt was dropped, and quotes could split the prompt into extra
arguments. Orca's PowerShell codex and claude wrappers now escape their
arguments at runtime for the PowerShell that is actually running, through
one shared __OrcaNativeArgs function. pwsh 7.3+ already escapes, so it
gets the text untouched.
* fix(windows): escape agent args for .cmd shims under pwsh 7.3+ too
pwsh 7.3+ defaults to the Windows argument mode, which still passes
.cmd/.bat targets the Legacy way, so a codex or claude .cmd shim kept
dropping quotes there. The function now takes the program it escapes
for and names the modes that escape by themselves, and anchors the 5.1
trailing-backslash fix at the true end of the text (`$` also matched
before a final newline). Tests now run the real codex wrapper with a
quoted prompt, cover pwsh's Legacy mode and the newline case, and pin
the claude wrapper's call.
* fix(windows): strip comment lines from the PowerShell bootstrap payload
The shared escaping function pushed the encoded bootstrap to 27,324 of
the 28,000-character budget, leaving almost no room for a startup
command to ride in -EncodedCommand, so short commands fell back to
stdin (the path #7236 broke). Comment lines document the source but
only spend that budget in the payload; dropping them frees 3,732
encoded characters, more headroom than main had.
* fix(windows): quote agent args with "" where cmd.exe or Windows PowerShell reads them
Escaping embedded quotes as \" let pwsh 7.3+'s .cmd quoting expose a
prompt's `&` to cmd.exe, which then ran the rest as a command. Where
cmd.exe reads the line, or Windows PowerShell counts quotes naively,
the function now quotes the argument itself and writes each quote as
"", as process-host's windows-command-line.ts already does. That also
delivers text 5.1 used to strip, so its special cases go away.
The codex and claude wrappers now come as one fragment with the
function they call, the bootstrap test pins the startup-command budget
instead of restating the strip, and the cwd-restore comment leaves the
payload too.
---------
Co-authored-by: Orca Worker <orca-worker@localhost>
* fix(native-chat): clicking Send or Stop keeps focus in the composer
A click focused the Send/Stop button; when it disabled (empty draft after the
turn, or while a Stop is in flight) Chromium dropped focus to <body>. A pointer
press on it no longer moves focus, matching Enter. Adds an e2e regression with
a turn-capable Claude stub. Fixes STA-10897.
* Tighten comments
* feat(sidebar): the owning host raises the workspace unread flag (dark)
* fix(sidebar): phone session open clears and spares the flag; refresh views; blur rescan
* fix(sidebar): throttle failed view retries, resend failed empty views, hold presence one refresh longer
* test(sidebar): switch-on window writes neither the unread flag nor a stamp
* fix(usage): price Claude long-context requests per request, and price Haiku 5.5
Haiku 5.5 bills a whole request at higher rates once its prompt (input plus
cache reads and writes) exceeds 100K tokens. Claude usage was only kept as
daily totals, so that could not be priced and Haiku 5.5 stayed unpriced. The
existing Sonnet 4.5/4 200K tier had the opposite problem: it was applied as a
marginal threshold to each day's totals, so a day of short requests could be
surcharged and a single long request was under-charged (the Claude side of
STA-8117, fixed for Codex in #22360).
Each turn is now classified when it is aggregated, against its own model's
threshold, and daily and per-location rows carry the long-context subset as
an optional `longContext` field. A streamed turn that grows across the
threshold moves its whole share in the resume correction. estimateCostUsd
takes one token object and bills that subset at the long-context rates and
the rest at base, replacing the per-bucket tiering.
Saved per-file projections carry a projection version; files from an older
version are reparsed on the next scan, so history picks up the split without
touching the signed cache schema.
* fix(usage): keep the projection version out of the integrity hash
Hashing it made older inline reports fail validation and show the
'could not be validated' error; the scanner's version check alone decides reparse.
* Check sign-in duplicates against the listed account identity
A managed account folder's current Claude login can differ from the identity saved for it. The account list shows the current login, but the duplicate check compared the saved identity, so a sign-in could be missed or wrongly flagged. Duplicate detection now uses the same listed accounts as the account list, and the duplicate helper accepts account summaries. Tests cover a folder whose login email changed, and one where only the organization changed.
* Tolerate invalid Claude account rows during sign-in
normalizeModelForPricing was an ordered chain of regexes in which a new point
release fell through to its major: claude-sonnet-5-5 was billed as Sonnet 5
(cache reads at $0.20 instead of $0.10), and any future opus-5-x / fable-5-x
would be too. claude-haiku-5-5 matched nothing and silently added $0 to a
total that showed no caveat.
The resolver now parses family + major + optional minor (family-first and
legacy version-first IDs) and only accepts an exact MODEL_PRICING key, so an
unknown version is unpriced rather than borrowing another model's rates. The
deliberate opus-4.x -> 4.8 and sonnet-4.x -> 4.6 fallbacks go with it.
Adds Sonnet 5.5 and Mythos 5/5.1 rows. Haiku 5.5 stays unpriced: it reprices a
whole request above 100K prompt tokens, which daily totals cannot express.
The Claude summary now carries hasUnpricedModels (as Codex has since #22073),
and both panes share one unpricedCostLabel helper for the caveat.
Fixes#30338
* fix(cloud-auth): show the browser sign-in wait and offer its link from Relay sign-in
Clicking 'Sign in for Relay' handed the sign-in page to the OS browser and
then showed nothing, so a browser that never came forward looked like a dead
button, and the 5-minute wait ended silently. The Relay sign-in row now shows
the wait with a Copy sign-in link button, and a wait that times out toasts.
STA-10940
* fix(cloud-auth): show the sign-in wait on every Orca sign-in button
The waiting text and Copy sign-in link now come from one shared notice used by
Mobile Relay, Orca Account, Artifacts (settings, page and publish popover),
Share Skills, the unexpected sign-out card and dev tools. The copyable link is
dropped as soon as the browser answers, and a copy with no waiting link
reports a failure instead of doing nothing.
STA-10940
* refactor(cloud-auth): main owns the waiting sign-in link; renderer only mirrors it
The notice now shows exactly while main holds a live link: main announces the
link when the browser wait starts and clears it the moment the browser
answers, so 'Waiting for sign-in in your browser' no longer outlives the wait
during the token exchange. Only the newest attempt may publish its link, so an
older attempt reporting late cannot replace or later wipe it. The renderer's
separate pending flag and its resets are gone.
STA-10940
* fix(cloud-auth): tolerate a sign-in link read that returns no promise
The app-wide IPC tests stub every bridge method as returning nothing, and the
one-time link read called .then on that result.
STA-10940
* fix(cloud-auth): a torn-down link subscription ignores its late snapshot; restore auth bridge tests
A window's one-time sign-in link read could land after its subscription was
disposed and overwrite a newer value from the replacement subscription. The
auth-status refresh tests the earlier rewrite dropped are back, alongside the
link cases.
STA-10940
* feat(cloud-auth): sign-in buttons show Signing in… with copy-link and cancel links
Every Orca sign-in button greys out to 'Signing in…' with a spinner until the
sign-in ends. While the browser has not answered, a line offers 'Copy sign-in
link' and 'Cancel'; Cancel aborts the browser wait so a lost tab never locks
the button. Main owns the in-flight sign-in (waiting on the browser, then
finishing) and every window mirrors it.
STA-10940
* fix(open-in): open a managed server's workspace in VS Code over Remote-SSH (#30332)
* fix(open-in): only open a server workspace over SSH when its files are on that host; validate the SSH link; let process-host quote code.cmd
* test(open-in): expect the workspace ref in the source-control Open in payload
* fix(open-in): pin an SSH-access server's editor host to the settings it was verified on
applyCreatedWorktree merged a create into any same-id row, so a local create could inherit a
server twin's stamp (or wipe it). Lineage refresh stamped a server's reply with the focused host and
replaced every same-id row, overwriting the local twin. Both now match id and host, and the lineage
reply is stamped with the transport it came from.
* fix(browser): a remote viewer larger than the host's browser pane sees the whole page and clicks land under the cursor
Fixes#30341
* test: note why the screencast double is cast
* fix(browser): drop guest frames until the fitted size is applied; forget the cached CSS size when the frame size changes
* fix(browser): keep a press's release and markup inside the letterboxed remote frame
* fix(browser): resume live frames once fitted metrics land; remeasure the page after its frame size changes
A tab restored before its worktree catalog loads gets a placeholder row. SSH placeholders had no
host, and a server's nested-SSH placeholder carried only runtime:E, so an ssh: host selection found
no matching owner and routed the terminal as a direct dial from this desktop. Stamp the SSH target,
and the server proxying it, from the repo row; leave a repo id that lives on several hosts unstamped.
The three ipc modules re-exported symbols owned elsewhere. Importers and test mocks now name the
defining module, and the re-export lines are gone (no barrels).
* fix(layout): a CLI split whose pane main closes before it mounts answers at once (dark, layout-owner build only)
* test(layout): the closed-before-mount split fails because of the close
* Open file links beside their source tab's group
Clicking a file link in a terminal or agent chat now opens the file in an editor next to that tab, not in whichever group happens to have focus. A new "Open in this group" row pins the file to the source group when that group has neighbors.
- Capture the source tab at click time, so focus changes during path resolution don't redirect the file.
- Rank candidate groups by adjacency: a neighbor already showing the file, then a neighbor showing an editor, then any neighbor, then the source group.
- "Open in this group" never falls back to a browser, system app, or other group. If the group is gone or the target is not an editor, it shows a toast and does nothing.
- Line reveals target the exact view that was opened, and are dropped if that view closes before the editor applies them.
* rm scratch files
* Preserve empty split panes during tab reconciliation
Reconciliation removed every empty group, so an empty split pane the user had made beside a tab was collapsed on the next pass. A file link opened from that tab then landed in the source group instead of the empty pane. Reconciliation now removes only groups that stale-tab cleanup emptied, and keeps panes that were already empty. Tests cover terminal and agent-session sources in both worktree and folder workspaces.
* Preserve runtime owner for local file links
* fix(drops): name extensionless dropped images by their bytes
A file dropped onto a remote workspace with no extension (some drag sources
hand images over as `noname`) now gets the extension its PNG/JPEG/GIF/WebP
signature declares, so the agent sees `noname.png`. Repo icon detection
reuses the same signature check.
Fixes#30378
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(drops): sniff extensionless images only for .orca/drops and chat attachments
Explorer imports into a user-picked remote folder keep the name, matching
local imports. Chat attachments on a paired server now use the staged name
so they get the extension too.
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Neil <neil@stably.ai>
A client's pane rename already lands in the host's saved layout (titlesByLeafId),
but terminal.list/show never read it, and clearing a remote tab's last pane title
sent no titles at all, which every host reads as 'unchanged'.
- terminal summaries carry an optional paneTitle from the host's saved layout;
the CLI list prints it ahead of the terminal title. Older hosts omit it.
- clearing the last pane title of a remote tab pushes titlesByLeafId: {}, which
old and new hosts both persist as cleared.
Supersedes #30333, which renamed the whole tab via terminal.rename.
Co-authored-by: wangshifeng <shifeng.wang@magic-shield.com>
Every renderer sender of layout.apply (sendLayoutApply, recordLayoutApply, the activation
terminal create and the live terminal adoption) returns before the call for a worktree or
folder workspace a paired server owns, and a source census pins that no other renderer file
sends layout.apply.
* Accept host aliases and infer the host for a single-setup project
`orca worktree create --project scalp-it` failed with "Project is not set
up on the selected host" even though the project has exactly one ready
setup, and `--host trader-local` was rejected as an invalid value even
though `orca host list` prints that name. Three separate causes:
- `--project` was matched against the stored `projectId` only, which is a
provider-scoped string (`github:owner/repo`). The display name shown by
`orca project list` never matched, so the lookup found nothing and the
failure was reported as a missing host.
- With no `--host`, the host fallback existed but was unreachable, because
the project lookup above had already filtered to zero candidates.
- `parseExecutionHostId` accepts only `local`, `ssh:<id>` and
`runtime:<id>`, so a bare alias could not parse at all. `ssh:<label>`
already resolved labels; only the unprefixed spelling was missing.
Now:
- `--project` takes the project id or the display name, resolving through
`project.list` and falling back to the setup's own display name for
hosts that predate that method. Two projects sharing a name are refused
rather than guessed.
- `--host` is only required when the project is set up on more than one
host. A single setup is used directly; an ambiguous one fails with every
host and the flag to select it.
- `--host <alias>` resolves against both axes: paired servers by name
before routing, SSH targets by label once a client exists. An alias that
names a machine on both is refused with the prefixed flag for each.
Errors now echo the flag the caller typed instead of an `ssh:` spelling
they did not use, and carry the hosts to retry with in `nextSteps`.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Refuse a host alias that collides with duplicated SSH labels
assertAliasNamesOneHost asked findSshTargetByName whether the alias also
named an SSH target, but that function returns undefined for a label two
targets share — deliberately, so an ambiguous label never resolves. Read
as a boolean it says "no SSH target by that name", so an alias naming one
paired server and two same-labelled SSH targets looked conflict-free and
resolved to the server.
The opposite direction was already covered: a unique SSH target against
two servers sharing a name fails through assertEnvironmentNameUnambiguous.
Fall back to ambiguousSshTargets so both axes refuse, and list every
colliding target in nextSteps rather than only the first.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Only fall back to setup display names on a server without project.list
findProjectsByName caught every error from project.list and returned an
empty list, so a network failure, a permission error, or a genuine server
fault was indistinguishable from "no project by that name" — and the
caller was then told `Project is not set up on the selected host` for a
project that exists and is set up.
Narrow the catch to method_not_found, the only code that means the weaker
fallback is warranted, matching callProjectHostSetup and listSshTargets in
the same layer. The function now returns null for that version gap and an
empty array for a real miss, so the two cases stay distinguishable at the
call site.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Refuse a legacy display-name match that spans two projects
The pre-project.list fallback matches ProjectHostSetup.displayName, which
is the repo's folder name rather than the project's name, so two projects
holding repos with the same folder name both match. selectSetupOnHost
would then have taken the first candidate — targeting a project the caller
never named.
Reject when the matched setups span more than one projectId and name each
one, the same rule the project.list path already applies to duplicate
display names. A single project with several setups is unaffected; the
host selection below still decides between them.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Spell the --host placeholder as <host> in the project setup usage lines
The worktree create usage was updated to <host> when aliases became
valid, but the four project setup-* lines kept <host-id> in both the
command specs and the root help text they mirror. Those commands accept
an alias too, so the narrower placeholder hid it from anyone reading help.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Document host names and the optional --host in the CLI docs
The published CLI reference described `--host` as taking only `local`,
`ssh:<target-id>` or `runtime:<environment-id>`, and the automations page
showed `--host <hostId>` as required alongside `--project`. Both now
understate what the commands accept, which is how the original report
started: `orca host list` prints a name, the docs never said you could
type it, so the generated id looked mandatory.
Spell out that `--host` takes the bare name from `host list` on either
axis, that a name resolves only when unique and is otherwise refused with
the prefixed flag for each candidate, and that `--host` is unnecessary
when the project has a single setup. Also note `--project` accepts the
display name from `orca project list`.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Name the runtime host id as the paired-server tie-breaker in the docs
The host-selection page told readers to fall back to ids when a name is
ambiguous but only ever showed `--environment <server-name>` for paired
servers — and that selector is exactly the one that cannot separate two
servers sharing a name. Spell out the pair the resolver actually emits in
its nextSteps: `--host ssh:<target-id>` and `--host runtime:<environment-id>`.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Spell the automations create usage as --project <id-or-name> [--host <host>]
`automations create` resolves --project and --host through the same
alias-capable resolver as `worktree create`, and its own notes already say
--project takes a display name and --host takes a bare host name. The usage
line still read `--project <id> [--host <id>]`, the last spot left on the old
spelling (flagged by CodeRabbit after the main merge).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Give the fake runtime client cast in the host alias tests its SAFETY note
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(cli): accept every name host list and project list print, everywhere --project/--host is taken
Extends the alias work to the commands it did not reach and the names it
missed:
- `project setups --project <display name>` filtered by projectId only.
- `project setup-existing-folder|setup-clone|setup-create --project <name>`
now send the project id that name resolves to (pass-through for ids and
for servers without project.list).
- `--host "this machine"` (the local row's name) resolves to local, and is
refused if an SSH target or paired server shares it.
- One pair of wrapping quotes is ignored, since cmd.exe passes single
quotes through literally.
- With --environment/--pairing-code, a bare --host name is a machine the
routed server reaches, not a different locally paired server.
- An unknown project is reported as a project miss, not a host miss.
Co-authored-by: Younghwan Chae <chyohw97@gmail.com>
* fix(cli): keep exact project ids over names, and refuse an unverifiable host name
An exact --project id whose only setup is not ready no longer falls through
to a project whose display name equals that id. A bare --host name that
matches a paired server now routes there only once this machine's SSH
targets were actually read, and under --environment/--pairing-code a bare
name always means a machine the routed server reaches.
Co-authored-by: Younghwan Chae <chyohw97@gmail.com>
---------
Co-authored-by: younghwan <chyohw97@gmail.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
* 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
* Resume CLI conversations directly in native chat
* fix(native-chat): preserve resume refusal behavior and honest transcript feedback
* fix(native-chat): retain import read evidence in typed result
* Use the header test fixture record type
* Consolidate launch owner imports and correct feedback assertion
* Keep existing resume translation in runtime catalog
* 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
* Keep deferred chat publication in the shared launch owner
* test(chat): allow host evidence in create routing
* Satisfy launch publication checks and scope slot cleanup
* Preserve canonical tab membership on projected reorders
* Use recorded launch ownership for surface recovery
* Reserve deferred chat order through existing group actions
* Pass the active layout owner into launch cancellation
* 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
* test(native-chat): pass the layout owner in the layout-owner cancel test
main's new test predates launch cancellation taking the active layout owner.
* 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.
* fix(native-chat): keep a History resume that joins a terminal resume alive when the terminal closes
A History click that joins a pending terminal-tab resume now takes the
launch over: the terminal hand-off runs at join time and the create
resumes by conversation id, so closing the terminal before create no
longer abandons the shared launch.
* refactor(native-chat): move the launch source hand-off into its own module
* fix: pick the chat that adopts a header resume the way History does
* feat(cleanup): the owning host records last terminal use for cleanup's last active (dark)
Owner decision Q2: with ORCA_SIDEBAR_INPUTS=main, the host that owns a
workspace writes lastTerminalUseAt when a terminal takes input (window, phone
or CLI, through the one input funnel) or a phone attaches, at most once a
minute per workspace. Output never counts. Cleanup's last active reads it
next to the Recent stamp, so a workspace used only from the phone never looks
idle. Never a sort key.
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(cleanup): count only typed input, skip paired-server terminals, share record routing
---------
Co-authored-by: Claude <noreply@anthropic.com>
* fix(shortcuts): Cmd+C no longer pastes on remapped Latin keyboard layouts
On macOS the main-process paste intercept matched `code === 'KeyV'` OR
`key === 'v'`, so on Turkish F (where the C letter sits on the physical
KeyV) Cmd+C was routed to ui:appMenuPaste. Trust the produced key when it
is a Latin letter or digit and only fall back to the physical code for
non-Latin layouts (Cyrillic etc.), matching the shared keybinding matcher.
Fixes#29574
Claude-Session: https://claude.ai/code/session_01TYbS9P1pnW4WRN7bGpuNaW
* test(shortcuts): cover Dvorak, Korean and IME composition for the macOS paste intercept
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* refactor(shortcuts): match the macOS paste intercept by produced key token, in its own module
The produced key token wins on every Latin layout, punctuation included (BEPO puts . on KeyV); non-Latin and IME keys fall back to the physical key.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: İzzetEmre <emreizzet@hotmail.com>
Co-authored-by: Jinwoo-H <jinwoo0825@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* test(sidebar): the desktop window and a feed reader draw the same list (dark)
* fix(sidebar): a feed reader lists a paired server's folders and nested workspaces as the window does (dark)
* fix(sidebar): the paired-server shadow compares the lineage the server's check keeps (dark)
* fix(sidebar): paired-server rows carry the unread flag Smart Done reads (dark)
* Settle Codex review turns from their session completion
* Preserve interruption relay metadata when settling Codex reviews
* Keep the Codex turn id across status writers and settle through the existing host verdict path
The turn id that matches a rollout turn end was dropped by every lead writer
that did not pass it, so a failed turn after a child update never settled.
Drop the synthetic Stop label: the unlabelled poll update already carries the
host's main-agent verdict, including a failure, to current desktops. A remote
desktop now takes the host's settled verdict for a late hook, and a rollout
settle retires hook-only children like Stop does.
* Retire Codex children through the poll and start a fresh turn on any non-compaction session start
A rollout-settled turn deleted the child roster, so the local reconcile
re-seeded the child from the last row as running and the pane stayed Working.
Clear it instead, only from the poll (a child hook settling the root must not
drop that live child), and let a remote desktop clear its roster when the host
reports a done turn with no children. A resumed or forked session no longer
inherits the previous turn id. Also drops accidental pnpm-lock churn.
* Replay a Codex rollout turn end as the root Stop it stands in for
Codex fires no Stop for internal subagents such as /review, so the rollout's
task_complete was settling the main agent through a second path that every
other status writer (local and remote reconcile, child hooks, polls) had to
learn about. The transcript poll now replays that end as an ordinary root Stop
through hook normalization, and a root hook for an already ended turn is
classified as that Stop. A failed end carries its failure outcome and is
published unlabelled like a cancellation, since desktops derive a labelled
Stop's outcome as success. The separate settle path, the remote host-verdict
adoption and the child-hook rollout reads are gone.
* Keep an ended Codex turn retired across compaction, and keep WSL polls alive
A compaction SessionStart queued before the next prompt carried the previous,
already ended turn id, so the poll replayed its Stop: a Done flash and a
second "finished" notification. A compaction now keeps the turn only while
it is still running and its rollout end is unrecorded. The WSL guest
rescheduled its transcript poll with the update instead of the stored row,
so the poll stopped after its first update and a later turn end never
replayed there. A Stop also clears the child roster instead of deleting it,
so a reconcile cannot re-seed retired children from the previous row.
* Restate an ended Codex turn's hook in provider dispatch
Keeps the shared hook normalizer within its size limit after main's merge;
the Codex dispatch returns the event it normalized, which the envelope uses
for the published name and prompt classification.
* Type the rollout record helper in the Codex turn-end test
* Accept Codex's turn_complete name and keep only replayed Stops labelled
The rollout reader also records turn_complete, the name the existing
interruption reader already accepts. A poll row that is not a replay no
longer restates the last hook's event name, so only a replayed Stop can
carry the Stop label.
* Drop a delayed Codex hook for an ended turn the lead has moved past
A late root hook for an older finished turn was restated as Stop and marked
the newer running turn Done; passing it through would instead rewind the
lead's turn id so the poll replays the old end.
* feat(sidebar): Smart Done means finished and unread (dark)
Owner decision Q1: with ORCA_SIDEBAR_INPUTS=main, a Done row holds its tier only
while the workspace unread flag is set, so opening the workspace or sending the
next prompt ends it. Reuses the existing flag; no new state or timer.
Co-Authored-By: Claude <noreply@anthropic.com>
* test(sidebar): reading a workspace drops its Done row in the live list
Co-Authored-By: Claude <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
* fix(macos): the TCC prompt watcher's log stream dies with Orca
log stream only writes when its predicate matches (about never), so it never
gets SIGPIPE when Orca dies without running stop() (crash, SIGKILL, e2e
teardown, force-quit dev builds) and lived on under launchd. One dev machine had
126 orphans holding 566 MB and ~40% CPU. A /bin/sh wrapper now holds a
never-written stdin pipe from Orca and kills log stream on EOF.
* fix(macos): reclaim TCC log streams that older builds orphaned
The wrapper stops new leaks, but users upgrading still have every log stream an
earlier crash or force-quit left under launchd. Ten seconds after launch, once
per process, Orca terminates launchd-parented processes running exactly the
watcher's old command line. A live watcher's log stream is parented by its
wrapper, so it never matches.
* test(macos): document the fake log stream cast
* fix(macos): arm log stream wrapper trap before fork; verify orphan executable
* fix(macos): re-read orphan identity before SIGTERM; disable inherited errexit in wrapper
* fix(layout): a workspace only main held shows main's tab on the window's first visit (dark, layout-owner build only)
* fix(layout): a live terminal no pane shows goes through main, which names the tab it holds; a pane close main has no pane for closes here (dark, layout-owner build only)
* test: no type assertions in the CLI-first tests
* refactor: the switch-off build keeps today's live-terminal mint as it was
* review: frame sync under the line limit; the first-visit test names exactly main's tabs
* fix(layout): adopt takes the recorded pane only while it is unbound
* fix(layout): a refused live-terminal adoption is declined, not counted as shown
* 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.
* refactor(sidebar): the stamp-sorted window list runs no settle, latch or saved order (dark)
With ORCA_SIDEBAR_INPUTS=main the window's sort hook is a pure read of the
rows' stamps: no settled-epoch store listener, no Smart latch, no saved Smart
order writes, no class telemetry over the old attention map. Switch off: the
existing hook, unchanged.
Co-Authored-By: Claude <noreply@anthropic.com>
* test(sidebar): stamp sort hook reads no live sources; stronger switch-on tests
Co-Authored-By: Claude <noreply@anthropic.com>
* test(sidebar): clear the order-save mock between tests
Co-Authored-By: Claude <noreply@anthropic.com>
* wip: main keeps sleeping retention only
* fix(sidebar): stamp sort reads a shared empty-sources constant, not a renderer layout literal
The layout-writer ratchet counts layout field keys written in the renderer;
the empty sources object now lives beside SmartSortSources in shared code.
Co-Authored-By: Claude <noreply@anthropic.com>
* refactor(sidebar): main keeps sleeping retention only; no order of its own (dark)
Clients sort by the owning host's stamps, so main's temporal owner loses its
settle window, Smart latch, create grace and catalog-listing classifier, and
the shadow check stops comparing order. Retention, its clock and its shadow
comparison stay.
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(sidebar): every retention listing shares one sequence; publish after the first
Co-Authored-By: Claude <noreply@anthropic.com>
* test(sidebar): the first listing bumps the retention revision
Co-Authored-By: Claude <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
* refactor(sidebar): the stamp-sorted window list runs no settle, latch or saved order (dark)
With ORCA_SIDEBAR_INPUTS=main the window's sort hook is a pure read of the
rows' stamps: no settled-epoch store listener, no Smart latch, no saved Smart
order writes, no class telemetry over the old attention map. Switch off: the
existing hook, unchanged.
Co-Authored-By: Claude <noreply@anthropic.com>
* test(sidebar): stamp sort hook reads no live sources; stronger switch-on tests
Co-Authored-By: Claude <noreply@anthropic.com>
* test(sidebar): clear the order-save mock between tests
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(sidebar): stamp sort reads a shared empty-sources constant, not a renderer layout literal
The layout-writer ratchet counts layout field keys written in the renderer;
the empty sources object now lives beside SmartSortSources in shared code.
Co-Authored-By: Claude <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
* fix(cleanup): count visits as last activity once terminals stop stamping Recent (dark)
With ORCA_SIDEBAR_INPUTS=main, terminal start and exit no longer write
lastActivityAt, so a workspace used only through plain shells could look idle
to workspace cleanup and old in the jump palette. Both now read the later of
the Recent stamp and the last visit. Switch off: unchanged.
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(cleanup): stamp the workspace you leave; space table reads visits too (dark)
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(cleanup): every reveal stamps the workspace you leave, host-aware (dark)
Co-Authored-By: Claude <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
Two merged changes left lines past the formatter's 100-column width in
structured-session-create-support.ts, structured-agent-session-create-adoption.ts
and transcript-opencode-sqlite-query.ts. Any branch that merges main gets them
reformatted by the pre-commit hook, which pushes two of them past the 300-line
limit and fails CI. Format them, and keep them under the limit by moving the
bounded row readers and the resume adopt payload into the modules that own them.
No behavior change.
* feat(sidebar): sort stamps reconcile stored tiers at startup against live hook rows and terminals (dark)
* test(sidebar): a restart clears an unbacked local tier and keeps an SSH host's
* fix(sidebar): run the sort tier reconcile once hook rows and PTYs settle, seeding hydrated rows
* fix(sidebar): clear a stored tier only on proof its terminals died; a rest seed adds no clear
* chore(sidebar): merge origin/main; locality helper moved to shared
* feat(sidebar): sort Recent and Smart by host stamps behind the sidebar-inputs switch
With ORCA_SIDEBAR_INPUTS=main, the window, main and the phone order Recent by
lastActivityAt and Smart by smartTier then smartAt (Needs you, Done, Working,
then the rest by Recent). The window holds its displayed order while the
pointer is over the list or a drag runs. Switch off: today's sort path.
Co-Authored-By: Claude <noreply@anthropic.com>
* chore: restore files the merge commit's formatter rewrote
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(sidebar): stamp-order review fixes behind the sidebar-inputs switch
The window writes no Recent stamps with the switch on (terminal start and
exit, unread marks, comment edits), the hover hold covers only Recent and
Smart and ends with a drag, main follows only explicit metadata saves, and
the phone picks its sort from the unfiltered rows.
Co-Authored-By: Claude <noreply@anthropic.com>
* test(sidebar): folder writers and a Manual drag under the stamp switch
Co-Authored-By: Claude <noreply@anthropic.com>
* test(sidebar): name the stamp type in the ps summaries test
Co-Authored-By: Claude <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
* Require a full hover delay before each tab preview
Tab previews opened almost immediately after a previous preview had been shown, so sweeping across the tab strip caused a flurry of popups. Raise the tab tooltip delay to 800 ms and drop the warm-start chain: the shared open-state context, the 300 ms skip delay, and the slide animation that carried position between cards. Every tab now waits its full dwell time, and keyboard focus still shows the preview immediately.
* Test that tab hover cards wait a full dwell on every hover
The tab hover card no longer slides instantly between neighboring tabs. Each hover now waits for the full delay before showing a preview. The e2e test still expected instant sliding, so it was rewritten to match.
- Remove the frame-recording helpers and the slide and reversal assertions.
- Check that no card appears before the dwell, and that the card appears after it.
- Check that leaving a tab cancels a pending preview, and that time spent on other tabs does not count toward the dwell.
- Check that re-entering the same tab requires a fresh dwell.
* chore(layout): the drift check's ignore comment names every main-only kind (dark, layout-owner build only)
* fix(layout): a quit stage retried after a failed one still sends the resume records (dark, layout-owner build only)
* feat(layout): the window's local layout stream reopens after a loss, and its snapshot redraws every workspace (dark, layout-owner build only)
* fix(layout): a command whose target main's copy lacks is a logged refusal, not the window's own change (dark, layout-owner build only)
* feat(layout): frames keep each tab the window drew, closed or moved as drawn until main answers; the global frame hold goes (dark, layout-owner build only)
A refused draw-first terminal create is closed (its terminal stopped) at the refusal. The
activation create no longer draws main's tab itself while the stream is down: the reopened
stream's snapshot does.
* fix(layout): a tab move or drag-out the frame already drew is not placed again by the window (dark, layout-owner build only)
Also holds the group sizes the window drew until main saves them.
* test(layout): with frames flowing, a pin promotes a preview tab once and tab-change bookkeeping runs once (dark, layout-owner build only)
Also states that the held-tab overlay builds frame slices only.
* test(layout): read the rename's title without a cast
* chore(layout): the drift check's header lists the same ignored records as its inline comment