Commit Graph
1918 Commits
Author SHA1 Message Date
Brennan Benson e2fa611916 feat(native-chat): chat scratch folders expire after 30 days unused (#28394)
* fix(files): never write through a dangling symlink

A path that doesn't exist yet was authorized by walking up to the nearest
existing folder. A dangling symlink fails realpath with ENOENT, so the walk
stepped over it and approved the link's own path; the write then followed
the link and created its target outside the allowed folders. The walk now
continues from the link's target, and returns that real destination.

* refactor(native-chat): rename the chat visuals folder to the chat scratch folder

The per-chat folder agents write into is the chat's scratch space, not only
a visuals folder. Its root becomes <state>/chat-scratch/<hash> (the hash is
unchanged) and the agent variable becomes ORCA_CHAT_SCRATCH_DIR. The old
ORCA_CHAT_VISUALS_DIR is never set and is still stripped from what every
agent child inherits, Pi included. The visuals skill names the new variable
and says the folder is temporary.

* test(native-chat): keep the delivery test to the rename

The launch parity snapshot already pins that every agent drops the
pre-rename variable.

* feat(native-chat): chat scratch folders expire after 30 days unused

A chat's scratch folder was removed only when its record was gone or its
local workspace was proven deleted, so the folder of a long-idle chat
lived forever.

The sweep now expires a chat's folder once its newest journal row and
the newest entry anywhere in the folder (complete, no-follow scan) are
older than 30 days, unless the host holds a running agent, a delivery or
an open dispatch for the chat. The final recheck and a rename to
<hash>.expired run in the chat's session lane and then a per-folder lock
that Orca scratch saves (desktop IPC and host RPC) also take; the delete
runs outside both. A save that settles after the age scan marks that
expiry stale. Leftover .expired folders and the pre-rename
native-chat-visuals root are removed.

The workspace-removed rule and its catalog verdict are deleted.

* fix(files): refuse any dangling symlink in the missing-path walk

Following a dangling link by hand resolved relative targets against the
lexical parent, not the link's real folder: through a folder link the walk
could loop forever or authorize the wrong file. Refuse instead: realpath
can't say where a dangling link leads, but a write would follow it.

* test(files): drop the loop case that duplicated the dangling-link refusal

* refactor(native-chat): trim the scratch expiry and test the real live-work veto

- Remove the other-profile catalog options (running profile id, never-written
  profiles) that only the deleted workspace verdict used; history cleanup's
  reader is unchanged.
- The sweep keeps its original remove-and-warn block; an unused chat's
  folder is parked by one function. Drop the unused `now` option and the
  candidate identity check.
- The live-agent case runs against the host's real runWhileExecutionIdle.

* style(native-chat): keep the session host within its line limit after the main sync

* fix(native-chat): every scratch save and every overlapping sweep share the folder lock

Binary (base64) runtime saves now take the scratch folder lock like text saves, and each folder
keeps a set of expiry candidates so one sweep finishing no longer hides a save from another.
2026-10-14 18:22:39 -07:00
Jinwoo Hong 3b11b7ac94 fix(mobile): phone server rows follow the desktop's other-client and sleeping filters (#28819)
* fix(mobile): phone server rows follow the desktop's other-client and sleeping filters

A phone created through a desktop now records whom it acted for, so the
desktop's Hide other-client workspaces filter keeps the user's own phone
workspaces (locally and on servers) and hides only other people's.
The desktop relay marks each server row its sidebar would hide, and the
rows its own browser or chat tabs keep awake; the phone applies both.

* test(e2e): dedupe sidebar row ids in the visibility spec

* fix(mobile): address review: owner naming, old-server relayed phones, scope trim

* refactor(mobile): record a phone's owner as creator and fold desktop tab activity into the host flag

A phone now records its owner as the creator (this host for a directly
paired phone, the relaying desktop for a delegated one), so the shared rule
is unchanged and onBehalfOf is gone. The relay ORs the desktop's saved
browser/chat tabs into hasHostSidebarActivity, so the phone's status dot
follows too, and local worktree.ps counts the same signals. The old-server
fallback is removed.

* test(e2e): poll the phone and the sidebar together in the visibility spec
2026-10-14 21:18:01 -04:00
Neil 222b780276 Reuse real launcher builds and guard unused runtime RPC imports (#28905) 2026-10-14 18:17:14 -07:00
Neil 67a3aa492d feat(wsl-mover): shell history moves with the project to the WSL host (D1b-6c-1) (#28902)
* feat(wsl-mover): shell history moves with the project to the WSL host (D1b-6c-1)

* fix(wsl-mover): moved WSL history lands under the host's login shell (D1b-6c-1)

* fix(wsl-mover): history follows the shell a pane would actually run, fallback included (D1b-6c-1)

* fix(wsl-mover): history installs follow the real pane shell, wait for live shells, and mark what they installed (D1b-6c-1)

* fix(wsl-mover): history installs wait until live shells can be checked (D1b-6c-1)
2026-10-14 17:56:14 -07:00
Brennan Benson d78363ab36 fix(agent-launch): find a repo's project the way the window does (#28237)
* fix(agent-launch): find a repo's project the way the window does

The host now also takes a project named after the repo as that repo's project, as the window
does, so both pick the same project row and runtime even when a repo was moved out of the
project named after it.

* refactor(agent-launch): share one repo-to-project match with the window

The host's agent-launch lookup and the window's launch preflight now call one projectMatchesRepo
instead of each writing the match out. No behaviour change on either side.
2026-10-14 17:52:41 -07:00
Neil 1f50bd305f Reduce unrelated setup in attention and mailbox fixtures (#28897) 2026-10-14 17:42:56 -07:00
d5324c96e3 fix(ssh): the SSH password dialog no longer closes itself and comes back (#28883)
* fix(ssh): the SSH password dialog no longer closes itself and comes back

A host that drops an unanswered login no longer replaces the open prompt
with a new one, a background dial no longer replaces a connection whose
reconnect is asking the user, one Cancel closes every open prompt for that
host, and a paired client's automatic connect stays automatic.

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

* fix(ssh): a typed password its reconnect has yet to use is not replaced by a background dial

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

---------

Co-authored-by: m4air <m4air@Mac.localdomain>
Co-authored-by: Claude <noreply@anthropic.com>
2026-10-14 17:21:11 -07:00
Neil 39815a086f Reduce CI import, frame-capture, teardown, and shard costs (#28879)
* Avoid unused module imports and trim cosmetic test coverage

* Capture hidden popup frames and attach CDP before guest creation

* Overlap relay test projects within the existing worker budget

* Consolidate native preparation and add an opt-in transform cache comparison

* Release inherited debugger sockets during final E2E teardown

* Refresh unit scheduling weights from all five qualified ARM shards

* Avoid retaining unasserted RPC call history in the delivery model

* Use the supported unconditional background wait syntax

* Overlap unconditional output upload with native preparation

* Verify relay integration stays enabled for ordinary unit callers
2026-10-14 17:10:55 -07:00
Neil 0b1a6007e0 feat(wsl-mover): a moved pane reopens in its last folder on the WSL host (D1b-6b) (#28885)
* feat(wsl-mover): a moved pane reopens in its last folder on the WSL host (D1b-6b)

* fix(wsl-mover): consume a restore hint only when its pane spawns, scope it to its worktree, retry failed installs (D1b-6b)
2026-10-14 16:53:05 -07:00
Neil 66451a2c3a feat(orcad-migration): a capability-gated blob channel beside scrollback (D1b-6a) (#28866)
* feat(orcad-migration): a capability-gated blob channel beside scrollback, for the WSL mover's new data (D1b-6a)

* fix(orcad-migration): the commit names its blob set, prune clears leaked blobs, reserved names refused (D1b-6a)
2026-10-14 16:28:23 -07:00
Neil 6607c54984 perf(ci): cut redundant tests and reuse expensive setup (#28669)
* perf(ci): reduce test overhead and reuse expensive build work

* test(ci): keep worker startup outside failure-case deadlines

* test: isolate push fixture advisory locks

* test(ci): preserve fixed drain retry allowance

* Align CI contracts with current triggers and guide content

* Stabilize consolidated reveal scenarios and retain behavioral CI contracts

* Fix upstream lint blockers while retaining behavior and assertions

* Classify global Jira preferences in the WSL migration inventory

* Align Jira test fixtures with startup and build safety checks

* Include package-test routing dependency in CI checkouts and fixtures

* Keep native stream classes intact in transcript dependency tests
2026-10-14 16:14:27 -07:00
Brennan Benson 850e71e672 Resume in New CLI for OpenCode and OMP chats (#28075)
* feat(chat): Resume in New CLI for OpenCode and OMP chats

OpenCode's and OMP's CLIs have no copy flag, but their chat protocol copies a
conversation (ACP session/fork). For a fork of one of their chats, the chat's
host now has a short-lived agent, launched exactly as the chat's own, copy the
conversation; the terminal then opens the copy with a plain resume. The chat
keeps its own conversation, so the CLI is never a second writer on it.

The copy travels on the existing aiVault.prepareSessionResume fork request as
an optional forkedSessionId; a host that predates it answers without one, and
the client asks for an Orca update instead of resuming the original.

* fix(chat): open an OpenCode 2.x chat's CLI copy with opencode, not opencode2

The copy kept the row's agent, so Resume in New CLI ran `opencode2 --standalone --session <copy>`,
which OpenCode 2.x doesn't install. The chat's host now answers the copy with the agent whose CLI
opens it: the chat's own.

* fix(chat): type the copied conversation's agent as a history agent

* fix: preserve independent chat copies through CLI launch

* test: keep only the tests that pin user-visible behavior

* fix(chat): validate OpenCode copies on their execution host

* test(chat): expect copied OMP forks after main integration
2026-10-14 16:06:50 -07:00
Jinwoo Hong cc7d03d19c fix(daemon): keep every pane in main's layout through a terminal service restart (#28852)
Restarting the terminal service ends each session with a synthetic -1 exit. Main
read those as ordinary deaths and retired every terminal surface from the saved
layout, while the window kept drawing them. Phones and the CLI lost the tabs, and
the next launch restored a split as a single pane.

The restart now labels those exits as replaced, and main records them in its
existing intentional-stop register, the one terminal restarts already use. The
runtime keeps the surface and the window receives the same replaced label.

Claude-Session: p2-headless
2026-10-14 17:25:20 -04:00
Neil cc03142b9c feat(wsl-mover): background writers step aside for a project a WSL move is taking (D1b-5c-3) (#28835)
* feat(wsl-mover): background writers back off a project a WSL move is taking (D1b-5c-3)

* fix(wsl-mover): re-check before renames, quiet owned automations, stable folder ids (D1b-5c-3)

* test(wsl-mover): seed journals one phase at a time and advance existing moves (D1b-5c-3)

* fix(wsl-mover): a branch rename that already ran settles even when the move skips the folder (D1b-5c-3)
2026-10-14 14:13:51 -07:00
Jinwoo HongandClaude Opus 5.5 23103e511a perf(layout): re-project only the written partition in the layout stream (#28845)
The layout stream (layout.subscribe, still unused) re-loaded every session
partition and ran the load's save-and-diff report after each write. A save
now names the partition it wrote, the stream re-loads only that partition
without the report, and the renderer layout client no longer re-subscribes
on a timer. Adds a latency spec variant with a subscriber attached.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 17:13:29 -04:00
Brennan Benson 941a0f2a8a fix(opencode): refuse a picked OpenCode model in any \\wsl$ repo (#28236)
OpenCode in a \\wsl$ repo runs in WSL whatever the project's setting, and Orca can't verify a
model there yet, so picking one is refused, as it already is for a project set to WSL, instead
of being checked on Windows.
2026-10-14 13:10:31 -07:00
Neil 8f83f6b076 feat(wsl-mover): pause a moving project's terminals through Sleep and prove they exited (D1b-5d) (#28820)
* feat(wsl-mover): pause a moving project's terminals through Sleep and prove they exited (D1b-5d)

* fix(wsl-mover): gate again under the fence, swap journal phases atomically, keep sessions frozen through commit (D1b-5d)

* test(wsl-mover): the fence e2e walks the journal one phase at a time, as a move writes it (D1b-5d)
2026-10-14 13:07:50 -07:00
Brennan Benson 8dfaeea572 Resume in New Native Chat for Pi (#28080)
* feat(chat): Resume in New Native Chat for Pi terminal conversations

A Pi conversation started in the terminal now offers "Resume in New Native Chat" in its Session
History row menu and its tab's right-click menu, like the other chat agents: a new Pi chat takes
it over, showing its history and continuing it.

Pi joins the shared adoption path. Its import finds the session file Session History lists under
the row id, and reads it with OMP's decoder, since OMP kept Pi's session format. A Pi chat's handle
names its session file, so the import says so and create mints the adopted handle from the found
file; the committed-create replay compares through the registration's row id, and the adopted
link id is kept to the link-id alphabet. A Pi chat forks a conversation recorded in another folder
instead of resuming it, so the workspace rule covers Pi too.

* fix(pi): preserve adopted session identity and active history

* Fix saved Pi adoption history and summary content

* test: keep only the tests that pin user-visible behavior
2026-10-14 12:31:40 -07:00
Neil b0308f24ef feat(wsl-mover): refuse edits to a project a WSL move is taking (D1b-5c-2) (#28761)
* feat(wsl-mover): refuse edits to a project a WSL move is taking, and drop its background bookkeeping quietly (D1b-5c-2)

* fix(wsl-mover): hide only local rows of a moved project; the host's own row keeps its id (D1b-5c-2)

* fix(wsl-mover): refuse a user's unread toggle, fence branch deletes, host setups and issue commands; settings unfreeze once the host owns a repo (D1b-5c-2)

* test(wsl-mover): the fence e2e refuses edits to moving projects and drops their bookkeeping quietly (D1b-5c-2)

* refactor(wsl-mover): name the update type the fence inspects (D1b-5c-2)

* test(wsl-mover): run the fence tests that open a real store in the Node runtime (D1b-5c-2)

* fix(wsl-mover): a host setup's own repo stays frozen once the host owns it; e2e pins the quiet drop (D1b-5c-2)
2026-10-14 12:13:28 -07:00
Brennan Benson 33b85580b4 fix(terminal): name the fix that applies when --shell is refused (#28235)
A refusal names a \\wsl$ working directory first, since no runtime setting can move it out of
WSL, and names Settings > Project Runtime when the runtime comes from the global default.
2026-10-14 11:49:07 -07:00
Brennan Benson ff1285a936 fix(terminal): judge an agent's --shell against the runtime it runs in (#28234)
* fix(terminal): judge an agent's --shell against the runtime it runs in

`orca terminal create --shell` for an agent is now judged only against the runtime that agent
runs in, not also against the project's saved runtime. So --shell wsl.exe for an agent in a
\\wsl$ repo (saved as Windows host, or with no setting) runs instead of being refused.

* chore(ci): re-run checks against current main

* chore(ci): re-run checks against current main

* chore(ci): re-run checks against current main

* chore(ci): re-run checks against current main

* chore(ci): re-run checks against current main
2026-10-14 11:22:35 -07:00
Jinwoo Hong b85597cbfa fix(serve): restore a remote server's browser tabs after it restarts (#28166)
* feat(serve): keep browser tabs across an orca serve restart

A host with no window now saves the browser tabs it opens in the same shape a
desktop window saves its own, and restores them the same way: listed at once
with their last address, each page started when a client first opens or
drives it. A closed tab is forgotten; a page that ends with the process is not
a close.

* test(e2e): the server's published strip now holds its saved browser tab

* fix(serve): save browser addresses cheaply, open once, restore by page id

Review fixes: a page's address is written onto its saved records without
reloading the layout, and only on committed navigations; reopening a saved
page writes nothing; a command naming only the page finds the workspace that
saved it; a refused save no longer throws from a page event or a close. The
browser-tab map is renamed to say it lists saved tabs too.

* fix(serve): list, switch and close saved browser tabs from commands that name no page or worktree

* fix(serve): a tab list does not start saved pages; only a command acting on a page does

* fix(serve): a pageless command starts the browser tab the layout shows, not the first saved one

A host now records the browser tab a window would show: the one it just
opened, and the one a tab switch picks. With nothing running, a command
that names no page starts that tab; when the layout shows none it starts
nothing and reports no browser tab open.

* fix(serve): report the saved active browser tab as current

Claude-Session: p2-headless

* chore(ratchet): count the serve browser-tab save as a main-process layout writer

Claude-Session: p2-headless

* fix(serve): use the window's active browser tab rule, one scope, one active row

Claude-Session: p2-headless

* fix(browser): pick the active tab among tabs this host can start

A desktop document page or a remote page recorded as active no longer leaves a
pageless command with no tab. Adds a test for the first-saved-worktree scope.

Claude-Session: p2-headless

* refactor(browser): restore saved serve tabs through the page wake point

Saved serve browser tabs now start where a window would mount them: the two
existing wake helpers ask one host hook before messaging a window. The offscreen
backend reports opened and moved pages, so the command layer no longer threads
save, list, select or close hooks. No worktree named and no page running reports
no tab, as a window does.

Claude-Session: p2-headless

* fix(browser): only page creation opens a saved serve tab

A navigation now only updates an existing saved tab, so a late load after a close
cannot re-add it; a restored page sends no open, so its saved title stays.

Claude-Session: p2-headless

* refactor(browser): drop the restore mark the open/navigate split made redundant

Claude-Session: p2-headless

* refactor(browser): save serve tabs from the create command, list unstarted ones as tab rows

The create command saves the tab (it holds the group), so the backend reports only
navigations. Unstarted saved tabs are tab-info rows the existing listings take
as-is, the restore dedupe map goes (a created page registers before createTab
returns), and one lookup gives a page's unified tab id.

Claude-Session: p2-headless

* fix(browser): opening a serve browser tab no longer records it as active

Which tab shows is a view's choice; after a restart the active tab is the one a
window recorded, else the first.

Claude-Session: p2-headless

* fix(browser): give the composed browser command owners the saved-tab host member

Completes the forward merge of main's browser command split (#27576).

Claude-Session: p2-headless
2026-10-14 14:13:11 -04:00
Jinjing c51a85ec65 Shorten worker sub-dispatch instructions (#28565)
* Allow workers to dispatch one level of sub-workers by default

The default nested worker depth was 1, so a coordinator's workers were refused on their first dispatch. Raising it to 2 lets one generation of sub-workers run without changing any settings. The depth constant, the global settings default, and the affected tests now share that value, so they stay in step.

- Set NESTED_WORKER_MAX_DEPTH_DEFAULT and the settings default to 2.
- Pin the cap-1 refusal test to an explicit cap of 1, and point the depth-2 test at the default constant.
- Update the resolver and settings pane tests to expect 2 when unset.

* Keep worker depth at 1 and shorten sub-dispatch instructions
2026-10-14 11:07:00 -07:00
Brennan Benson 38255300d9 fix(worktrees): never delete a folder git no longer tracks (#25075)
* fix(worktrees): a retried or resumed delete only removes the folder it started deleting

A failed worktree delete stays listed for a retry, and an interrupted one is
finished at the next start. Both later deletes force-deleted whatever was at
the recorded path, proven only by "git does not list it and it has no .git",
so an ordinary folder the user put at that path since was deleted with its
files, with no click needed in the startup case (STA-9338).

An accepted removal now records the checkout directory's identity (device,
inode, creation time) in its durable record. A retry, a startup finish, and
the in-slot check before the force-delete refuse any folder at the path that
is not that directory. Startup no longer rebuilds a missing .git for a
registered checkout unless the folder is the recorded one. Records without an
identity (older builds, or no creation time) are only finished through Git's
own checks and never force-deleted.

* fix(worktrees): identify the accepted checkout by inode and a real creation time only

The device number changes when an external volume is attached again, so a
genuine leftover read as a different folder. Windows reports a missing
creation time as a negative number rather than 0, which slipped past the
guard.

* refactor(worktrees): every background removal states the checkout identity it accepted

A new acceptance site that forgot the identity used to compile and silently
write records no retry or resume could ever finish. The resume tests also
assert the identity was read, so their refusals can't pass as unrecorded.

* fix(worktrees): a delete that leaves the user's folder in place also ends the workspace

When Orca decided the folder at a failed or interrupted delete's path was not
the one it started deleting, it dropped the removal record but kept the
workspace's metadata. That metadata still says Orca created a checkout there,
and with no record left, the next Delete of the same workspace (a runtime
Delete from mobile, the CLI or a paired client, or a second desktop Delete on a
row not yet refreshed) went to the cleanup that deletes an Orca-created folder
Git no longer lists, and removed the user's folder after all.

Now, when a removal ends without deleting while Git does not list the path (a
listing ends a failed delete, or a retry, resume or first delete fails with no
leftover to show), the host runs a finished delete's bookkeeping: metadata,
terminal history, caches and change events. The folder and the branch are left
alone, and nothing is ended while Git still lists the path.

* test(worktrees): cover the in-slot folder check when Git cannot remove a registered checkout

Reverting that check left every test green: only the unregistered branch's
check had a test where the folder changes during the delete-slot wait.

* fix(worktrees): a resumed or retried Git delete checks the folder inside the delete slot

The check that the folder at the path is still the one Orca accepted ran
before the watcher gate and the delete-slot wait, which can last minutes behind
other large deletes. A folder restored there meanwhile with a copy of the
checkout's .git file passes Git's own validation, so `git worktree remove
--force` deleted it. The resume and retry now repeat the check inside the slot,
right before Git deletes, as the non-Git branch already did; the refusal skips
the Windows recovery that could otherwise finish the delete itself. A normal
first delete is unchanged.

* test(worktrees): assert the second desktop Delete refuses the user's folder

The handler result is untyped, so the previous catch-all did not typecheck; the
assertion now names the refusal the Delete gets once the workspace has ended.

* test(worktrees): the desktop Delete test's stand-in host ends the workspace without a cast

* fix(worktrees): a listing asks Git before dropping a failed delete, and ends its workspace in the same step

* fix(worktrees): a failed delete Git cannot be asked about stays failed for the next listing to decide

* test(worktrees): the failed-delete record test states that Git still registers the checkout

* fix(worktrees): a restart keeps a failed delete whose checkout is gone, so the first listing ends its workspace

* fix(worktrees): a folder Orca cannot read keeps its failed delete and is never deleted

* fix(worktrees): a failed delete leaves the disk only after its workspace's end is saved

* fix(worktrees): a runtime Delete of a failed delete's row is told when Orca left the folder in place

* test(worktrees): the desktop Delete test's stand-in host returns nothing

* fix(worktrees): a Delete of a failed delete's leftover whose .git Orca cannot read says so

* Revert "fix(worktrees): a runtime Delete of a failed delete's row is told when Orca left the folder in place"

This reverts commit 68d65989de.

The shortcut reaches no real client: the CLI and mobile list the workspace before deleting it, which ends the failed delete first, so they still answer not found (the folder is kept either way).

* fix(worktrees): a failed delete Git could not be asked about is dropped once Git says it still registers the checkout

* revert to main before the standard-model rework

* fix(worktrees): stop deleting Orca-created folders that have no .git and that Git no longer tracks

Deleting an Orca-created workspace whose folder Git no longer tracked and that had no `.git` force-
deleted the whole folder once the user confirmed force. "Orca made a workspace here once" and "there
is no .git" do not prove the folder still holds that checkout: an ordinary folder the user put at
the path was deleted.

Such a folder now gets the existing "Refusing to delete unregistered worktree path" answer, forced or
not. Once the user removes the folder, Delete takes the existing missing-path branch and drops the
workspace. An orphaned worktree whose `.git` file points back at this repo is still deleted after
the user confirms force, as before.

* fix(worktrees): delete a failed or interrupted workspace only through Git

A delete that failed partway (Git dropped the registration, files left) stays listed as "Delete
failed", and a delete a quit interrupted is finished at the next launch. Both later deletes removed
the folder with Orca's own file removal, proven only by "Git does not list it and there is no .git".
An ordinary folder the user put at that path was deleted, at startup without any click.

Orca now deletes a checkout only through Git, plus the existing cleanup right after a Git delete that
succeeded in the same operation:

- Delete on a failed row: if Git registers a checkout at the path, the normal delete runs. If the
  folder is still there and Git does not register it, Delete refuses with "Git no longer tracks
  <path>, so Orca won't delete it. Remove the folder yourself; Orca removes this workspace once the
  folder is gone." and keeps the row. With the folder gone, Delete prunes Git's stale entry, deletes
  the branch as recorded and drops the workspace.
- Startup finish of an interrupted delete: a checkout Git still registers is removed with
  `git worktree remove` and the user's recorded force choice, no longer always forced. If Git refuses
  or fails, the row shows Git's error. The step that rewrote a missing `.git` so Git could force the
  delete is gone. A folder Git does not register becomes a failed row and is not deleted; with the
  folder gone, the rest of the delete finishes.

Records written by earlier daily builds still load; fields this version does not know are ignored.

* test(worktrees): read Git's listing before the Delete call in the real-Git failed-delete suite

* docs(worktrees): say when a failed delete's record ends under the Git-only delete

* fix(worktrees): keep main's handling of a startup finish Git refuses, and say the listing drops the workspace

A startup finish that Git refuses for a checkout Git still registers now clears its record and
leaves Git's normal row, exactly as an in-session failure does on main; the resumed-only failed
record and the listing that put Git's error on Git's own row are gone, and the listing is main's
again. An unregistered folder that still exists is still a failed row, and nothing is deleted.

The refusal now reads: "Git no longer tracks <path>, so Orca won't delete it. Remove the folder
yourself, and Orca will drop this workspace from the list." The listing drops a failed record once
its folder is gone, as on main.

* fix(worktrees): finish a failed delete's branch and push-target remote when it fails

A delete Git fails partway keeps a "Delete failed" row for the folder Git left. The row's record
also carried the branch delete and the push-target remote cleanup for later, but the listing drops
the record once the user removes the folder, so both were silently left behind. Now, once Git no
longer registers the checkout, the failure runs that Git side right away (merged branch only,
stale admin entry, push-target remote), in the one settle path desktop and runtime share. It
deletes no files, and a failure of this bookkeeping is logged without touching the failed row.

* fix(worktrees): unregister an interrupted delete's checkout whose .git Git already deleted

A quit mid-delete can leave a checkout Git still registers but whose .git link Git already
deleted (Git deletes it first on Windows). No `git worktree remove` can validate it, so the startup
finish failed, the record was cleared, and the workspace came back as a normal row no Delete could
ever remove. The startup finish now has Git prune that registration (refused while locked; every
file kept) and then treats the folder as one Git no longer registers: the "Delete failed" row with
the remove-it-yourself message, ending once the user removes the folder. Adds the non-forced
partial-delete case: Git refuses, the files stay, and the row returns as before.

* fix(worktrees): name a worktree Git registers inside a failed delete's folder before asking the user to remove it

The refusal for a folder Git no longer tracks tells the user to remove the folder. When that folder
holds another worktree Git still registers, doing so would delete that worktree's files too. Delete
on the failed row and the startup finish now run the existing nested-worktree check first, so its
message ("contains another registered worktree: <path>") wins, as it did before this branch.

* fix(worktrees): don't prune other worktrees when a delete fails

The Git side of a failed delete started with a repo-wide `git worktree prune`. It did nothing for
the failed delete, whose admin entry Git had already dropped, but it unregistered any other
worktree whose folder was missing at that moment (an unmounted drive), so Orca dropped that
workspace too. The failure now runs only the branch step; the prune stays where it was on main,
in the folder-gone finish and at startup.

* test(worktrees): cover the push-target cleanup every delete job wires for a failed delete

A failed delete removes the push-target remote Orca added through the hook each job creator passes: desktop Delete, runtime Delete, and the startup finish. Nothing tested that wiring; these tests fail when any creator drops it.

* fix(worktrees): say the leftover folder was left on disk instead of what Orca won't do
2026-10-14 10:58:27 -07:00
Brennan Benson 81ed8c0feb feat(runtime-files): the host serves a chat's scratch folder as a file root (#28397)
* fix(files): never write through a dangling symlink

A path that doesn't exist yet was authorized by walking up to the nearest
existing folder. A dangling symlink fails realpath with ENOENT, so the walk
stepped over it and approved the link's own path; the write then followed
the link and created its target outside the allowed folders. The walk now
continues from the link's target, and returns that real destination.

* refactor(native-chat): rename the chat visuals folder to the chat scratch folder

The per-chat folder agents write into is the chat's scratch space, not only
a visuals folder. Its root becomes <state>/chat-scratch/<hash> (the hash is
unchanged) and the agent variable becomes ORCA_CHAT_SCRATCH_DIR. The old
ORCA_CHAT_VISUALS_DIR is never set and is still stripped from what every
agent child inherits, Pi included. The visuals skill names the new variable
and says the folder is temporary.

* test(native-chat): keep the delivery test to the rename

The launch parity snapshot already pins that every agent drops the
pre-rename variable.

* fix(files): refuse any dangling symlink in the missing-path walk

Following a dangling link by hand resolved relative targets against the
lexical parent, not the link's real folder: through a folder link the walk
could loop forever or authorize the wrong file. Refuse instead: realpath
can't say where a dangling link leads, but a write would follow it.

* feat(runtime-files): the host serves a chat's scratch folder as a file root

File commands accept `path:<this host's chat-scratch root>/<32 hex>` and
resolve it only when it names this host's own folder, at the file-commands
seam so the selector never reaches chat placement. They authorize against
the project roots plus the chat-scratch root, refuse files.open/openDiff
for the folder, and agentSession.readVisual's ok results carry the file's
absolute path so a client can build the selector.

* test(runtime-files): name the readVisual helper's parameter type

* test(files): drop the loop case that duplicated the dangling-link refusal

* test(runtime-files): run the readVisual RPC test, which opens a real SQLite store, under Node

* test(runtime-files): put the same-suffix folder inside the authorized root so only the selector refuses it

* test(runtime-files): wait for the stores to close before removing the state folder
2026-10-14 10:49:52 -07:00
Neil dc146c09e2 fix(ssh): the CLI, phone and paired clients no longer list a converted host's retained rows (#28754)
* fix(ssh): the runtime lists a converted host's retained rows as hidden, as the desktop does

* fix(ssh): never name a converted host as uncovered in runtime worktree listings

* fix(ssh): a scoped worktree listing never names a hidden host either
2026-10-14 09:40:07 -07:00
Neil 3879c6d96f feat(wsl-mover): fence moving projects from new terminals and hide them once the host owns them (D1b-5c) (#28668)
* feat(wsl-mover): a WSL move fences its projects from new terminals and hides them once the host owns them (D1b-5c)

* fix(wsl-mover): hide owned rows from every list, follow respelled profile paths, let a live pane reattach (D1b-5c)

* test(wsl-mover): fake stores without type assertions (D1b-5c)
2026-10-14 08:29:08 -07:00
Neil ef3406788d feat(jira): remember repository project defaults (#28622)
Remember an optional Jira project for each repository so its issue picker and Tasks list start with the intended project and site. Keep temporary project overrides separate from the saved default, and choose a mapped workspace destination while retaining the Jira account that supplied the issue. Older hosts that discard the optional saved field return an actionable message.

Keep account ownership separate from repository execution identity. An explicit matching repository owner disambiguates repeated IDs; automatic defaults require ID uniqueness, and overrides include execution-host identity. Preserve existing preselection behavior, saved/manual query classification and linked-view choices. Reuse existing persistence, identity and query builders; check the final UTF-8 query length for searches and project-filtered presets and show a recoverable error before dispatch.

Validation at reviewed 9b174ae4: 205 affected tests, node/web typechecks, seven changed-source quality checks, and 189 fresh rendered assertion invocations with 39 inspected screenshots and 1,413 stable source hashes pass. Independent source and artifact reviews found no blocker. The rendered evidence uses synthetic transports. Ubuntu 22.04 persistence CI stopped before tests because the native watcher crashed during the build; those inputs are unchanged from the preceding passing head. Full CI is not claimed green.
2026-10-14 06:20:47 -07:00
Neil a0c46b405f feat(jira): load attachment images outside issue Markdown (#28631)
Load Jira attachment images separately from issue Markdown so descriptions and comments stay small while screenshots load lazily and open in the existing lightbox. Downloads begin when an image mounts, capture its owning host, require negotiated image support, and use bounded authenticated byte streams and caches. Older clients retain their inline-image handlers.

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

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

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

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

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

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

Co-authored-by: Eugenio Valeiras <eugenio.valeiras@gmail.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-10-14 05:10:29 -07:00
Neil 8c5ac7a890 Make existing Jira assignees searchable and add Assign to me (#28612)
Add source-aware Jira assignee controls on the existing issue workspace and mutation queue. Search, self-assignment, rollback, pending edits, and stale-source replies retain captured account ownership alongside transition/comment forms. Move existing draft/menu callbacks into the existing actions module to stay within the current lint limit; correct the stale file-open arity fixture.

Validation: exact-head node/web typechecks,133 tests/14 suites,all changed source full-file lint and7 quality stages,source lifetime/catalog checks,and independent source/action-extraction reviews passed. Fresh hidden-renderer41/41 checks passed across assignment/transition/comment and extracted title/label actions;35 source hashes stayed stable,zero renderer errors. All owned proof processes cleaned. Full CI still contains protocol/IPCs max-lines violations confirmed byte-identical to current main; separate board/image corrections are in review.

Requested by Andy (@andyhorn) in issue #17052; no external source PR code copied.
2026-10-14 04:45:54 -07:00
NeilandWooseong Kim eb1ee8f908 fix(jira): collect required fields before changing status (#28588)
Collect required Jira transition fields through the shared issue save queue, preserve entered fields after rejected saves, and refresh the comment timeline after successful transition comments. Keep captured-host and older-peer compatibility.

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

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

Co-authored-by: no-support <jwan0131@gmail.com>
2026-10-14 03:37:52 -07:00
NeilandMinho Yi cacce1790d feat(tasks): browse Jira and start workspaces on mobile (#28616)
Browse Jira on mobile and create linked workspaces through the paired desktop, retaining shared task queries and protecting account/session changes.

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

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

Refs #11986 and #26055.

Co-authored-by: Dan Cieslak <dcieslak19973@users.noreply.github.com>
Co-authored-by: kokbee <kok2209@gmail.com>
2026-10-14 03:03:50 -07:00
Neil 4f01443acb refactor(runtime): check workspace creation and metadata ownership (#27635) 2026-10-14 01:18:04 -07:00
Neil fe060f9ba0 refactor(runtime): check workspace removal and recovery ownership (#27624) 2026-10-14 01:16:48 -07:00
Jinwoo Hong d3b17ca9f4 fix(runtime): save a windowless host's chat tabs in its layout (#28354)
* fix(runtime): save a windowless host's chat tabs in its layout

Claude-Session: p2-headless

* fix(runtime): remove a chat's saved tab on the index's hidden notice, which every close reaches

Claude-Session: p2-headless

* fix(runtime): a chat whose record is gone loses its saved tab at startup

Claude-Session: p2-headless

* test: run the windowless chat-layout host test in the Node runtime project

Claude-Session: p2-headless

* fix(runtime): remove a chat's saved tab from whichever partition still holds it

Claude-Session: p2-headless

* refactor(runtime): list saved chat tabs across partitions in the layout module

Claude-Session: p2-headless

* refactor(runtime): read saved chat tabs straight from the store's partitions

Claude-Session: p2-headless

* fix(chat-tabs): save a windowless chat tab under its own tab id

Review pass: the saved tab reuses the id status and attention address, the close
checks for a window before scanning partitions, and the host tests seed readable
records with unreadable, other-host and no-index cases.

Claude-Session: p2-headless

* test(chat-tabs): a window opening a windowless-saved chat reuses its tab

Claude-Session: p2-headless

* test(chat-tabs): check the saved tab with the lookup a window reuses

The window's existing-tab lookup moves to the shared projection so the
windowless-layout test exercises it without importing the renderer.

Claude-Session: p2-headless

* test(chat-tabs): assert the reused tab's identity directly

Drops the shared lookup added for the test; two functions already carry that name.

Claude-Session: p2-headless

* fix(chat-tabs): leave an unstamped chat tab in an SSH partition to that host

Claude-Session: p2-headless

* fix(chat-tabs): count an unstamped chat tab as this host's only in the local partition

A desktop mirrors a remote server's chats into that server's runtime partition
without a host stamp; the startup sweep must not delete them.

Claude-Session: p2-headless

* refactor(chat-tabs): one chat tab writer, one startup match, local partition only

Chats run only on local hosts, so their saved tabs live in the local partition:
the cross-partition listing and its writer go. Open and close share one layout
load, the startup fill and sweep become one function beside the index seed, and
the tab filter is the existing savedSessionId.

Claude-Session: p2-headless

* fix(chat-tabs): restore and sweep saved Claude chat tabs like any structured chat

The Claude skip predates Claude structured chats (#18519, #18560); an id the host
has no record of is already dropped by the readable restorer.

Claude-Session: p2-headless
2026-10-14 04:15:36 -04:00
Neil 5592d98c3d refactor: check terminal layout methods and organize modules (#27614) 2026-10-14 01:15:31 -07:00
Neil 078f8f314f refactor: check terminal output methods and organize modules (#27606) 2026-10-14 01:14:12 -07:00
Neil 8d88c44484 refactor(runtime): type terminal waits and delivery evidence (#27602) 2026-10-14 01:12:50 -07:00
Neil 16c499933b refactor(runtime): check terminal identity modules (#27595) 2026-10-14 01:09:28 -07:00
Neil b20d8ac507 refactor(runtime): remove redundant runtime typecheck suppressions (#27590) 2026-10-14 01:08:08 -07:00
Neil aadc1d489e refactor(runtime): type workspace queries in domain modules (#27588) 2026-10-14 01:06:51 -07:00
Neil 9c72b3c30f refactor: check headless session tab methods (#27584) 2026-10-14 01:05:31 -07:00
Neil 663cab34d6 refactor(runtime): give file operations checked domain owners (#27582) 2026-10-14 01:04:14 -07:00
Neil b84d371a9f refactor(runtime): compose typed browser command owners (#27576) 2026-10-14 01:02:56 -07:00
Neil 230b39c279 refactor(runtime): organize file search and terminal artifacts by domain (#27571) 2026-10-14 01:01:37 -07:00