Commit Graph
19397 Commits
Author SHA1 Message Date
Neil 3cb4853187 Keep dropped-macOS updaters on their last compatible build and explain why (#38777)
* Keep dropped-macOS updaters on their last compatible build and explain why

Stamp minimumSystemVersion into every mac update manifest from the built
app's LSMinimumSystemVersion, and refuse to publish an ungated release.
When the updater withholds a newer release for this macOS, show a one-time
notice and say so in Settings and the update card.

* Trim the unsupported-Mac notice copy
2026-11-05 18:29:16 -08:00
OrcaWinandOrcaWin 4c49ddca3e test: remove 477 low-value agent and platform tests (#38778)
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-11-05 18:21:27 -08:00
NeilandcoolwindX d938f33139 fix(browser): ask a connected desktop to host when open-url finds no browser host (#38762)
A server learns of a desktop that can host its browser pages only after that
desktop opens a browser tab, so open-url failed until one existed. Desktops now
opt in on their client-events subscription. When a server-initiated page needs
a host and no lease exists, the server asks the newest such desktop to start
hosting and places the page on the lease it names. Only an explicit decline
moves on to the next desktop, so a slow desktop can never leave two leases; a
desktop that stays silent is named in the error.

When several desktops already host, the page goes to the one showing this
workspace's pages, then the newest lease, instead of failing as ambiguous, and
no desktop without a lease is asked.

Managed SSH desktops do not opt in, so their hosts keep using the desktop
browser bridge. Leases stay lazy, so an idle desktop holds none and bridge and
tab-query routing are unchanged.

Fixes #35066

Co-authored-by: coolwindX <45729206+coolwindX@users.noreply.github.com>
2026-11-05 18:19:35 -08:00
705fd10b99 fix(editor): keep plugin new files in the floating workspace (#37878)
Plugin new-file items chosen from the floating workspace's + menu were
created under the active main workspace. Route floating targets (an
existing floating tab group, or the empty floating panel) to the
floating-notes directory with the floating workspace identity, matching
the built-in floating Markdown action.

Co-authored-by: Hunter Pittman <22402914+Hunter-Pittman@users.noreply.github.com>
Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
2026-11-05 18:19:29 -08:00
NeilandBlue Thunder Somogyi 66695b1e9d Route federation ack and import over the shared control connection (#38735)
* fix(runtime-environments): route federation ack and import over shared control

The one-second federation sync pulls over the retained shared-control
connection but sent orchestration.federationAck and
orchestration.federationImport on a fresh one-shot socket, closed on the
main thread after every tick that carries progress. On a Mac whose
network content filter holds each TCP close for about a second, that
close froze keystroke relay for the whole app.

Allowlist the two relay mutations in shouldUseSharedControlEnvelope so
they take the same support-routed path as federationPull, including the
one-shot fallback for hosts without shared control. Both handlers are
synchronous and idempotent by request id and cursor. Long-running
mutations such as orchestration.workerStart stay one-shot, and
ORCHESTRATION_MUTATION_METHODS is unchanged.

* test(runtime-environments): pin federation relay transport to shared control

Cover the transport of one federation sync tick so a change that
reintroduces a per-tick one-shot socket fails a test:

- a pull, ack and import for one dispatch open no one-shot socket beyond
  the initial status.get when the host advertises shared control, and
  keep all three on one-shot sockets when it does not
- federationAck and federationImport fall back to one-shot with their
  envelope intact when shared control is unavailable
- shouldUseSharedControlEnvelope shares control for the relay reads and
  the two allowlisted mutations, and keeps orchestration.workerStart and
  other mutations on one-shot

* fix(orchestration): prove the worker server contract once per host incarnation

Every federation mutation without caller-proven contract first fetched
status.get to check the orchestration contract capability, then sent the
mutation as a separate call. The contract is now proven once per host
incarnation (runtime id) and each later mutation is pinned to that
incarnation through the existing expectedRuntimeId routing. A restarted host
answers runtime_environment_changed before any effect; the new incarnation is
proved and the mutation resent. A downgraded restart without the contract
receives no mutation, closing the window the old probe left between its check
and the send.

Fixes #37516

* fix(remote-runtime): honour request preparation on shared control

Federation import selects the prefix of its relay batch that fits the
host's inbound request bound through prepareRequest. That preparation was
only applied on one-shot sockets, so once import moved to shared control
its full batch was sent unprepared. Shared-control requests now serialize
through the same preparation step as one-shot requests, and every route an
import can take (shared or one-shot, pinned to a runtime or not) carries it.
The orchestration transport takes prepareRequest and expectedRuntimeId as
one send-options object.

Fixes #37516

---------

Co-authored-by: Blue Thunder Somogyi <bluethunder.somogyi@bastion.com>
2026-11-05 18:19:16 -08:00
Hyeonho Lee (Ethan)andNeil 53fbfded41 fix(shell): open native file pickers as a modal of the window that asked (#37779)
The shell picker IPC handlers (pickDirectory, pickAttachment,
pickAttachments, pickImage, pickRepoIconImage, pickAudio) opened
parentless OS dialogs, so on macOS the picker floated as a free window
that could fall behind Orca instead of attaching as a sheet. Pass the
sender's BrowserWindow to showSingleFlightOpenDialog, which already
falls back to a parentless dialog when the window is missing or
destroyed.

Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
2026-11-05 18:09:17 -08:00
dağhan tektepe 6b9af049b4 fix(github-project): group by Parent issue using the row's parent (#38212)
GitHub returns no field value for the built-in Parent issue field, so
grouping by it put every row in "No Parent issue". Group by
row.content.parentIssue (keyed by URL so multi-repo projects with the
same issue number stay separate) and order groups by issue number.

Fixes #38099
2026-11-05 18:09:08 -08:00
Guderlilian 5ac627732c fix(editor): recognize .razor files as Razor (#38442)
Monaco registers only .cshtml for the Razor language, so Blazor .razor
components opened as plaintext. Add .razor to the Razor registration in
the association generator and regenerate the shared associations JSON.

Fixes #38380
2026-11-05 18:08:59 -08:00
gsxdsmandNeil 219dca09d0 fix(cli): refuse oversized local requests before dispatch (#37734)
The CLI now measures the serialized runtime request against the shared
1 MiB local transport limit before writing any bytes. Oversized requests
fail with invalid_argument and requestSent=false instead of being cut off
by the server mid-frame, and the client skips mutation recovery for these
unsent requests so it never suggests resending the same payload.

Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
2026-11-05 18:08:51 -08:00
Neil 38040c5b80 fix(ssh): blame an SSH directory upload failure by cause, not exit order (#38772)
The upload chose between local tar's and the remote's errors partly by which
process exit Node observed first, so a missing local directory could surface
as the remote tar's truncated-stream error and be classed as host-answered.
It now waits for both, then blames the remote only when the pipe into ssh
failed, meaning the far end stopped reading before local tar's output ended
(a local tar SIGPIPE or write error always comes with that). Any other local
tar failure caused the remote EOF and is kept.

Fixes STA-13013
2026-11-05 18:05:03 -08:00
OrcaWinandOrcaWin db63e7f092 test: remove 594 low-value Codex tests (#38775)
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-11-05 18:04:11 -08:00
GuderlilianandNeil 1d95ea1645 fix(sidebar): highlight the focused chat in workspace card agent rows (#38367)
The focused-agent selector returned null for any active tab that was not
a terminal, so no agent row highlighted while a chat tab was open. Resolve
the focused group's active chat tab and use its structured session pane
key, the same key chat rows and the status bridge use.

Fixes #38156

Co-authored-by: Neil <neil@stably.ai>
2026-11-05 18:03:37 -08:00
Emma HydeandNeil f2be279523 fix(terminal): retry scrollback anchor restore when xterm clamps after resize (#38228)
After a resize/reflow, xterm can silently clamp scrollToLine against stale
scrollbar geometry, leaving the viewport short of the saved anchor. Treat a
mismatch between viewportY and the target line as a retry instead of a
successful restore, so the next frame lands on the anchored line.

Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
2026-11-05 18:03:28 -08:00
b213e4153d fix(native-chat): let Cmd+R rename a chat tab from its composer (#38388)
The file-search text-key guard made the global shortcut handler yield
Cmd+R whenever focus was in an editable field, so the default tab.rename
chord did nothing while typing in a native chat composer. Exempt the
default (non-overridden) tab.rename chord when the target sits inside a
native chat root.

Co-authored-by: Michael Schaecher <276865230+big-sheq@users.noreply.github.com>
Co-authored-by: Neil <neil@stably.ai>
2026-11-05 18:03:19 -08:00
Marco Schäck ea2cb54d41 fix(plugins): let isolated editor frames announce their merge (#38546)
A custom editor whose document session has `merge` posts `editor.merges`,
so Orca hands it disk writes made under unsaved edits instead of showing
the changed-on-disk banner. The frame host forwards only a guest's
`editor.subscribed` and trusted chords to the editor, so in isolated
frames (the desktop default) the announcement was dropped: the merge
never ran and the banner always showed.

`editor.merges` is not input. Page script sends it through
`orca.document.session()` just as it sends `editor.subscribed`, so it
passes the guest filter the same way.

Fixes #38535
2026-11-05 18:03:10 -08:00
Jechan LeeandNeil 7255eb929d fix(browser): keep client hints alive under the temporary tab UA override (#38046)
A UA-only Emulation.setUserAgentOverride blanks every client hint, so
desktop tabs on the temporary override (e.g. Google sign-in) sent empty
Sec-CH-UA-Platform and friends. Restate Chromium's own desktop platform,
platform version, and architecture with the override so hints match the
non-overridden browser on macOS, Windows, and Linux.

Co-authored-by: Neil <neil@stably.ai>
2026-11-05 18:02:58 -08:00
SungmanandNeil 4b638186d2 fix(native-chat): run the equalize pane shortcut from Chat UI panes (#37755)
The equalize-pane-sizes keybinding did nothing while focus was inside a
Chat UI pane because the native chat key handler only matched the split
shortcuts. Match terminal.equalizePaneSizes alongside split right/down and
run it through the terminal pane actions. When the pane cannot equalize
(lone or expanded pane) or lives in a tab group, leave the chord
unconsumed so the composer still receives it.

Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
2026-11-05 17:57:30 -08:00
NeilandClaude 3ca2f1876b fix(browser): name the previewed file in HTML preview annotations (#38771)
Workspace HTML previews are served from a grant-bearing orca-doc-preview
URL that the grab payload sanitizer blanks, so annotations sent to an
agent arrived as "Design Feedback: current page" with an empty URL.

Main now resolves the guest's committed document URL (tracked from
did-navigate, so pushState cannot rewrite it) through the live preview
grant to the host file path, and uses it as the payload page URL when it
is display-safe. The annotation heading keeps Windows paths intact
instead of parsing `C:\` as a URL scheme.

Fixes #38564

Co-authored-by: Claude <noreply@anthropic.com>
2026-11-05 17:57:19 -08:00
OrcaWinandOrcaWin d00f32fa29 test: remove 383 low-value runtime and account tests (#38770)
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-11-05 17:55:06 -08:00
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