Commit Graph
19378 Commits
Author SHA1 Message Date
OrcaWinandOrcaWin 4abbef34d4 test: remove 859 low-value runtime and account tests (#38763)
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-11-05 17:53:55 -08:00
Kelvin Amoaba c8c620cd21 feat(mobile): rename a workspace from the workspace menu (#38728)
* feat(mobile): rename a workspace from the workspace menu

The phone's workspace menu gets a Rename row that swaps the drawer to the
text sheet, prefilled and selected. Saving writes worktree.set displayName at
the row's owner, so server rows work too; an empty name returns the label to
its automatic branch or folder name, as desktop's Workspace details does. A
failed save keeps the sheet and text, shows the host's error and offers Retry.

* refactor: share the automatic workspace name rule with the phone's rename sheet
2026-11-06 01:52:38 +00:00
EdgerHaoandNeil 554a16f328 fix(agent-hooks): keep CodeBuddy PermissionRequest from reporting waiting (#38009)
CodeBuddy fires PermissionRequest even when its auto-review approves the
tool, so the pane flashed "waiting" for calls that never needed the user.
Real prompts still arrive as Notification(permission_prompt), so drop
PermissionRequest for CodeBuddy only; Qoder keeps the existing mapping.

Co-authored-by: Neil <neil@stably.ai>
2026-11-05 17:35:57 -08:00
registered2ndandNeil c624d85cbc fix(windows): a failed process-table snapshot can no longer kill the terminal host (#37820)
node-addon-api's default OnError hands the getProcessList callback an Error
(e.g. bad_alloc) instead of rows. The callback only checked for a missing
value, so `.some` threw inside the addon's callback loop, which is uncaught
and ended the terminal host with every PTY on it. Reject the read unless the
snapshot is an array.

Fixes #33350

Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
2026-11-05 17:30:38 -08:00
f13a67a7c4 fix(skills): stop the orchestration stub repeating its description (#37850)
The orchestration stub body opened with a near-verbatim copy of its front
matter description plus a paragraph on why the guide lives in the binary.
Give it the one-line opening every other stub uses and keep its only unique
rule: never substitute a non-Orca subagent tool for Orca orchestration. The
description is unchanged since it drives skill routing.

Regenerated skills/orchestration/SKILL.md, the bundle manifest, bundled
sources, and the released-digest ledger (adds the new stub digest only).

Fixes #37849

Co-authored-by: Michael <mhoeykens@wattvibe.eu>
Co-authored-by: Jinwoo-H <jinwoo0825@gmail.com>
2026-11-05 17:20:22 -08:00
6ff6b39e83 perf(terminal): cut renderer per-chunk classifier cost on coloured output (#38489)
* perf(terminal): stop allocating per SGR and rescanning ASCII output in renderer chunk classifiers

* fix(terminal): bound SGR delimiter scans to each parameter sequence

* test(terminal): pin background SGR classification for 38 colour skips and colon params

---------

Co-authored-by: m4air <m4air@Mac.localdomain>
Co-authored-by: Neil <neil@stably.ai>
2026-11-06 01:09:40 +00:00
OrcaWinandm4air f15e114aa8 perf(terminal): copy the retained transcript window once per output chunk (#38482)
Co-authored-by: m4air <m4air@Mac.localdomain>
2026-11-06 01:09:02 +00:00
OrcaWinandOrcaWin ab982d32b9 test: remove 137 low-value automation and account tests (#38755)
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-11-05 17:05:57 -08:00
2b0400f16a perf(renderer): cut per-store-write selector fan-out and DOM blink churn on switches (#38505)
* perf(renderer): cut per-store-write selector fan-out and DOM blink churn on switches

- useChatProgramFacts: skip resolving the host context revision while inactive
  (hidden worktrees' tab bars ran two JSON.stringify per store write each).
- useTerminalTabActivity / useTabAgent: memoize per-tab selectors on this tab's
  own inputs so unrelated writes reuse the last result.
- Only suspend cursor blink for panes holding live WebGL: DOM blink is
  focus-gated CSS, and toggling the option rewrote xterm's stylesheet and
  invalidated style on every hide and reveal.

Refs #38413, #26785, #9715

* test(terminal): cover retained WebGL cursor suspension through production paths

---------

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
Co-authored-by: Neil <neil@stably.ai>
2026-11-05 16:59:04 -08:00
OrcaWinandOrcaWin cbc2475d14 test: remove 286 low-value session history tests (#38754)
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-11-05 16:58:57 -08:00
Neil cc8cdd480b End a remote pane whose session exited before its shell was ready (#38747)
* fix(terminal): end a remote pane whose host retired its surface

A remote session that exits before its shell is ready left the pane
"Reattaching terminal" forever: the host retires the pane surface and
publishes a close record for it, but the client only looked for a live
surface and kept polling for one to come back. The client now reads the
host's close record as the end of that pane and ends it with the exit
code the host recorded, both when opening the pane and when reattaching
after its stream ends. A live replacement the host publishes still wins.

Refs #38151

* fix(terminal): fence the retired-surface end to the attachment that asked

Refs #38151

* fix(terminal): keep a retired remote pane revivable by a host replacement

Refs #38151

* fix(terminal): judge a host replacement against the probed exit incarnation

Refs #38151

* fix(terminal): end a retired remote pane only while the host still confirms it

Refs #38151

* fix(terminal): keep an unconfirmed retirement's attach retry parkable

Refs #38151

* fix(terminal): keep watching a retired pane through absent-surface frames

Refs #38151
2026-11-05 16:54:42 -08:00
Kelvin Amoaba 6bd548ba5c feat(mobile): render math in chat and PR descriptions, with GitHub's line-count table (#38682)
* feat(mobile): render math in Markdown with desktop chat's delimiter rules

Move the price, citation, closer and display rules plus the KaTeX settings
to src/shared/markdown-math-rules and point desktop chat at them. Mobile's
markdown-it gets the same grammar, and pages typeset formulas with KaTeX,
whose fonts ship as page assets. Mermaid's engine leaves katex external so
both share one katex chunk.

* build(mobile): count KaTeX fonts in the page budget and keep math to one chunk

Only the licensed terminal font keeps its own allowance; KaTeX's fonts
count toward the budget like code. The typesetter imports katex itself,
sharing mermaid's chunk, so math adds one chunk instead of four.

* fix(mobile): draw the PR line-count table's +/− glyphs at GitHub's size

github.com draws \Huge at \Large (1.44em), so the line-count table's
glyphs sit inline at text height. Cap the larger size commands the same
way, and let inline math take its height from KaTeX's struts so rows keep
normal height and numbers stay on the text baseline.

* test(mobile): pin math delimiters to desktop chat's results

* refactor(mobile): drop type assertions from the math tree nodes

* chore(mobile): register the math mdast nodes beside <details>

* fix(mobile): read bracket math only in chat, keep math parsing linear

GitHub text (PRs, Tasks, files) reads $ and $$ only, so \[Bug\] stays a
bracket; chat rows opt into desktop chat's \( and \[ too. Remember failed
block scans and skip dollar-run tails so hostile input stays linear, leave
unfinished math out of remend like desktop, bound the formula cache by bytes,
retry a failed KaTeX load, and count symbol commands in table widths.

* fix(mobile): copy formulas as TeX and keep GitHub's unclosed $$ as text

Share desktop's selection-to-TeX reader so a copied selection carries each
formula once, as its source. An unclosed $$ in GitHub text stays a
paragraph, chat's math delimiters reach HTML-derived Markdown and summaries,
failed block scans only skip openers inside the lines they read, unfinished
math anywhere in the streaming tail skips remend, and table widths no
longer spread every command into Math.max.

* refactor(mobile): share the formula copy handler and cap sizes only in GitHub text

Desktop and mobile copy through one shared handler. GitHub surfaces draw
\Huge as github.com does while chat keeps desktop chat's sizes, PR and file
text skip bracket scanning entirely, and the dollar-run scan and token
alias live in one place.

* chore(mobile): stop tracking the generated KaTeX stylesheet

#38611 committed it by mistake; postinstall writes it and .gitignore now
lists it.

* fix(mobile): follow desktop chat's new dollar rule through the shared module

#38651 changed desktop chat's inline $ rule (shell variables and prices stay
text, a single $ can span lines, $5+2$ is math). Move those checks into
src/shared/markdown-math-rules, point desktop at them, and give mobile's
markdown-it rule the same behaviour.
2026-11-06 00:53:54 +00:00
Neil 4aeda96176 Move automation run history out of the signed SSH migration manifest (#38718)
* fix(ssh): move automation run history beside the migration manifest

A host whose automations kept a lot of run output overflowed the 768 KiB
signed migration manifest, so every Connect was refused and the host was
locked out. Run history now travels as a capability-gated
`automation-run-history` blob: the manifest signs only its sha256 and
size, the destination imports the runs atomically with the catalog at
commit, and a host without the capability is refused before anything is
sent instead of importing automations without their runs. Journals and
manifests signed with inline runs are still accepted. The oversize error
now names the largest section with an action that trims it.

Fixes #37051

* refactor(ssh): read migration capabilities from their own client module

Keeps orcad-migration-catalog-client.ts within its line budget.

Refs #37051
2026-11-05 16:50:32 -08:00
Neil 2d9d71f72f fix(terminal): give remote Windows panes the ConPTY resize model (#38715)
* fix(terminal): give remote Windows panes the ConPTY resize model

A pane's xterm took ConPTY's resize rule only when the PTY was local. A
paired server or SSH target on Windows runs every PTY, WSL shells included,
through its own ConPTY, but the client grew such panes the Unix way and
pulled history into the screen. ConPTY repaints only what it believes
changed, so stale TUI frames (e.g. Claude Code's inline prompt) stayed
stranded mid-screen after a resize.

Decide the model from the OS of the machine that owns the PTY: the paired
server's status hostPlatform or the SSH target's remotePlatform. The
renderer applies it at mount and again at PTY bind, because a restored pane
can mount before the server's status arrives. Main's copy of an SSH pane
follows the same rule.

* fix(terminal): resolve the PTY owner from the bound transport and apply a late OS once

A paired server's mirrored pane can be that server's SSH target, so the
bound transport's host names the owner. A remote owner's OS can arrive
after bind; the pane stays on the Unix model and watches the store until
it is known, then applies it once.

* fix(terminal): fall back to the pane's SSH owner and stop watching owners that can't be named

When a paired server's transport can't name its host yet, the pane's own
ssh: owner decides the OS instead of the server's. A pane with no
nameable remote owner keeps its creation-time model instead of watching
the store forever. Local-owner detection lives in one helper; an
undefined connection id still means unresolved, not local.

* fix(terminal): re-resolve a pane's resize model when its transport learns the PTY's host

A reattached mirror on a paired server binds before attach metadata says
the PTY belongs to the server's SSH target, so the pane took the
server's model and stopped looking. The remote transport now notifies
when its execution host is learned or changes, and the pane re-resolves
then.

* test(terminal): justify the partial RuntimeStatus fixture with a SAFETY disable
2026-11-05 16:49:34 -08:00
Neil c0b2df5d8d fix(ssh): leave an unlinked host disconnected, and name it when a project needs its server (#38746)
Forget and Stop removed the managed server but left the SSH host's transport up with nothing
serving it, so project setup offered the host and then failed with the raw connection id.
The unlink now hangs up the host's transport inside its lifecycle, so every surface offers
Connect (which sets a server up again) and a Connect queued behind the unlink still wins.
The setup error names the host and its toast offers Connect, and the forget/stop help says
the host is disconnected.

Fixes #37767
2026-11-05 16:44:40 -08:00
Jinwoo Hong a9ba59c944 Nest workspaces under parents on other machines (#38740)
* feat(lineage): store cross-store parent links beside same-store lineage (inert)

A workspace's parent can live in another Orca store. Save that link in the
child's store under a new top-level key, crossStoreLineageByChildKey, so older
builds' same-store cleanup never reads it. Nothing writes records yet.

- Types, load normalization (malformed entries dropped one by one), section label.
- Store get/set/remove by child; a child keeps at most one parent record.
- Every maintenance path acts by the child only: deletion and missing-worktree
  pruning, repo removal and deregistered-repo residue, identity migration,
  profile project transfer, WSL move projection and UNC drive conversion, and
  the orcad migration dormant payload (export, validation, import, subtraction).

* fix(lineage): gate cross-store links on host capability; one parent per child

- Migration stage/commit carrying crossStoreLineage requires the new
  orcad.migration-cross-store-lineage.v1 capability; older hosts are refused
  before staging instead of silently dropping the links. Empty arrays are omitted.
- Payload validation and destination prepare reject a child that has both a
  cross-store and a same-store parent, through one shared conflict check.
- Saved entries stay on disk as written; the map is Record<string, unknown> and
  readers go through validCrossStoreLineage. Re-keying moves unreadable entries unchanged.
- Tests: literal expectations; drop touched mock-call and work-count assertions.

* test(lineage): drop clone and save work-count assertions from project group removal test

* feat(lineage): resolve cross-store parents into the sidebar parent map (inert)

- One resolver: a saved record nests its child under the single visible
  row whose store reports the parent's installationId and whose instance
  matches; copied installs, unknown stores and stale instances stay standalone.
- getSidebarLineageParentsByIdentity merges resolved cross-store parents with
  same-store edges before cycle breaking; a valid same-store edge wins.
- Workspace delete collects descendants only from the parent's own store.
- Store identity: paired stores from their verified status hostDescriptor;
  this store from the published descriptor, else host-installation.json.
- Nothing passes a view or writes records yet, so the sidebar is unchanged.

* fix(lineage): import WorkspaceKey from workspace-scope after main move

* fix(lineage): resolve sidebar parents once over complete rows; keep unowned rows' delete rule

- resolveSidebarLineageParentIdentities resolves same-store edges and cross-store
  records once over every visible store's unfiltered rows, then drops cycles.
  Ancestor injection and nesting reuse that map, so a filtered-out twin still
  makes a parent ambiguous.
- Removed, retired or refusing paired servers leave the view: their records stay
  saved and their children standalone. Offline servers keep their last-known id.
- A row with no owner is unknown, not ours: it never resolves, and delete only
  excludes descendants whose store is known and differs from the parent's.

* fix(lineage): refuse UNC rekey collisions and transfers giving a child two parents

The UNC drive conversion rekeys cross-store child keys through the shared
collision merge, so a UNC key and its drive key with different records throw
instead of one silently overwriting the other. Profile project transfer runs the
shared child-key conflict check before writing the target.

* feat(lineage): write, carry and nest cross-machine parents

- Capability worktree.lineage.crossMachine on the child's store; the CLI
  refuses before any request when the server lacks it.
- worktree.set / worktree.create accept crossStoreParent
  { installationId, workspaceKey, instanceId } and write only
  crossStoreLineageByChildKey (refused when it names this store, or the
  child has no identity). Remove Parent clears both.
- worktree.crossStoreParentRef: the parent's own store names it with its
  installationId; reparent refuses a parent whose chain already reaches
  another store. Same-store Set Parent refuses the same.
- worktree.ps rows and lineage lists carry the records; same-store lineage
  fields stay null so older readers show the child standalone.
- The desktop sidebar resolves parents over its own and paired stores'
  records (PR 2's resolver) at the visible-worktree and row-plan call sites.
- CLI: active/current or an inferred terminal parent against a server is
  sent as a cross-store parent.

* fix(lineage): read worktree rows through the record selector; mark the test runtime cast

* fix(lineage): hydrate every store, fence refreshes, honest CLI fallback

- Each host's catalog hydration fetches its lineage snapshot, so this
  desktop's identity and records load whichever store is focused.
- Cross-store records join the per-child request-start fence: a stale
  lineage list can't restore a removed record or override a newer one.
- A failed inferred cross-store parent sends noParent instead of
  forwarding the selector a server could match to a same-store twin.
- Inferred cross-store creates record env-workspace/inferred capture.
- The writer states its trust contract; the chain rule runs at the mint.

* feat(lineage): cross-machine parents on the phone, pickers, chat resume and labels

- Phone: the list nests a child under a parent on another machine, from the
  records rows already carry and the store identities its host verified
  (status.get descriptor; relayed servers' installationId on the host list).
- Set Parent popover, context menu and the composer's parent picker offer
  parents in other visible stores. The parent's own store names it
  (worktree.crossStoreParentRef); the window checks it against that store's
  verified installationId and the child store's capability before any write.
  Remove Parent and Open parent cover cross-machine links.
- Native chat resume ancestors follow resolved cross-machine parents.
- Host sections keep a nested child under its parent and keep its own
  machine label.
- Main sidebar feed resolves cross-store parents (rows carry records; desktop
  facts and paired-host facts carry identities); paired web windows read
  their host's installationId; reveal without an occurrence plan resolves
  them; `worktree create --agent` names a parent on another machine.

* test(lineage): type the mint test's parent row through unknown

* test(lineage): give the cross-machine tests' partial-row casts their SAFETY reasons

* fix(cli): worktree set names a parent on another machine

* feat(lineage): name the machine of parent candidates on another machine

* fix(lineage): Set Parent and Remove Parent act on the clicked row's own host

* fix(lineage): phone resolves parents over every row before the repo filter

* fix(lineage): chat resume nests by host-qualified identity across machines

* fix(lineage): local create refuses a same-install parent before checkout and shares the RPC recorder

* fix(lineage): name parent machines without a focus read; keep create's probes synchronous

* fix(lineage): chat resume keys offers by their own machine; only cross-machine parents leave a section

* fix(lineage): name an older parent server before asking it for a cross-machine ref

* fix(lineage): an app-picked parent on another machine records as manual

* fix(lineage): chat resume keeps base grouping; only resolved cross-store parents move a chat

* revert(lineage): drop chat resume from cross-machine parents; it groups by machine as before

* refactor(lineage): CLI builds cross-machine parents; explicit only

- Drop worktree.crossStoreParentRef and its reparent chain walk: the CLI
  builds the ref from this desktop's installationId (worktree.lineageList
  now reports it) and the parent row it reads. Cross-machine chains are
  allowed; same-store cycle validation and same-install refusal stay.
- No inferred cross-machine parent: remove crossStoreParentInferred and
  the fallback states. Against a server, this machine's terminal, cwd and
  env context is never sent; the CLI prints how to nest explicitly.

* refactor(lineage): build cross-machine parent refs from verified ids; pickers hide only loops; one desktop install id source

* refactor(lineage): refuse moving a linked workspace instead of carrying the link

Orcad migration and profile project transfer now refuse, before any staging or
write, when a workspace in the move has a cross-store parent record (raw entries,
readable or not). Remove the carry machinery: the dormant crossStoreLineage
field, its runtime capability and destination gate, import/subtract/claim code,
transfer carry and the two-parent conflict checker. Also drop the unused
removeCrossStoreWorkspaceLineage.

* docs(lineage): describe cross-store parent refs without the removed mint RPC

* test(lineage): drop the RPC call-list assertion from the cross-store ref test

* fix(lineage): refuse linked moves before any write and for shared-repo children

- Migration refusal passes the child's own host stamp to the scope matcher and
  refuses an unstamped key whose shared repo id the move includes, so such a
  child can't move while its link stays behind.
- Profile transfer checks the read-only source snapshot before pending-move
  recovery or storage migration; the payload check stays as the final guard.

* fix(lineage): refuse a linked profile move before the profile lookup can restore the index

* fix(lineage): startup hydration of other stores loads only cross-machine links

* fix(lineage): list the child's own machine first in the parent picker

* fix(lineage): keep profile argument errors ahead of the linked-move preflight

* fix(worktrees): let the first listing replace a restored server workspace stub

A restored active server workspace starts as a session stub with no head.
If git status fills in its branch before the first listing lands, the
listing merge took that as a concurrent branch switch and kept the stub,
dropping the catalog row (instance id, lineage, provenance). The workspace
then showed standalone until the next listing.

* fix(worktrees): accept a headless row's listing only when it confirms git status

A stale listing could otherwise roll back a branch switch git status saw on
an empty-head row (e.g. an SSH fallback row) during the first connected listing.

* feat(lineage): CLI nests a create on another machine under the caller workspace

- No parent flag and no --no-parent against a server: the caller's workspace
  on this machine (env, else cwd) is sent as an inferred cross-store parent,
  the way --parent-worktree active builds it. The host records it as
  inferred from environment or cwd.
- A server without worktree.lineage.crossMachine, or a caller workspace that
  can't be linked, still creates unnested and prints one note.
- An inferred parent falls away for folder targets, as same-machine folder
  creates record none.

* fix(lineage): CLI cross-machine inference reads the caller terminal and suppresses fallbacks

- The caller terminal's workspace joins env and cwd as an inferred parent,
  with same-machine precedence: env and terminal must agree, cwd only alone.
- A fallback (old server, unlinkable or unresolved caller, ambiguous cwd)
  prints one note and sends noParent, so the server's own create-time
  inference can't nest what the CLI reported as not nested.

* feat(sidebar): drag a workspace onto one on another machine to nest it

Dropping a workspace on a workspace from another machine now sets the
parent through the same cross-machine path as Set Parent: same eligibility
(loops, older servers), same refusal messages. Drop targets carry host
identity so the child's own copy is the one written.

* refactor(lineage): this machine's Orca resolves the caller parent for a create on another machine

- New read-only worktree.resolveCallerParent runs the same-machine create
  resolver over the caller's env, terminal and cwd context, so stale
  contexts are skipped and only validated parents can conflict, and returns
  the cross-store ref or a reason.
- The CLI's own precedence and terminal lookup are removed; it keeps the
  capability gate, one note with noParent on any fallback, and silence with
  no Orca context.

* feat(orchestration): remote worker-start --worktree new-child nests under the coordinator's workspace

worker-start --on <server> --worktree new-child creates the server's worktree
under the coordinator's workspace on this machine, using the same verified
cross-machine parent ref as the CLI's caller auto-nest. A server without
cross-machine parents still starts the worker, top-level, with a warning in
the receipt. Guide, CLI help and docs describe it.

* fix(lineage): resolveCallerParent reads this store's identity without creating it

- Uses the published host descriptor's installationId, else the existing
  host-installation.json; never creates or repairs it. Missing identity
  returns a reason, so the CLI creates unnested with its note.
- Drops the read-only wording: parent resolution does the same catalog
  upkeep a same-machine create does.

* fix(sidebar): validate every cross-machine parent before a multi-select drop writes

A drop of several workspaces builds every cross-machine parent ref first, so an
older server's refusal leaves the whole selection unchanged. Drops the touched
preview-timing test cases.

* refactor(orchestration): build the remote new-child parent ref with the shared caller resolver

Orchestration still picks the caller workspace; resolveCallerParentForAnotherStore
verifies it and builds the ref, and both read the installationId the same way.
Drops the copy and mock-call tests.

* fix(lineage): narrow the inferred source with a guard instead of casts

* fix(lineage): a composer parent picked on another machine nests normally once the destination is that machine

The retained host-qualified pick is classified against the current destination:
same host gives the ordinary parent fields, another store the cross-machine ref.
Also corrects two stale comments.

* perf(lineage): read only the requested child's cross-store entry

* perf(lineage): request each store's lineage list once at startup

* perf(lineage): cap optional caller-parent inference at 3 s and skip a timed-out local Orca

* perf(lineage): return cached sidebar parents when selector inputs are unchanged

* fix(lineage): snapshot selector inputs without a type assertion

* Route startup lineage and the sidebar lineage memo through their owners
2026-11-05 19:42:19 -05:00
Neil 61803ba8ba fix(orcad): a supervised server does not start without its terminal daemon, and orcad defends its instance lock (#38709)
* fix(orcad): yield a lost instance lock and refuse non-durable terminals under a supervisor

Fixes #37605

* fix(orcad): treat an unstattable lock as unproven; scope supervised help to orcad

Fixes #37605

* fix(daemon): report fresh-terminal ownership from current spawn routing

Fixes #37605

* fix(daemon): narrow fresh-spawn routing check to degraded providers

Fixes #37605

* test(orcad): cover lock-loss yield and fix stale test paths after rebase

Fixes #37605
2026-11-05 16:41:49 -08:00
Neil 9cc0be5800 Let a paired host expand ~ in the paths its terminals print (#38711)
* fix(terminal): let a paired host expand ~ in the paths its terminals print

A paired-server pane expanded `~/…` on this client, using HOME from the
pane's startup env or a home guessed from the cwd. When the host's home
was not under /home/<user>, /Users/<user> or /root, the link never
appeared; when the guess was wrong, it named another file. The host now
receives the `~/…` text and answers with its own home's path, as it already
does for its local workspaces. WSL workspaces keep client-side expansion.

Refs #35968

* fix(terminal): keep a paired pane's launch HOME for its ~ links

Refs #35968

* fix(terminal): keep SSH homes client-expanded and open folder roots from paired ~ links

A server that proxies an SSH workspace refuses ~ paths, so paired panes
over one keep the client's expansion. A host-resolved ~ root that names a
folder workspace now activates through the workspace dispatcher.

Refs #35968

* fix(terminal): send a paired host one separator after ~

Hosts join what follows `~/` onto their home, so `~//work/file` became
the absolute `/work/file` once the client forwarded `~` paths as printed.
The client now collapses separators right after `~` before asking.

Refs #35968

* fix(terminal): link a paired ~ folder path only when it names a workspace

A home-relative path ending in a separator, such as ~/proj/src/, skipped the
folder-link filter so its host could expand it, but only a workspace root opens
from a click, so subfolders became links that did nothing. The host's answer
must now name a workspace root.

Refs #35968

* refactor(terminal): resolve a linked file's editor target in its own module

The editor workspace, host and relative path a linked file opens under now
come from resolveLinkedFileEditorTarget, keeping terminal-file-open-routing
within the 300-line limit. No behaviour change.

Refs #35968
2026-11-05 16:40:58 -08:00
Neil 6ee3ac76ff fix(plugins): ship plugin author typings in packaged builds (#38751)
electron-builder always appends `!**/*.{...,d.ts,...}` to the app `files`
patterns, so the resources/plugins/{declarative,types}/*.d.ts listed in
asarUnpack never reached the package and `orca plugin types` / `orca plugin
create` failed with ENOENT. Ship the typings via extraResources at
<Resources>/plugins (beside plugins/launch), resolve them there when the CLI
runs from app.asar.unpacked, and fail afterPack if any checkout typing is
missing from the packaged output.

Fixes #38542
2026-11-05 16:39:52 -08:00
buf0-bot[bot] ad17492205 Update README downloads badge (#38752)
Co-authored-by: buf0-bot[bot] <252831055+buf0-bot[bot]@users.noreply.github.com>
2026-11-06 00:39:37 +00:00
OrcaWinandOrcaWin 7634d08f74 test: remove 419 low-value hook and search tests (#38748)
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-11-05 16:35:58 -08:00
Neil 6077bbaf7a Download a granted host file in pages instead of whole (#38693)
* fix(files): download a granted host file in pages instead of whole

A paired host serves a file outside every workspace only through its
terminal grant, whose read is whole-file, text-only and size-capped, so
Shift-click and "Download & open with default app" on such a file opened
it in Orca with "too large" or "binary" errors instead of downloading.
Hosts now page a granted file through files.readTerminalArtifactChunk,
behind a capability, and downloads stream through it like workspace files.

Refs #35968

* fix(files): pin a granted file's ctime so a large download can't mix versions

Files above the digest cap are pinned by stat alone, and utimes can put a
rewritten file's mtime back. ctime moves on every write and can't be reset,
so a same-size in-place rewrite now makes the grant stale.

Refs #35968

* fix(terminal): let a newer link click win over a granted download's host check

Refs #35968

* fix(terminal): drop a superseded granted download whatever the host answers

Refs #35968

* test(files): wait out a coarse ctime tick before rewriting a granted file

On filesystems that stamp ctime in coarse ticks, a rewrite right after the
grant could keep the granted ctime and make the stale-page test flaky. The
test now rewrites until the ctime moves. Also corrects a comment that said a
grant can't be downloaded.

Refs #35968

* style(files): format the granted chunk capability constant

Refs #35968
2026-11-05 16:35:12 -08:00
Jinwoo Hong 8ca8a7b7ab ci: auto-merge the README downloads badge PR (#38658)
* ci: auto-merge the README downloads badge PR

main is PR-only, so the badge workflow's PR sat unmerged and the badge stopped updating. Enable auto-merge (squash) on the PR after each update; it merges once 'PR checks passed' succeeds.

* ci: auto-merge the badge PR by its URL

Merge the PR the step created or found (scoped to main) rather than resolving the branch name again.

* ci: ignore fork PRs when finding the badge PR

A fork PR can reuse the badge branch name; only the bot's own same-repo PR may get auto-merge.
2026-11-05 19:32:27 -05:00
Brennan Bensonandggbdpq ca971bba2e Clear a Claude permission wait when Claude ends the request itself (#38648)
* Clear a Claude permission wait when its tool batch ends

When Claude ends a permission request itself (prompt timeout, built-in
safety check, non-interactive denial), no Post* hook fires, so the
terminal kept showing "needs permission" for the rest of the turn while
Claude kept working.

Claude 2.1.118+ sends PostToolBatch once every call of a model step has
resolved, listing each call's tool_use_id. Install it (not for
OpenClaude), name each PermissionRequest's request from the matching
PreToolUse in the listener, and settle a held wait only when its own
batch lists that request, locally and over SSH/WSL relays (live and on
reconnect replay). Elicitation dialogs, unnamed waits, newer prompts and
finished turns are left alone. The offline hook spool skips the batch
event, which carries every call's output.

Co-authored-by: ggbdpq <ggbdpq@gmail.com>

* Name a permission from its open call and keep held prompts across sibling results

Parallel calls interleave their hooks, so a PermissionRequest is named
from the one open PreToolUse it matches rather than the latest one. A
subagent's sibling result no longer restates its wait over a held
prompt row, a subagent's settled request can't clear the lead's own
open prompt, and retention reads only the entries the history keeps.

Co-authored-by: ggbdpq <ggbdpq@gmail.com>

* Keep the pane waiting while any other permission request is open

The listener's single wait slot holds only the newest prompt, so a
batch now settles a wait only when no other open call still has a
pending permission request. This replaces the missing-saved-state
guess, which missed two concurrent subagents and stranded a subagent's
wait after a relay restart. A fresh session start, not compaction or a
child, retires open calls, and a batch's id list is read once.

Co-authored-by: ggbdpq <ggbdpq@gmail.com>

* Skip a spaced batch event in the offline spool and pin batch-read cost

Co-authored-by: ggbdpq <ggbdpq@gmail.com>

* Settle a self-denied wait only while no other prompt is open

Orca keeps one wait per pane, so overlapping prompts can't be settled
one by one yet. A batch now leaves every held row alone while another
call still waits on the user: a permission request, or a question from
its PreToolUse. A stopped subagent's calls retire, and a call that waits
on the user is never evicted by the open-call bound. Overlapping prompts
keep today's behaviour and never show working while one is open.

Co-authored-by: ggbdpq <ggbdpq@gmail.com>

* Count open MCP dialogs and unmatched requests as waiting on the user

An Elicitation now waits in the open-call registry until its
ElicitationResult (matched by elicitation_id), and a PermissionRequest
that matches no observed call still records an entry that waits until
its owner stops or the turn ends. Either keeps a batch from clearing
the pane while that prompt is still on screen.

Co-authored-by: ggbdpq <ggbdpq@gmail.com>

* Keep tool calls apart from waits Orca cannot identify

An MCP dialog is now identified by its owner, server and elicitation id, so a result ends only that dialog. A dialog without an id, or a permission request whose call was never seen, is an unidentified wait that only a stop, turn end or fresh session retires. Only real tool calls name a request or settle by id, so an internal key never reaches the status row.

Co-authored-by: ggbdpq <ggbdpq@gmail.com>

* End a call only from its own agent and keep lead prompts across task notifications

A tool result or batch now ends only a call opened by the same agent, so a reused id cannot end another agent's prompt. An injected task notification is not a new user turn, so it no longer retires the lead's open prompts.

Co-authored-by: ggbdpq <ggbdpq@gmail.com>

* Show the offline spool skips Claude tool batches

Co-authored-by: ggbdpq <ggbdpq@gmail.com>

---------

Co-authored-by: ggbdpq <ggbdpq@gmail.com>
2026-11-05 16:31:58 -08:00
Brennan Benson d048aacd59 fix(sidebar): keep resize controls above workspace panes (#38705)
* fix(sidebar): keep seam controls above workspace panes

* test: cover sidebar hit targets over terminal content
2026-11-05 16:31:03 -08:00
Brennan Benson c7eae748d8 fix(tabs): a session's own name replaces a quick-command tab name (#38677)
* fix(tabs): a session's own name replaces a quick-command tab name

* fix(tabs): only a session's own name replaces a quick-command tab name

Session titles now say whether they are the session's own name (a rename
or a provider-generated name) or a stand-in such as the first prompt. A
quick-command tab keeps its label until the session has a name; a name
typed on the tab still wins. Titles from older hosts and saved tabs with
no kind keep the label; old saved titles are asked for once.

* fix(tabs): classify History-cached titles for session-id-only title requests

* fix(tabs): keep a cached session title when its transcript cannot be reread

* fix(tabs): keep a session title when its classification reread fails

* fix(tabs): keep the transcript for an unclassified cached session title

* test(tabs): keep the cached-title fixture's agent literal
2026-11-05 16:30:03 -08:00
Neil b468781c20 fix(claude): drop the local-command caveat when rewinding to a slash command (#38743)
Rewinding to a Claude local command (e.g. /usage) cut the fork at the
command row's parent, which is Claude's isMeta <local-command-caveat> row.
The fork then ended on the caveat, so the next prompt was read as the
local command and Claude replied "No response requested." Cut at the
caveat's parent instead.

Fixes #38686
2026-11-05 16:29:36 -08:00
Neil 47e8985117 fix(serve): keep orca serve running while it probes the login shell (#38515)
* fix(serve): keep `orca serve` running while it probes the login shell

`orca serve` started from an interactive terminal stopped itself ("[1]+
Stopped") before printing its pairing link. orcad inherits the terminal
and probes the login shell with `$SHELL -ilc`; an interactive bash 5 or
zsh takes the terminal's foreground job, so orcad's next terminal access
raised SIGTTOU/SIGTTIN. Give both shell probes (login environment and
PATH) a POSIX session of their own so they never hold a controlling
terminal. Windows keeps attached spawns, where `detached` would open a
console.

Refs #38347

* fix(serve): end a detached shell probe with its owner and on timeout

A probe in its own session no longer receives the terminal's Ctrl+C or
hangup, so a hung rc could outlive `orca serve`. Kill the probe's process
group by its own pid when it times out and when its owner exits while it
still runs; orcad and Electron turn INT/TERM into an exit, so a Ctrl+C
reaches it that way. No signal listeners: one would replace Electron's
graceful signal quit. The timeout now also ends what the rc started, not
just the shell.

Refs #38347
2026-11-05 16:28:20 -08:00
Brennan Benson 64ad230648 Open a terminal when splitting a Chat UI pane (#37984)
* Open a terminal when splitting a chat pane

* Clean empty terminal splits after remote creation settles

* Align retained menu test with terminal split labels

* Verify translated terminal split rejection messages

* Load remote regression dependencies in suite setup

* Keep split actions searchable by chat context and terminal terms

* Preserve later workspace selection when splitting into a terminal

* Use the existing group argument in terminal creation control

* Preserve existing split searches when opening terminals

* Rank eligible palette actions before checking split results

* Describe terminal splits accurately in the chat tour
2026-11-05 16:23:58 -08:00
89b51e2514 Include native chat tabs in Ctrl+PageUp/PageDown cycling (#37952)
* fix(tabs): include native chats in terminal shortcut cycling

Co-authored-by: Hrithik <hrithik@Hrithiks-MacBook-Pro.local>

Co-authored-by: Ignacio Correia <ignacio.r.correia@gmail.com>

* fix(tabs): keep cycling after selecting a hydrating terminal

* test: bound tab cycling work as tab counts grow

---------

Co-authored-by: Ignacio Correia <ignacio.r.correia@gmail.com>
2026-11-05 16:23:36 -08:00
Kelvin Amoaba 81e68744f2 fix(browser): Escape typed in a page exits element grab, find and the password list (#38741)
* fix(browser): Escape typed in a page exits grab, find and the password list

A focused page keeps its keys, so the renderer's Escape listeners for
element grab/annotate, the find bar and the saved-password list never
fired. Main now hands Escape from browser and HTML preview guests back
to the renderer for that page; during a grab pick the page doesn't
also get it, like C/S.

* test(browser): say why the grab Escape test waits out the toast

* refactor(browser): share one page-Escape subscription hook
2026-11-06 00:22:02 +00:00
Brennan Benson e1bc4e8618 Cmd/Ctrl+Enter on an empty chat composer steers the first queued message (#38732)
* fix(native-chat): Cmd/Ctrl+Enter on an empty composer steers the first queued message

* fix(native-chat): empty-composer Cmd/Ctrl+Enter steers only where the chord means steer

With Queue follow-ups off, Cmd/Ctrl+Enter queues the typed message, so a
second press on the now-empty composer would steer the card just queued.
2026-11-05 16:21:44 -08:00
OrcaWinandOrcaWin 0b27a777ea test: remove 220 low-value hook, timeline and Ruby tests (#38739)
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-11-05 16:20:16 -08:00
OrcaWinandOrcaWin 9a255602d2 test: remove 161 low-value ACP tests (#38738)
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-11-05 16:16:45 -08:00
Neil a0cea20878 fix(quick-open): resolve a folder twin's file list on the host the user selected (#38736)
Chat @ file search, Quick Open and the + menu looked a folder workspace up without a host, so a
same-id folder on two hosts resolved to nothing and listed no files. They now resolve the selected
copy, the same way skill lookup does.

Refs STA-13130
2026-11-05 16:16:15 -08:00
Jinwoo Hong 4c82aab266 README: refresh hero and feature animations (#38726)
New hero still: a coordinator running two workers with the mobile app replying. The Features table is rebuilt from the website's product animations (13 rows led by orchestration, chat or terminal, run anywhere and the mobile app), Also in the box lists the rest of the feature wall, the tagline matches the website, and the contributors image shows up to 500 people.
2026-11-05 19:13:29 -05:00
Jinwoo Hong 611feba7cc fix(terminal): press Enter once the composer shows the pasted prompt (#38401)
Press Enter once the composer shows exactly the pasted prompt, instead of waiting for a quiet screen that background work never gives.
2026-11-05 19:13:10 -05:00
Jinwoo Hong 0fe7a0beed fix(orchestration): hold mail behind a draft typed into OpenCode (#38451)
Hold orchestration mail behind a draft typed into an OpenCode composer (1.x and 2.x).
2026-11-05 19:09:51 -05:00
Jinwoo Hong 4fd7aea9d5 Keep a nested server workspace nested after the desktop restarts (#38378)
* fix(worktrees): let the first listing replace a restored server workspace stub

A restored active server workspace starts as a session stub with no head.
If git status fills in its branch before the first listing lands, the
listing merge took that as a concurrent branch switch and kept the stub,
dropping the catalog row (instance id, lineage, provenance). The workspace
then showed standalone until the next listing.

* fix(worktrees): accept a headless row's listing only when it confirms git status

A stale listing could otherwise roll back a branch switch git status saw on
an empty-head row (e.g. an SSH fallback row) during the first connected listing.
2026-11-05 19:05:24 -05:00
Brennan Bensonandyu.xia 369e23b084 fix(git): keep the agent-terminal credential guard scalar-only so env scrubs can't break git (#38716)
* fix(git): keep the agent-terminal credential guard scalar-only so env scrubs can't break git

Agents that scrub env names matching KEY/TOKEN/... (DeepSeek Harness) dropped
GIT_CONFIG_KEY_n but kept GIT_CONFIG_COUNT, so every git command in those
terminals failed with "missing config key GIT_CONFIG_KEY_0". GCM_INTERACTIVE
already carries credential.interactive and suppresses GCM's GUI, so the guard
now emits only scalar variables. Tracked in #26594.

Co-authored-by: yu.xia <yuxianice@163.com>

* chore(git): drop stale indexed-config comments and a dead WSL forward branch

---------

Co-authored-by: yu.xia <yuxianice@163.com>
2026-11-05 16:04:25 -08:00
OrcaWinandOrcaWin 27b1ed858c test: remove 184 low-value mobile, CLI and ACP tests (#38733)
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-11-05 16:02:19 -08:00
NeilandPr1p 925cc80507 fix(orcad): only evict real version slots from the artifact cache, so the shared Node runtime survives upgrades (#38717)
* fix(orcad): keep the shared node runtime out of artifact-cache eviction

pruneOrcadArtifactCache treated every non-dot entry under a target
directory as a version slot, including runtimes/ — the Node runtime the
slots share across Orca versions. When an upgrade introduced a third
build, runtimes/ was usually the oldest entry by mtime and was removed,
after which orcad could not start (#38181). Skip the reserved directory.

* docs(orcad): document what the artifact prune keeps and what it must not evict

* fix(orcad): only evict names that parse as artifact version slots

Retention now shares the slot-name grammar with the materializer, so
runtimes/, staging dirs and any future sibling under a target are never
counted as a version slot. Installs that already lost runtimes/ re-place
the pinned Node on the next orca serve start.

Fixes #38181

---------

Co-authored-by: Pr1p <zwq19980411@gmail.com>
2026-11-05 15:57:37 -08:00
Neil 8f490f2fcd fix(browser): attach the desktop browser bridge to environment recipe SSH hosts (#38722)
* fix(browser): attach the desktop browser bridge to environment recipe SSH hosts

Recipe workspaces connect runtime-owned SSH targets that serve a managed
Orca server, but the bridge picked candidates from the SSH settings list,
which hides runtime-owned targets, and browser routing refused them too.
Pick bridge candidates from the managed-orcad claims, validate route
partitions by registration, and route deployment hosts whoever owns them.

Fixes #38603

Reported-by: Mehul K (@mek97)

* fix(browser): follow recipe host connections for the bridge and page routes

Owned SSH targets returned before the bridge rechecked, so a recipe host
connecting never attached it, and recipe browser panes had no published
SSH state to wait on. Recheck the bridge on every SSH state change, and
read a recipe target's connection from its server's state.

Fixes #38603

* fix(browser): settle a recipe browser route when its server is unreachable

Only a live dial waits; lost or never-made contact now lets the route
card offer Retry and browsing from this device.

Fixes #38603

* test: annotate hoisted fixtures instead of casting

Fixes #38603
2026-11-05 15:55:01 -08:00
OrcaWinandOrcaWin 5bc786c1a7 test: remove 133 low-value workflow, mobile and CLI tests (#38725)
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-11-05 15:52:52 -08:00
Kelvin Amoaba 3dd9a49e93 fix(mobile): stop asking to update a current desktop when it can't be reached (#38242) 2026-11-05 23:49:21 +00:00
Kelvin Amoaba 9b2427b958 fix(mobile): say why an agent set to Chat opened as a terminal (#38696)
* fix(mobile): say why a chat launch started a terminal instead

When the host falls back to a terminal for an agent the user set to Chat
(for example Antigravity without its chat runtime installed), show the
host's reason in the launch toast instead of opening a CLI silently.

* fix(mobile): skip the fallback notice for agents that have no chat

* fix(mobile): keep the launch notice toast up long enough to read

* refactor(agent-launch): own the chat-fallback rule in the launch contract
2026-11-05 23:48:01 +00:00
OrcaWinandOrcaWin 4001dc9edf test: remove 112 low-value static-gate and runtime tests (#38723)
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-11-05 15:42:24 -08:00
Neil 0f18e8a40c fix(browser): pin the workspace host for browser downloads into a server workspace (#38721)
The server-side download transfer store wrote through the runtime file
mutations without a host expectation, so every write was refused as
coming from an outdated client. Each transfer now captures the
workspace's host as it prepares the download directory, and passes that
expectation on every later mutation, so a host that moves mid-transfer
refuses the rest.

Fixes #38122
2026-11-05 15:42:22 -08:00
OrcaWinandOrcaWin b47835025b test: remove 166 low-value prebuild and release tests (#38712)
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-11-05 15:27:06 -08:00
Brennan Benson 28f0d162c7 fix(chat): repair account setup and recover startup retries (#38389)
Prepare a missing owned Claude account home through its existing setup owner and retain useful startup failure guidance. Keep uncertain opening messages in their original chat for explicit Retry, accept exact late proof without duplicate messages, and persist one aggregate launch snapshot before notifying listeners.

Verified with independent code and performance review, Claude/Codex runtime recovery and upgrade QA, targeted renderer and persistence contracts, and required CI. Tracked in STA-13879.
2026-11-05 15:25:52 -08:00