* Separate editor tab placement from content ownership
* Show scoped Source Control sections for attached worktrees
* Preserve active diff refresh and validate section catalogs
* Complete folder section test worktree metadata
* Use contextual types for scoped Source Control actions
* Preserve optional scoped action parameters
* Preserve editor ownership through restore, mirroring and workspace removal
* Retire Source Control drafts only after confirmed owner removal
* Limit section work to visible expanded repositories
* Keep editor follow-up actions and bulk closes in their invoking views
* Track the consolidated editor order writer in its owning module
* Use complete typed fixtures for repair contracts
* Preserve live editor placement during layout readback
* Pass projected editor placement directly to serialization
* Read attached worktree lineage from shared graph module
* Retain inferred owner checks and scope placement retirement
* Preserve selection in untouched workspaces during purge
* Exercise editor placements in existing launch and lifecycle fixtures
* Preserve shared buffer prompts and editor strip order
* Pause inactive section context subscriptions
* Keep section recovery and file actions in the folder workspace
* Describe attached Source Control in folder workspace CLI help
* Attribute Git metadata signals once per dispatch
* Watch mounted source control sections outside the warm budget
* Resolve section watches only for served filesystem roots
* Restore restart-stable editor identity across SSH opens
* Apply section terminal routing to explicit Chat choices
* Include cross-store descendants in folder workspace panels
* Keep cross-host recovery on its existing workspace route
* Start cross-host section agents in their workspace without navigation
* Update memoized Source Control labels on language changes
* Remove unused terminal close-to-right implementation
* Suspend inactive Source Control body observers
* Retain base ref query while section observation pauses
* Cover retained base ref search across section visibility
* Resolve note menu ownership from the retained snapshot
* Preserve section agent working directories and background placement
* Keep Source Control sections active while their overlays are open
* Read current settings when Source Control commands execute
* Adapt editor and browser test stores to view subscriptions
* Preserve bound store hooks for ordinary app views
* Trust agent starts outside the placement workspace
* Trust the section worktree through transient launch ownership
* Extract editor follow-up file actions from the panel
* Fail section launches when trust capability lookup fails
Preserve explicit unsupported-host fallback, propagate lookup failures to the existing launch error path, and let the existing transport retry refresh rejected capability probes.
* Follow the moved pane launch test in web configs
* Preserve section file ownership during refresh and close
* Share tab order removal across editor close paths
* Verify companion editor close through the real store
* Close all workspace split copies for legacy mobile file requests
* Keep unmatched mobile close requests within their workspace
* Reuse the agent menu render snapshot
* Keep attached folder roots scoped to their resolved host
* Apply repository trust decisions to the launch trust owner
* feat(remote-runtime): use E2EE v2 for desktop and CLI connections to remote runtimes
Desktop connections to remote runtimes (orca serve, orcad, managed SSH,
browser bridge) and the orca CLI now use E2EE v2 framing for hosts that
support it, and fall back to the legacy framing for hosts that do not.
- One-shot requests try v2 first. A host that refuses the v2 hello
(hosts from before v2) or refuses capabilities on v2 token auth gets
one transparent legacy redial; nothing but the handshake is sent before
authentication, so the redial is safe.
- After a v2 sign-in succeeds, the host's pinned key is remembered in the
profile (shared by the app and the CLI). Every connection to that host,
including long-lived sockets, then uses v2 and never falls back.
- Long-lived sockets keep legacy framing until a host has proven v2, so
they never see an older host refuse a v2 hello.
- Hosts accept clientCapabilities on direct v2 token auth, so desktop and
CLI clients keep their capability negotiation on v2. Relay token auth
stays exact.
* feat(remote-runtime): recover a latched host that was downgraded, without a legacy downgrade path
A host this profile had signed in to over v2 was refused for good if it
was later downgraded below capability-carrying v2 token auth. Hosts now
advertise pairing.e2ee-v2-token-capabilities.v1. When a latched host
refuses v2, a one-shot request asks the host over legacy framing (keyed
to its pinned key, so only the real host can answer) for status.get:
- no capability: the host itself says it was downgraded; the latch is
dropped and the request goes ahead over legacy;
- capability present, or no answer: the request fails with an error
that says to update the host or check the network path. Only the
read-only status check ever ran over legacy.
A refusal faked on the path cannot produce the host's answer, so it
cannot force legacy framing for a latched host.
* feat(serve): refuse public source addresses on orca serve unless allowed
`orca serve` listens on every interface. It now accepts connections from
loopback, private LAN (10/8, 172.16/12, 192.168/16, 169.254/16, fc00::/7,
fe80::/10) and Tailscale (100.64/10, fd7a:115c:a1e0::/48) sources, and drops
public-source TCP connections before HTTP parsing or the WebSocket upgrade.
Public sources stay accepted when:
- --allow-public is passed (orca serve, orcad, serve service install);
- a paired device has authenticated from a public address (persisted flag);
- --pairing-address is a hostname or a public IP literal;
- any device paired before this server first ran the check is still paired.
The startup log states which rule applies; the first refused public address
each hour is logged with the flag name. Desktop listeners and Orca Relay are
unaffected.
* feat(serve): accept on-link public addresses and add --refuse-public
A public source inside a prefix of one of this host's own interfaces (os.networkInterfaces, re-read at most every 5 s) is a same-network peer, such as a LAN client using global IPv6, and is now admitted. Prefixes wider than /16 (IPv4) or /48 (IPv6) are ignored.
--refuse-public (orca serve, orcad, serve service install) refuses public addresses regardless of grandfathered pairings, a remembered public connection or the pairing address; it cannot be combined with --allow-public.
* feat(cli): pair clients with a running Orca via `orca pair`
A headless Orca could only print a pairing offer at startup, so pairing a
phone with `orca serve service install` meant stopping the service. `orca
pair` asks the running runtime for an offer over the owner's local socket
through a new `pairing.createOffer` method (pairing-admin, never granted to
paired clients or phones). It reuses the desktop's offer logic, now shared
by the Settings IPC handlers: mobile offers stay LAN-only unless --relay,
and the first network pairing widens a loopback bind as the desktop does.
Also honor an explicit ORCA_SERVE_RUNTIME=orcad on packaged macOS. Electron
serve needs a desktop login and aborts in an SSH session with nobody logged
in; orcad is plain Node and serves there, at the cost of no in-app updater.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* refactor(cli): move the serve-restore and pair handler groups out of the manifest
handler-group-manifest.ts was already at the 300-line limit on main, so
adding the pair group failed lint. Group the two serve/pairing entries in
their own file, as jira, linear and reference already are.
* test(mobile): pin the desktop pairing IPC handlers' replies and runtime calls
Characterizes mobile:getPairingQR and mobile:getRuntimePairingUrl across
every branch (explicit and default address, LAN-only with no address,
Relay without an address, relay failures, this-computer reach, widening
and its failure, unavailable offers). The same file passes unchanged on
main and on this branch, so moving the offer logic into
pairing-offer-requests.ts did not change the desktop path.
* test(mobile): type the QR stub instead of casting it
---------
Co-authored-by: Office exitnote <h@Offices-MBP.lan>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Neil <neil@stably.ai>
Multi-repo folder workspaces can parent workspaces on this machine and on paired servers, and their children nest under them in the sidebar and on the phone. Task parents are inferred at creation instead of on every lineage refresh.
* feat(serve): add bind address option
* fix(serve): validate service bind address
* fix(serve): validate bind flag values
* fix(serve): reject scoped bind addresses
* docs(serve): document bind option paths
* docs(serve): note that --mobile-pairing needs an all-interfaces bind
* fix(serve): move bind address parsing to shared so the packaged CLI can load it
The packaged CLI ships out/cli and out/shared but not out/main/orcad, so
cli/runtime/serve-bind-flag.js failed the packaged runtime-import check
(and would fail to load) when it imported main/orcad/orcad-bind-address.
Move the module, unchanged, to src/shared and point orcad, Electron serve
and the CLI at it.
* test(serve): pin down the default listener and the --bind startup paths
- Extract the Electron serve listener pin into resolveServePinnedBindHost
and test that serve without --bind stays unpinned (all interfaces, as
before), an explicit --bind pins it from either argv form, and the orcad
browser sidecar stays on loopback.
- Golden the orcad and Electron serve argv without --bind against the
pre---bind output.
- Test that the CLI passes bind only when given and refuses --bind with
--mobile-pairing before launching.
- Guard that serve-bind-flag and the shared serve validation load only CLI
and shared modules, which is what the packaged CLI ships.
---------
Co-authored-by: lw <13579959+bertramlongwuyan@user.noreply.gitee.com>
Co-authored-by: Neil <neil@stably.ai>
Co-authored-by: xuanchangfeng <210147232+xuanchangfeng@users.noreply.github.com>
* feat(cli): attach existing worktrees to folder workspaces
worktree set accepts --parent-worktree folder:<id> (hosts advertise
worktree.set.folder-parent.v1), keeping each worktree to one parent in the
store. --parent-worktree active/current resolves to a folder workspace from
its terminal or enclosing directory, and worktree create without --repo under
a folder workspace lists the folder's Git repositories with a ready command.
* fix(cli): report folder parents and keep typed flags in folder repo choices
worktree show/set report a folder parent; the missing-repo error lists --repo
choices to add to the user's command and names the Attached worktrees panel.
Load and import repairs skip malformed rows and only touch imported worktrees,
an import keeps a worktree's existing parent, a twin-owned folder parent warns,
and folder ambiguity keeps its choices.
* fix(persistence): settle one parent per imported worktree before writing
An Orca server migration import now decides each touched worktree's parent
before any write: an explicit folder parent replaces an inferred worktree
parent, any other disagreement refuses the import. --parent-worktree active
in a folder terminal still lists the folder's repositories.
* fix(persistence): let a folder parent beat hydrate's inferred worktree parent
Load repair and import reconciliation share one rule: an inferred worktree parent loses
to a folder parent when the folder was chosen explicitly or the worktree parent carries
hydrate's orchestration stamp. Import tests now run payloads through the manifest parser
and cover a conflict that appears after staging.
* fix(cli): list folder repos when active resolves to the enclosing folder
worktree create without --repo used only the terminal's env to find the folder, so
--parent-worktree active run outside an Orca terminal fell back to the generic
missing-repo error. Use the parent create already resolved.
* fix(plugins): drop a plugin's approval when its catalog moves it to another repository
* plugins: revokePluginConsent refuses when the plugin service is not running
* cli: orca plugin install/update ask again when a plugin moves to another repository, for every plugin kind
Orca already drops protected strings when it loads a language pack and keeps
its own English text for trust, permission and destructive dialogs.
validate:plugin and orca plugin validate still rejected the whole pack, so the
community directory refused otherwise-good packs. They now report how many
protected strings are ignored as a warning; every other pack rule stays strict.
Antigravity automations refused --extra-agent-args, so unattended agy jobs
could only use the model and effort from agy's global settings.json. Allow
--model and --effort through the existing allowlist; permission, mode, and
other flags stay refused.
Part of #35234 (STA-12678).
A recipe VM workspace runs on its VM's own Orca server, so a phone, the CLI or
the desktop reaching it while the VM slept dialed a server that was not there
and failed. Only the host window's click ran the recipe's resume.
- The VM wake owner (WorkspaceSleepCoordinator) tracks the VM operations it
was asked for and answers where the VM stands: a pending wake or a running
suspend outranks the stored record.
- The host holds a request that opens the workspace (worktree.activate,
defined once in shared code) until the VM is up: three minutes for the
desktop and the CLI, and for a phone only as long as still lets the relay
connect inside the phone's 30-second deadline. A VM still up keeps a sleep
on its way from suspending it; a VM asleep or suspending wakes after what is
running, joining a wake already pending. The open answers waking only when
the hold runs out, wake_failed when the wake fails, and asleep when a Sleep
asked for while it was held won; that open then wakes nothing more. Other
requests answer waking or asleep instead of dialing nothing. A Sleep asked
for during a wake keeps its place in the lifecycle queue and starts once the
wake is done. A caller that gives up stops waiting and frees its queue
place; the wake goes on.
- An open of a server the user disconnected is refused before anything wakes
its VM, and cancelled if they disconnect while it waits. A wake carries the
SSH host's user hold from when it was accepted and every Disconnect since,
so its connect never lifts a hold; the VM still resumes and its new SSH
address is saved for the user's own Connect.
- A wake re-pairs a paired VM's server. The request that waited for it is
held to its caller's pairing as of before the wake (a stale caller is
refused before it can wake anything), then to exactly the pairing revision
the resume produced (including a re-pair its SSH reconnect made), whatever
process now serves it; any other change still
refuses it. A phone link still preparing for an open survives only the
re-pair its own wake made; any other retirement, or a user Disconnect, ends
it, and a link preparing for anything else is retired at once.
- The hold lives in the desktop's environment preparation, which its own calls
and subscriptions, the phone relay and the CLI all go through. A phone open
on a link to a server whose VM is not up gets a fresh link; the old link's
requests are answered waking or asleep. Further opens join a link already
waiting on the wake. A phone link's whole open (server preparation with any
hold, grant sync and the socket) shares one deadline inside the phone's
own, for every server.
- The CLI dials a VM directly, so `orca worktree activate` asks the desktop
(runtimeEnvironment.wakeRecipeVm) to open the VM and reads the server's
pairing again; the refusals (including runtime_manually_disconnected) keep
their codes over RPC, and a host without recipe VMs answers that one RPC
with method_not_found. Hosts that do this advertise
host.recipe-vm-wake.v1.
- A workspace this host runs on a recipe VM over its SSH target wakes on this
host's own activation, through the resume's explicit connect
(wakeWorkspaceVm -> resumeVm -> connectRuntimeOwnedSshTarget). That connect
runs whenever the VM is in SSH mode, even when its recipe had no resume step,
since suspend tore down the transport.
Fixes#36923
Co-authored-by: ggbdpq <ggbdpq@gmail.com>
* feat(cli): add orca host rm for saved SSH targets
orca host rm --host ssh:<target-id> calls the runtime RPC ssh.removeTarget,
the same removeSshTarget the Settings UI reaches, so the tombstone keeps the
passive ~/.ssh/config import from adding the alias back.
Also document how the import treats Match blocks and Include files.
* fix(ssh): clear a removed target's live state and rename in main for every removal path
Removing an SSH target through ssh.removeTarget (orca host rm, paired clients)
left open tabs with stale reconnect state and kept the host's rename, because
only the Settings dialogs cleaned up after their own IPC call. The shared
removal mutation now drops the display-label override and tells the renderer,
which clears the target's state once for all callers.
Fixes#37949
* fix(ssh): clean up after a removed target in the teardown every removal ends in
A recipe VM's cleanup removes its SSH target through removeRegisteredSshTarget
directly, so the window kept that target's live state. The broadcast and the
rename drop now run there, after the target is gone.
Fixes#37949
* test(cli): keep host rm's retarget-flag refusal next to its other cases
Its old file was removed on main.
Fixes#37949
* refactor(cli): give the host, managed-server and VM handler groups their own module
Keeps the handler manifest under the file-length limit.
Fixes#37949
---------
Co-authored-by: Florin Timbuc <florin@sowild.design>
* fix(cli): keep read-only commands working when environment usage can't be recorded
The CLI recorded each environment's last-used time with a strict store write,
so a profile whose directory refuses chmod (EPERM) failed commands whose reply
had already arrived. The desktop already skipped such failures in one path.
Move that best-effort recorder to shared code and route every desktop and CLI
usage write through it.
Fixes#38673
* test: assert the usage write failure, not call counts
Refs #38673
* test: skip the chmod EPERM case on Windows and drop a cast
Refs #38673
* feat(cli): select and remove any saved agent account from `orca account`
`orca account select` and `orca account rm` accepted only OpenCode and
Devin. They now cover every provider with saved accounts — Claude, Codex,
OpenCode, Devin and Antigravity — through the runtime methods the
status-bar switcher and Settings already use (accounts.selectClaude,
selectCodex/selectCodexForTarget, selectData, antigravitySelect and the
matching remove methods). No runtime or wire change.
`--account` takes an id, an email/label (case-insensitive) or `system`.
A name shared by several accounts is refused with their ids rather than
guessing which subscription to bill. `system` is refused for Antigravity
(no system default), for Claude from the WSL bridge (the RPC has no
target and would reset the Windows host), and always for `rm`.
`orca account list` now prints each account's id and a `system` row,
accepts `--agent` for every provider, and adds the Antigravity block when
the host can read it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* test(cli): assert the host refusal for Antigravity accounts from WSL
The host rejects every client-selected WSL target for Antigravity account
management, reads included, so the test now mirrors that refusal and checks
that no select call is made instead of asserting an impossible success path.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* test(cli): keep only the account-selection tests that guard this change
Moves the guards into account.test.ts (the handler layer that owns them) and
drops layer-duplicating, copy-pinning, Cartesian and mock-only cases per
AGENTS.md test rules; un-exports resolveAccountId (test-only export).
---------
Co-authored-by: Maxime AUBURTIN <m@hellomax.io>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* fix(omp): launch OMP chats with their saved model and accept OMP effort
OMP's Chat UI model option names only listed provider/id values, so a
worker started with an OMP role or provider/model:level selector failed
before its first message. OMP resolves those on its launch line, so the
chat's saved model now rides 'omp acp --model', and a new session counts
it as applied. OMP's launch catalog gains its thinking levels, so
--effort is accepted and applied (--thinking in a terminal, the session's
thinking option in a chat).
Tracked in STA-13562.
* fix(omp): keep OMP role chats working after a restart
OMP's ACP model option takes listed provider/id values only, and a load restores the conversation's own model, so a role-started chat failed with "Unknown ACP model" on its first send after an Orca restart.
- New chats keep the saved model on OMP's launch line; resumed chats no longer pass it, so a renamed role cannot stop a good conversation from opening.
- When OMP's model option refuses a value as unknown, Orca applies it through OMP's own /model, the resolver --model uses, as a hidden send-time command. It counts as applied only on OMP's "Model set to" reply and a normal end; cancel or timeout retires the process.
- The command's reply and title notices never reach the chat, its status or unread state.
- Proof from the launch line or /model is retired when OMP changes its model or thinking itself; ordinary model-option proof keeps its existing rule.
- A refusal names the agent and the next step; on OMP older than 18.1.9, whose /model takes ids only, it also names the release to update to, carried on the modelUnavailable fact.
Tracked in STA-13562.
Co-authored-by: Narry <3047793+NarryG@users.noreply.github.com>
* fix(omp): keep role steers in order and say when OMP has no model
- A role steer that OMP's model option refuses now holds its place among queued steers: answers ahead of it are cancelled as they start, and a Stop or teardown while it waits withdraws it without sending /model or the message.
- OMP's "No model selected" (an unresolved launch role, or signed out) is reported as its own failure, "OMP has no model selected. Check your model selection and OMP sign-in, then send again.", instead of a sign-in failure; the next send applies the selection again.
- A cancelled model command reads as not sent, and an option write settles once.
Tracked in STA-13562.
Co-authored-by: Narry <3047793+NarryG@users.noreply.github.com>
* fix(omp): keep OMP's no-model text in the log and test a late /model answer
Tracked in STA-13562.
Co-authored-by: Narry <3047793+NarryG@users.noreply.github.com>
* fix(omp): a Stop still withdraws a waiting role steer after main's turn changes
Main now clears stopAt when a turn finishes, so reserveSteer counts Stops
instead of reading stopAt. Refusal wording moves to acpPromptRejection to
keep the turns file within its line budget; behaviour is unchanged.
Co-authored-by: Narry <3047793+NarryG@users.noreply.github.com>
* fix(omp): a Stop withdraws a role message anywhere in its preparation
A message that falls back to OMP's /model now counts as a held steer from
before its option writes until it is admitted. A Stop in that window
withdraws it unsent, even with no turn running; ordinary messages keep
their Stop behaviour. Restores the launch-role no-resend test main's file
removal dropped.
Co-authored-by: Narry <3047793+NarryG@users.noreply.github.com>
* fix(omp): a Stop before OMP refuses /model reads as a withdrawn message
A Stop the role message already took outranks OMP's own refusal of the
model command, so the message reads as cancelled and never sent rather
than as a model failure. Pins that a Stop with no turn running is still
refused while an idle send's first model-option write is pending: the hold
starts when the send falls back, not before its option writes (the host's
Stop aborts the send's signal there).
Co-authored-by: Narry <3047793+NarryG@users.noreply.github.com>
* test(omp): put the cast's safety note on the line above the cast
* test(omp): cover the new failure copy on phone and desktop, and OMP's thinking levels
Tests the no-model sentence and the update hint in English and one
translation on each surface, every level omp --thinking takes, and that a
missing model classifies as the agent's own refusal.
* test(omp): import the launch module once in the catalog test
---------
Co-authored-by: Narry <3047793+NarryG@users.noreply.github.com>
* fix(cli): use the terminal's worktree when cwd is outside every worktree
Browser, terminal and computer commands run from an unmanaged cwd fell back to the worktree focused in the window. They now use the calling terminal's ORCA_WORKTREE_ID or folder ORCA_WORKSPACE_ID before that default. Fixes#37899.
* fix(cli): resolve the terminal worktree against the catalog
Match ORCA_WORKTREE_ID on the caller's host so ids shared across hosts
go out as identity:, accept folder: ids from either terminal env var,
and keep the runtime default when worktree.list cannot be read.
* fix(cli): refuse a stale terminal worktree instead of using the focused one
When ORCA_WORKTREE_ID names a worktree the catalog no longer lists on the
caller's host, fail with a --worktree hint rather than silently targeting the
workspace focused in Orca. Clear the terminal's workspace env in the test setup
so CLI tests run the same inside an Orca terminal.
* fix(cli): keep the floating terminal on the runtime default
The floating terminal's ORCA_WORKTREE_ID is a sentinel with no catalog row, so it must not hit the stale-workspace error. Word that error as what the CLI can tell: the workspace is not listed, which also covers hidden worktrees.
* fix(cli): ignore terminal workspace ids from another Orca profile
A shell from the installed app that targets a dev build or throwaway serve carries worktree ids that runtime never issued. Reuse the runtime-source fence rule to skip them, so those commands keep the target runtime's default instead of failing.
* test(cli): drop tests that pass without the terminal-workspace fallback
Remove the list-failure and cwd-precedence cases, which pass on main, and the SSH stubs the folder case never reads.
---------
Co-authored-by: Kelvin Amoaba <97001695+AmoabaKelvin@users.noreply.github.com>
`orca terminal read term_abc` failed with a bare "Unknown command" because the
handle landed in the command path. When a term_/structworker_ handle follows a
command that accepts a target --terminal flag, suggest the flag form instead.
Caller-identity --terminal commands are skipped. The terminal-handle prefix
check moves to a dependency-light shared leaf reused by the session-address
codec.
Fixes#38312
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>
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
* 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
* 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
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
orca account list called accounts.list with refreshUsage:false, so on an
idle headless host the JSON's rateLimits stayed null. JSON output now
asks the host to refresh only stale usage windows (the same
refresh-if-stale an accounts.subscribe stream runs), via a new optional
refreshStaleUsage param. The host waits at most 10s for it, so slow
inactive-account probes can't push the CLI past its request timeout;
they keep running and land on the next call. Older hosts strip the unknown field and keep
their prior no-refresh behavior; human output still skips the refresh.
Fixes#37256
* Let a finished worker keep messaging its coordinator
* Tell a finished remote worker why its coordinator is out of reach
* Let a finished worker's ask wait for its answer instead of starting a follow-up
* Share the assignee check, keep the finished lookup in dispatch-lookup, and fix review findings
* Match a finished terminal worker by its process too; cover resume after a follow-up
* fix(automations): accept Pi model and thinking extra arguments
* fix(automations): keep Pi provider pins and refuse unpinned inline keys
* fix(automations): match Pi value options to its argument parser
* fix(automations): refuse Pi model overrides beside unscoped inline keys
* test(automations): cover Pi's last-provider-wins key pin
* fix(automations): refuse any Pi custom Command text with --api-key
* fix(automations): drop the Pi inline-key pin guard
Pi already sends an unpinned inline key to the chosen model's provider and warns;
the user typed both the key and the model, so Orca does not refuse it.
* fix(automations): say the run's worktree was deleted; document plain run-use recovery
* revert(automations): drop the deleted-worktree wording change (not reproducible)
* docs(orchestration): list Tasks on recovery and note run-use drops other Runs
* docs(orchestration): delete the legacy takeover guide; keep orchestration docs to the normal flow
* fix(browser): a split tab create joins a browser pane already beside the caller
* fix(browser): the joined page is the pane's visible tab
* fix(browser): a split with an unknown caller still splits beside the active pane