Commit Graph
13889 Commits
Author SHA1 Message Date
Neil e09d732d17 perf(wsl): pooled wsl.exe command loops and orcad compile cache on the WSL wake path (C4 follow-up) (#27923)
* perf(wsl): run stdin-free probes over pooled wsl.exe command loops; cache orcad's compiled bundle

The WSL wake path made ~37 sequential wsl.exe starts at ~180 ms each. Stdin-free
execCommand calls now go through a few long-lived wsl.exe sh loops (one command at a
time per loop, own session, stdin at EOF, base64-framed output). SSH has no such
method, so its commands and options are unchanged.

orcad enables Node's compile cache inside a client-managed slot, and hands it to the
profile preflight child, which compiles the same bundle.

* fix(orcad): share the slot compile cache with the preflight only in a managed slot

An unmanaged orcad started with its own NODE_COMPILE_CACHE kept that
location for its preflight before; never redirect it into the install dir.
2026-10-13 04:27:48 -07:00
OrcaWinandm4air 121386908b fix(ci): drop the duplicate Codex capacity replay entry (#28297)
#28286 and fba7b753ec both recorded the same capture; the duplicate key fails typecheck and lint.

Co-authored-by: m4air <m4air@Mac.localdomain>
2026-10-13 04:04:14 -07:00
OrcaWinandm4air d0b659604c fix(ci): drop the duplicate Codex capacity replay baseline entry (#28295)
#28192 and #28286 both recorded 'codex-model-capacity-fullscreen' with the same count, and the
duplicate object key fails typecheck (TS1117) on main.

Co-authored-by: m4air <m4air@Mac.localdomain>
2026-10-13 03:53:06 -07:00
OrcaWinandm4air 4dcd0b95c8 fix(ci): record the Codex capacity capture's known serializer divergence (#28286)
#28231 added the codex-model-capacity-fullscreen PTY capture without an entry in the replay
test's known-divergence table, so every PR's test shard fails on main (expected 0, got 12).
The serializer is untouched; the divergence is the same restore limitation the table records.

Co-authored-by: m4air <m4air@Mac.localdomain>
2026-10-13 03:42:08 -07:00
Neil fba7b753ec Cache mobile typechecks and overlap CI setup (#28192)
* ci: run mobile runtime test groups on separate runners

* ci: use a lightweight runner for the mobile result gate

* ci: distribute Bun tests across both mobile runners

* ci: reuse mobile compiler state without adding runner queues

* test: verify eviction of source variants in one cache bucket

* test: baseline existing serializer limits in the capacity capture
2026-10-13 03:35:51 -07:00
Neil 2cbb71963c fix(ssh): show managed hosts once in the connection menu (#28277)
* fix(ssh): show managed hosts once in the connection menu

* fix(ssh): name the merged host row after its SSH host in the status menu
2026-10-13 03:11:24 -07:00
Neil 3a7ec7dff1 perf: reuse verified Node and run the layout check with Bun (#28218)
* perf: overlap preflight daemon smoke and reuse verified Node executable

* Keep measured Node reuse and Bun layout wins; drop smoke scheduling experiments
2026-10-13 02:58:56 -07:00
Jinwoo Hong b589bb3cc9 refactor(runtime): main alone starts setup and default tabs for an activated create (#28153)
* refactor(runtime): main alone starts setup and default tabs for an activated create

An activated CLI or paired create with a window open handed setup and default
tabs to the host window, and with navigation 'all' to paired clients too, so
both could run setup. Main now provisions them itself. Only when main tried to
start setup and failed does it hand setup to the host window, which retries it
where its output or spawn error is visible.

* fix(runtime): hand the window the whole create when the agent fails to start

Main provisioned setup and default tabs after a failed agent start and sent the
window only the startup. The window found tabs, seeded nothing, and the agent
never launched. A failed agent start now provisions nothing and hands startup,
setup and default tabs to the host window, as before this PR.
2026-10-13 05:51:06 -04:00
Neil 4857afde79 fix(ci): route the worktree ready toast's host check by its owner row (#28265)
The ready toast added in #28238 read the Active Server setting through
getKnownExecutionHostIdForWorktree, which the focus-read ratchet rejects,
so every PR's static-analysis job failed on main.
2026-10-13 02:24:27 -07:00
Neil 9e52645e6c Show Codex model capacity failures as red agent status (#28231)
* Show Codex model capacity failures in agent status

* Reduce Codex capacity detection work on terminal output

* Preserve Codex capacity notices across partial terminal frames
2026-10-13 01:58:00 -07:00
OrcaWinandOrca Worker 4559c250b3 fix(windows): restrict secure files in-process instead of spawning icacls (#28135)
* fix(windows): restrict secure files in-process instead of spawning icacls

Every secure write on Windows spawned icacls.exe five times on the calling
thread (the Electron main thread for most stores): verify, /reset,
/inheritance:r /grant:r, verify, verify. Process creation under endpoint
security costs ~0.6 s each, so each write froze the UI for seconds (#26027).
The /reset + /inheritance:r pair is also what behavioural EDR scores as
ransomware tradecraft (#25583).

Add native/windows-security (@orca/windows-security): a small N-API addon
over advapi32 that reads and writes DACLs in-process. It exposes the token
user SID, a DACL read as SDDL, a DACL write (sync and on the thread pool),
a create-with-DACL file write, and an additive grant. Policy and
verification stay in TypeScript.

- writeSecureFile creates each temp file already carrying the protected
  user/SYSTEM/Administrators DACL (CreateFileW + SECURITY_ATTRIBUTES), reads
  it back once, and renames it. No ACL is modified on the write path and no
  process starts. Before: 5-7 spawns, 94-138 ms per write. After: 0 spawns,
  ~0.5 ms.
- A secure directory is verified once per process. It is rewritten only
  when verification fails, on the thread pool, because the write walks
  every descendant (~2 s on a 29k-entry userData).
- The verifier follows OpenSSH's rule: only the user, SYSTEM and
  Administrators may be granted anything, and the path must be protected
  (or, for a file, inherit from a secure parent). DACLs written by the
  icacls-era hardening pass unchanged, so there is no migration.
- If the addon cannot load, the write still happens and reports
  unrestricted, logged once. There is no icacls or PowerShell fallback.
- grantDirAcl, the startup userData grant, and the install-dir probe and
  package repair use the same addon, so whoami.exe and icacls.exe are no
  longer spawned anywhere at runtime.
- orcad's Windows slots ship the addon beside their bundles (built per
  arch, PE-machine checked). Its data-root privacy check fails closed, so
  without the addon a Windows orcad would not start.

Fixes #26027
Fixes #25583

* refactor(secure-file): harden existing Windows files through the sync lane

The read path called restrictWindowsPathSync directly and recorded success
itself; the existing sync lane already does both. One call site fewer.

* refactor(windows-security): one load, one rule, no fallthrough (review #28135)

- The descriptor parser is anchored, so a conditional ACE (its body holds
  parentheses) or anything else it cannot read fails closed instead of
  verifying the ACEs before it.
- findSecureDaclViolation is one flat rule: protected, allow rules only for
  the user, SYSTEM and Administrators (OpenSSH's ACE rule), and full
  control for the user. The inherits-from-a-secure-parent exception is gone:
  it accepted exactly what loosening the parent reaches.
- The addon and the user SID load once, behind one wrapper that adds the
  extended-length prefix to every path and runs async DACL writes to one
  path in order, so a startup grant cannot land after a re-lock and restore
  the broader DACL.
- With the addon loaded, a failed born-restricted create throws with the
  matching Node error code instead of falling back to a plain write.
- A grant leaves a NULL DACL untouched instead of turning 'everyone' into
  'only this SID'. WriteFile runs once and fails on a short write.
- The artifact create-intent store writes through writeSecureFile and drops
  its own temp writer, directory LRU and synchronous directory re-lock.
- Test seams the global fake makes redundant are gone.

* fix(packaging): fail a Windows build whose workspace addon binary is missing or foreign-arch

The @orca/windows-* workspace links exist on every host, so their presence
proved nothing: a missing or wrong-architecture .node only surfaced at
runtime, as unrestricted secure files or an orcad that refuses to start.
beforePack now reads each addon's PE machine against the target arch.

* refactor(windows-security): prune what the native ACL left dead (review #28135)

- Sync-spawn ratchet: hardenSecurePath and the sync-flag exemption it alone
  used are gone, and the advice and baseline header no longer say secure
  writers run icacls.
- The install-dir probe reads ACEs through parseSddlDacl, so an ACE it
  cannot parse fails the whole reading closed instead of being skipped; AC
  is a resolved alias there now.
- The packaging check reads the addon binary through node_modules, the copy
  that ships.
- No winCode-to-errno table: nothing reads .code off a failed secure write.
- The load memo is not mutated; secure-path keeps its own log-once flag.
- grantInstallDirPackageAcl is a plain function, and secure-file restricts
  a Windows directory and a POSIX path directly instead of through one
  two-platform helper.

* fix(windows-security): resolve LA in real-ACL tests; tear a file whose close fails

- The GitHub Windows runner is the built-in Administrator (RID 500), whose
  SID reads back as LA. Production already resolves it through
  localDomainSidOf; the win32 test helpers parsed without it. One shared
  readDaclOf resolves aliases for every real-ACL suite, and the verifier's
  unit test covers an LA-aliased directory and LA as a stranger.
- createFileWithDacl treats a failed CloseHandle like a failed write (lost
  data on a network volume) and deletes the torn file.
- Drop a duplicate test reset and a typo.

* fix(windows-security): load only from inside the app; read DACLs as structure (review #28135)

- Loader: orcad's staged binary first, then the workspace package only if
  its real path is inside the app (process.resourcesPath, or this
  checkout). A bare specifier otherwise walks every parent up to
  C:\node_modules, which any local user may create, and orcad holds
  credentials.
- The addon reads a DACL as { protected, aces: [{ type, flags, mask, sid }] }
  via GetAce, with a SID only for plain allow/deny ACEs and null for a NULL
  DACL. findSecureDaclViolation and the install-dir probe check that
  structure, so conditional, callback and object ACEs fail closed. The SDDL
  parser, its alias table, LA/localDomainSid resolution and getDaclSddl are
  gone; SDDL is only the write format.
- Install-dir tree pass: files flagless as before, but a directory only when
  its own DACL is protected (inheritably). Re-granting inheriting
  directories re-propagated each subtree (3.2 s for 1,093 directories). The
  root is no longer granted, or counted, twice. The two passes are plain
  functions behind a { root, tree } seam.
- One namespacing (path.win32.toNamespacedPath) at the addon boundary, also
  the per-path ordering key; the comment states the real sync-grant
  invariant.
- restrictWindowsDirectory(dir): Promise<boolean> replaces the callback
  lane; the reporter is guarded once, so the 'settle' stage is gone.
- Sync-spawn ratchet: the secure writers no longer spawn or block, so they
  leave the gate (109 -> 61 uses).
- Errors name the Win32 call and code only; a failed
  GetSecurityDescriptorControl fails the write instead of defaulting to
  unprotected.

* test(secure-file): assert the durable POSIX write's outcome, not the old chmod order

writeSecureFile now chmods the temp file to 0o600 before fsyncing it, so
the fsync also covers the mode change and the file is owner-writable when
opened. Assert what matters under umask 0o200: the write succeeds, the
temp file is fsynced read-only, and the published file is exactly 0o600.

* fix(windows-security): namespace paths with the host's toNamespacedPath

path.win32.toNamespacedPath rewrote POSIX temp paths (/tmp/x -> \tmp\x) in the
Linux runs of the Windows-logic unit tests, so the fake addon wrote to the wrong
place. The addon only runs on Windows, where the host function is the win32 one;
elsewhere it is the identity. The long-path assertion runs on Windows only.

* test(windows-security): assert the install-dir long-path prefix on Windows only

The prefix comes from the host's toNamespacedPath, which is the identity off
Windows; the Linux run of this suite expected the win32 escape.

* fix(windows-user-data-acl): grant the root even when its children cannot be listed (review #28135)

A readdir failure on userData aborted the whole grant, so the root grant
that could repair a DACL refusing the listing was never attempted. The
listing failure is now recorded, the root is still granted, and the
marker stays withheld so the next launch retries.

* test(windows-security): wait for relock propagation, accept Entra ID SIDs (review #28135)

- The large-relock test waited only for the parent to turn protected,
  which happens before propagation reaches the children, then read a child.
  It now waits on the parent and both ends of the child range.
- The token-user test rejected S-1-12-1 (Entra ID) accounts, failing the
  suite on Entra-joined dev machines.

---------

Co-authored-by: Orca Worker <orca-worker@localhost>
2026-10-13 01:56:55 -07:00
Neil 7246a0e269 Fix delayed agent launches such as sleep 30 && claude (#28205)
* fix(terminal): preserve preparation and discovery for delayed agent launches

* refactor(agents): centralize launch intent recognition

* refactor(agents): keep launch intent in one module apart from process recognition

Move recognizeAgentLaunchCommand next to the sleep-prefix scanner so the strict
process-table matcher is untouched, name the module away from the agent-launch
funnel files, share one executable-name normalizer, and colocate the
preparation tests with their modules.
2026-10-13 01:55:18 -07:00
OrcaWinandm4air acea005ed8 fix(linux): a server run from an extracted AppImage registers the stable launcher (#28241)
A supervised serve started through the stable launcher runs from one extracted generation with
no $APPIMAGE. Its CLI registration then refused Orca's own ~/.local/bin/orca-ide (a link to
the stable launcher) as a non-Orca command, and pointed the bare `orca` dispatcher at the
generation's launcher, which a later update prunes. Both now resolve to the stable launcher.

Fixes BUG-8 from the real-host Linux test run.

Co-authored-by: m4air <m4air@Mac.localdomain>
2026-10-13 01:55:07 -07:00
Jinwoo Hong aceebea0a4 fix(terminal): name a new tab's first pane so its saved layout is never empty (#28168)
* fix(terminal): name a new tab's first pane so its saved layout is never empty

createTab now always mints the first leaf id, so the window never saves a
terminal tab with an empty layout root. With no empty roots coming from the
window, main's save-time swap that kept its own root over an empty window
root (from #1909) has nothing left to guard, and is deleted.

* test(persistence): an empty-root layout from an old session still loads and gets its pane

* fix(persistence): name an old session's empty layout root once at load

A session saved before tabs named their first pane can hold an unmounted tab
with an empty root. If main bound a terminal there, the window's next save of
that tab dropped the binding once the save-time swap was gone. Name the pane at
load instead (the same pane the window would mount), so no save carries an
empty root. The rootless-pane resolver moves to shared so main and the window
pick the same pane.

* test(persistence): name the bind-then-stale-write order the load repair covers

* fix(persistence): name an old empty tab's pane with the stable headless leaf id

The load repair fell back to a random id, so an unsaved headless serve gave a
phone a new tab::leaf id on every restart. Use deriveHeadlessLegacyTerminalLeafId,
which phones already saw for these tabs. Also import the rootless resolver from
shared directly and return the loaded state unchanged when nothing needs repair.
2026-10-13 04:48:20 -04:00
OrcaWinandm4air 5929350944 fix(linux): direct AppImage serve honours the chosen profile and --supervise (#28227)
* fix(linux): run a direct AppImage serve through the CLI so it keeps its profile and supervisor

A direct `Orca.AppImage serve` stayed in the Electron process and skipped the CLI's serve
front end, so it ignored ORCA_USER_DATA_PATH and silently dropped --supervise. It now execs
into the bundled CLI (same pid, so service managers and signals still reach it), translating a
leading --user-data-dir into ORCA_USER_DATA_PATH. Also lists terminal-daemon and reference as
CLI commands so a direct binary launch of them reaches the CLI.

Fixes BUG-1, BUG-3 and BUG-10 from the real-host Linux test run.

* fix(linux): drop desktop switches anywhere in a direct serve launch

* fix(linux): keep a direct serve's --no-sandbox for the Electron server the CLI starts

---------

Co-authored-by: m4air <m4air@Mac.localdomain>
2026-10-13 01:45:54 -07:00
Neil 8cd5bb2a74 feat(wsl): deploy and launch orcad inside a WSL distro (WSL-via-orcad C4) (#27819)
* feat(wsl): deploy and launch orcad inside a WSL distro (WSL-via-orcad C4)

* fix(wsl): keep the install-dir fallback on the PATH detection searched, so a /mnt/c shim cannot re-enter

* fix(wsl): call the install-dir resolver exactly as before when no PATH is given

* fix(wsl): import orcad artifacts from their new orcad/ home

* fix(wsl): adopt an orcad serving in the distro that the activation record does not name

* fix(wsl): filter /mnt/<drive> PATH entries only in an orcad launched as a WSL host

SSH-managed orcad inside a distro and Orca under WSLg kept detecting
drive-mounted CLIs before this stack; the setsid launch now marks itself
with ORCA_WSL_ORCAD_HOST=1 and only that process filters.

* fix(wsl): keep the install-dir fallback on the process PATH outside a WSL host orcad

On Windows the preflight env merges the persisted registry Path, which
the fallback never searched before; only the marked WSL host runtime
now passes a filtered PATH.
2026-10-13 01:29:19 -07:00
OrcaWinandm4air bc519c57fa fix(serve): say what headless serve actually needs, and stop repeating the supervisor's restart line (#28230)
- A headless serve with no X display told the user to run `orca-ide serve`, which they just
  ran. Serve now gets its own message: it needs an X display, Wayland alone is not enough, and
  which X virtual framebuffer package to install (now including Arch's xorg-server-xvfb).
- The supervisor's restart line read 'Orca serve Orca serve exited via SIGKILL.; restarting'.
- The supervisor and 'serve service status' gave different advice after giving up; both now
  give the same, correct advice.

Fixes BUG-2 and BUG-9 from the real-host Linux test run.

Co-authored-by: m4air <m4air@Mac.localdomain>
2026-10-13 01:28:02 -07:00
Neil 93e66e1f0e Close worktree ready notification when its workspace opens (#28238)
* fix: dismiss worktree ready toast when its workspace opens

* fix: keep ready toast bound to its host and settled navigation

* fix: project ready notification ownership for paired hosts

* fix: retain captured host for legacy ready notifications
2026-10-13 01:09:51 -07:00
OrcaWinandm4air 1b9a408b8c test(daemon): a Node-hosted launcher keeps a busy Electron-hosted daemon and its terminals (#28211)
* test(daemon): a Node-hosted launcher keeps a busy Electron-hosted daemon and its terminals

Runs the desktop daemon launcher under Electron (ELECTRON_RUN_AS_NODE), opens a terminal on the
daemon it starts, then relaunches the launcher on plain Node at the same and at a newer version.
The busy Electron daemon is adopted as a normal target with its shell intact, and only after it
retires on its own does a Node daemon take the endpoint. Linux only.

* test(daemon): stop every launcher a failed adoption test leaves running

Share the launcher window and session harness through the fixture, track each launcher child,
kill it if its startup line never arrives, bound quit, and close any left open in afterEach.

---------

Co-authored-by: m4air <m4air@Mac.localdomain>
2026-10-13 00:42:38 -07:00
OrcaWinandm4air 49a1129aa6 build(linux): carry the pinned Node inside the AppImage so serve's slot works offline (#28204)
* build(linux): carry the pinned Node inside the AppImage so orca serve's slot works offline

Linux packaging places the pinned glibc Node at resources/node-runtime/bin/node before the
glibc-floor gate, proves it serves one PTY with the packaged daemon entry, and deb/rpm exclude
it through fpm. orca serve's orcad slot copies that Node (hash-checked) instead of downloading.

* test(serve): type the bundled Node fixture

---------

Co-authored-by: m4air <m4air@Mac.localdomain>
2026-10-13 00:39:39 -07:00
e9285f0f58 fix(floating-workspace): open the floating panel instead of blanking the main window on host reveals (#28184)
* fix(floating-workspace): reveal workspaces where they live instead of selecting the floating one

Every renderer path that takes the user to a workspace hand-wrote the main-window
selection sequence, so a floating workspace id (terminal switch, CLI focus, agent
launch, notification and mobile reveals, terminal handle links) became the main
window's selection and blanked it while the floating panel stayed closed.

revealWorkspaceSurface is now the one way to show a workspace: the floating
workspace opens its panel and leaves the main window alone, any other workspace
becomes the main window's selection and a visit. selectWorkspaceSurfaceOwner is
its non-navigating twin for tabs opening beside the user. The ad-hoc floating
checks in worktree activation, http links, the editor close queue, Activity and
open-terminal-beside-tab now go through the same module.

Co-authored-by: Velm14 <10477408+Velm14@users.noreply.github.com>

* fix(notifications): bind clicks for floating and folder workspace terminals

A terminal notification only bound its click when the worktree id carried a
repo ("repoId::path"), so floating-workspace and folder-workspace terminal
notifications did nothing when clicked. ui:focusTerminal now reveals any
workspace where it lives, so the click binds whenever there is a pane to focus
and ui:activateWorktree is sent only when a repo exists.

Co-authored-by: YuriNachos <19365375+YuriNachos@users.noreply.github.com>

* refactor(floating-workspace): tighten the workspace surface reveal

Move the floating-owned check into floating-workspace-panel-reveal as
revealFloatingWorkspacePanelFor so worktree activation no longer depends on
workspace-surface-reveal, rename selectWorkspaceSurfaceOwner to
selectWorkspaceSurface with a named state type, reuse the panel visibility
selector, and flatten the IPC bridges' surface-owner branches.

Co-authored-by: Velm14 <10477408+Velm14@users.noreply.github.com>

* fix(floating-workspace): leave the floating setting alone before settings hydrate

Host reveals (CLI focus, mobile, notification clicks) can arrive before
settings load; re-enabling the floating feature then would overwrite a
saved `false`. Unhydrated settings now skip the reveal.

Co-authored-by: Velm14 <10477408+Velm14@users.noreply.github.com>

* refactor(floating-workspace): drop extra reveal concepts

Co-authored-by: Velm14 <10477408+Velm14@users.noreply.github.com>

* fix(floating-workspace): keep main's setActiveWorktree call shape for plain reveals

Co-authored-by: Velm14 <10477408+Velm14@users.noreply.github.com>

---------

Co-authored-by: Velm14 <10477408+Velm14@users.noreply.github.com>
Co-authored-by: YuriNachos <19365375+YuriNachos@users.noreply.github.com>
2026-10-13 03:33:11 -04:00
Brennan Benson e2751d4318 After a restart, an agent tab asks the terminal host what is running, never "Couldn't confirm the agent started" (#28043)
* fix(agent-launch): after a restart a launch pane is an ordinary pane, never "couldn't confirm"

A launch pane's spawn read the launch record (and the tab's saved outcome) when no launch was
running for it, so a row left pending by a quit showed "Couldn't confirm the agent started." The
record never observes the process; the terminal host does. With no launch running in this process
the pane now spawns exactly like any tab: a live agent reattaches, an exited or absent one gives a
fresh shell, and a gone daemon shows the normal unavailable state.

- the running launch tells its pane how it ended in memory (finish carries the verdict); only a
  definite failure says "couldn't start", an unknown outcome leaves an ordinary pane
- deleted: agentLaunchPaneVerdictFromRecord, listAgentSessionOperationRowsOwningPane, the
  `unconfirmed` verdict and its notice string, the tab's persisted launch pane, ownedPane writes
- rows written by an older build that carry ownedPane are ignored; they expire with the row

* test(agent-launch): the restart pane test joins the attach suite it shares mocks with

* fix(agent-launch): a launch pane whose agent couldn't start keeps its notice for the session and never spawns a shell under it; a reload saves only a pending launch

* test(agent-launch): drop assertions on code that is gone, and the taking-back case the IPC suite already pins

* fix(agent-launch): narrow the finished-pane check, and test the saved session's launch pane with a typed tab

* fix(agent-launch): a launch whose outcome is unknown still says "Couldn't confirm the agent started" for the session, and attaches its agent if it turns up

* fix(agent-launch): main keeps a failed or unknown launch's answer for its pane until it quits, so the pane never spawns a shell under its notice; the window's spawn gate goes

* fix(agent-launch): a failure kept for a pane goes when the pane is closed during its launch, moved to another tab, or its workspace is removed; comments say what an unknown outcome and a reload now do
2026-10-13 00:31:12 -07:00
Brennan Benson f84451842d feat(settings): move Permissions for new chats into the Chat page's Behavior section (#28212)
The new-chat permission default sat alone above Appearance with its own header.
It is a behaviour choice, so it now renders as the first row of the Behavior
card, laid out like the rows already there. Its search entry and section id are
unchanged, so search and deep links still land on it.
2026-10-13 00:24:16 -07:00
OrcaWinandm4air 8e4e7be869 feat(daemon): pace automatic terminal restores on the desktop too (#28202)
* feat(daemon): pace automatic terminal restores on the desktop too

The desktop daemon now enforces the restore budget that orca serve and orcad
already enforce: the visible pane first, at most 3 automatic restores at once
(1/s), at most 8 live per machine, and the rest stay sleeping until opened.
ORCA_TERMINAL_SPAWN_ADMISSION=observe rolls newly launched daemons back to
count-only.

* fix(daemon): never put a user's own resume back to sleep; keep every refused old-client restore

A user-resume past the per-client allowance is still paced as automatic work
but is no longer deferred for a pause, the live cap or its deadline, so a visible
pane is never put back to sleep in front of the user. The legacy restore record
holds as many refusals as the host could run.

---------

Co-authored-by: m4air <m4air@Mac.localdomain>
2026-10-13 00:18:08 -07:00
NeilandSamuel Vila f118137dd3 chore(editor): credit earlier close and selection fix (#28063)
Samuel Vila (@SamuelAdigos) independently submitted both editor guards
and regression tests in #28063 before #28120 was opened. The narrower
implementation landed in #28120; this follow-up records credit for his
earlier contribution.

Co-authored-by: Samuel Vila <svila.smr@gmail.com>
2026-10-13 00:05:25 -07:00
Neilandm4air 687190e9f1 fix(ssh): let existing profiles migrate without losing client history (#28141)
* fix(ssh): migrate released profiles with retained client state

* fix(ssh): reuse the shared adoption rule when the local and host copies overlap

The host copy replaces the older local one only where it holds tabs, the
catalog places the workspace on that host, no other host claims it, no
worker fence or close record disagrees, and local drafts are kept. Elsewhere
both copies export and must agree, as on main.

---------

Co-authored-by: m4air <m4air@Mac.localdomain>
2026-10-13 00:04:25 -07:00
Neil 099bb76501 feat(wsl): detached distro holder re-adopted from a pid file (WSL-via-orcad C3) (#27800)
* feat(wsl): detached distro holder re-adopted from a pid file (WSL-via-orcad C3)

* fix(wsl): observe an early holder exit from spawn, not after the record write

* fix(wsl): release polls through a mid-exit unverifiable read until the holder's exit is proven
2026-10-12 23:59:13 -07:00
Jinwoo Hong d31f37c2dd chore(session): delete three session paths nothing calls any more (#28152)
* chore(session): delete three session paths nothing calls any more

- session:set-sync IPC (bridge, type member, web adapter stub); quit uses
  app:stage-before-unload-sync
- the session-write persist gate (shouldSchedulePersist /
  subscribeToPersistGateOpen); the one production subscriber passes neither
- reconcileDeadSessions and the binding's reconcileIfSessionDead; the live
  reconcileIfSessionMissing path stays

Tests that drove these are retargeted to the live paths or removed.

* chore(session): name shouldPersistWorkspaceSession in the deferral comment
2026-10-13 02:52:09 -04:00
Brennan Benson f0ccfcc0d3 fix(native-chat): seed the chat permission default from Claude's and Codex's own arguments (#28100)
* fix(native-chat): seed the chat permission default from the Yolo/Manual toggle as it shows

The one-time copy into Settings -> Chat -> Permissions gave Full access only
when every agent's arguments were exactly the bypass flag, so a profile the
toggle shows as Yolo (agents set differently, or an extra flag) got Ask.
Follow the toggle: Manual seeds Ask, anything it shows as Yolo seeds Full access.

* fix(native-chat): seed the chat permission default from Claude's and Codex's own arguments

The Yolo/Manual toggle has no saved value: it summarises every agent's arguments
and shows Yolo for any mix, including after Manual when another agent keeps custom
arguments. Seeding from it would grant Full access to people who chose Manual.
Read what Claude and Codex chats checked before they had modes instead: Full
access when both launch with their bypass flag, otherwise Ask.
2026-10-12 23:47:37 -07:00
BearMett 52e88e4469 fix(editor): keep a pinned duplicate open on close and ignore setActiveFile for a missing file (#28120)
* fix(editor): keep a pinned clean sibling open when closing its same-owner twin

Closing a document also closes clean same-owner duplicate records; a record
whose tab is pinned is now left open with its tab.

* fix(editor): ignore setActiveFile for an id with no open file

A stale tab or pane id could make a missing file active and persist it as the
workspace's active file.
2026-10-12 23:42:06 -07:00
Jinwoo HongandYEONGUK LEE d26b3b83a7 fix(i18n): translate Settings help as whole sentences instead of fragments (#28185)
* fix(i18n): correct Korean Settings translations

Consolidates the Korean Settings corrections from the community audit
(#27820) into one change. 514 catalog values are corrected: reversed
meanings (session search privacy notice), literal machine glosses
(Custom as 관습, gutter as 배수구, Open as 열려 있는), the terminal
caret named after the Cursor app, and Latin terminal/agent/repo left
inside Korean prose. Every value is pinned in the ko key overrides so a
catalog repair cannot revert it.

Co-authored-by: YEONGUK LEE <contact@abruption.dev>

* fix(i18n): keep the contributor's wording where it was already correct

Co-authored-by: YEONGUK LEE <contact@abruption.dev>

* fix(i18n): translate dictation shortcut help as complete sentences

* fix(i18n): retain existing dictation copy in other locales

* fix(i18n): translate complete settings count sentences

* fix(i18n): translate Git settings as complete rich sentences

* fix(i18n): clarify fast-forward protection in Korean Git help

* fix(i18n): translate repository hook guidance as complete sentences

* fix(i18n): delete settings fragment keys replaced by full sentences

The four settings sentence rewrites left their old fragment keys in every
catalog, kept alive only by tests asserting the new es/fr/ja/zh sentences
equal the old fragments concatenated. Drop those tests and the 32
now-unreferenced keys from all catalogs and the Korean overrides.

Co-authored-by: YEONGUK LEE <contact@abruption.dev>

* fix(i18n): align Korean dictation, Git, and setup-wait help with the English

Name the dictation modes with the same 토글/길게 누르기 labels as the mode
buttons, drop the Git fast-forward clauses the English does not contain,
and replace the leftover Latin "agent" in the setup-wait option.

Co-authored-by: YEONGUK LEE <contact@abruption.dev>

* fix(i18n): translate the new settings sentences faithfully in es, fr, ja, and zh

The sentence rewrites were assembled from the old machine-translated
fragments, carrying over English plural "s" suffixes (ja/zh), broken
agreement and an inverted meaning (fr), ungrammatical clauses (es),
word-salad word order (ja/zh), and missing spaces before "when one exists".
Retranslate each from the English. Also name zh's Hold dictation mode 按住
instead of 抓住 ("grab") so the button matches its description.

Co-authored-by: YEONGUK LEE <contact@abruption.dev>

---------

Co-authored-by: YEONGUK LEE <contact@abruption.dev>
2026-10-13 02:37:08 -04:00
Neil 1e48f446fb feat(wsl): orcad host transport over wsl.exe (WSL-via-orcad C1) (#27792)
* feat(wsl): WslOrcadHostTransport — orcad host transport over wsl.exe

* fix(wsl): key the WSL orcad identity to the runtime environment
2026-10-12 23:32:38 -07:00
Brennan Benson 484a54081a feat(feature-tips): make Enable Now the full-width action beside a plain Leave Off (#28199)
Drop the 'New agent tabs still open in the terminal' line and the taller
frame it needed.
2026-10-12 23:28:50 -07:00
Jinwoo Hong cbeb537146 fix(emulator): stop only the named simulator's stream helper (STA-10307) (#28176)
* fix(emulator): stop only the named simulator's stream helper (STA-10307)

serve-sim declares --kill [device] with an optional value, so the old
['--kill', '-q', udid] left it unset and serve-sim killed every stream.
Bind the device with --kill=<udid> and pin the behaviour with a contract
test that runs the bundled serve-sim against fake helpers.

* test(emulator): use the real kill-args builder in mocks; assert B's state survives
2026-10-13 02:14:49 -04:00
Jinwoo HongandYEONGUK LEE 0bbe7fb238 fix(i18n): correct Korean Settings translations (#28183)
* fix(i18n): correct Korean Settings translations

Consolidates the Korean Settings corrections from the community audit
(#27820) into one change. 514 catalog values are corrected: reversed
meanings (session search privacy notice), literal machine glosses
(Custom as 관습, gutter as 배수구, Open as 열려 있는), the terminal
caret named after the Cursor app, and Latin terminal/agent/repo left
inside Korean prose. Every value is pinned in the ko key overrides so a
catalog repair cannot revert it.

Co-authored-by: YEONGUK LEE <contact@abruption.dev>

* fix(i18n): keep the contributor's wording where it was already correct

Co-authored-by: YEONGUK LEE <contact@abruption.dev>

---------

Co-authored-by: YEONGUK LEE <contact@abruption.dev>
2026-10-13 02:09:02 -04:00
Brennan Benson 5de471a0cf Resume toast: say what happened and name its action (#28068)
* fix(native-chat): name the resume toast's action and keep it on the outcome's row

* fix(native-chat): give the resume toast's action the toaster's own button look
2026-10-12 22:58:23 -07:00
Brennan Benson 8be17ddf3a feat(chat): host writes one saved row per subagent (#27947)
* feat(chat): read one saved row per subagent

Adds the singular `subagent` journal block (one row per child, revised in
place, with a plain-text twin for older clients) and teaches every reader
to draw it: desktop and phone fold a turn's sibling rows into the existing
"Ran N subagents" row, the client and off-page rosters name children from
their own rows, the reopen pass settles a row a dead host left working,
and the phone and worker-transcript wires bound it. No producer writes the
block yet; that switch lands in the follow-up.

* feat(chat): host writes one saved row per subagent

Claude, Codex and Grok now only report child-work evidence. The host writes each
subagent's row from its child record, so the chat row, strip and sidebar read one
lifecycle. Deletes the shared subagent tracker, the per-agent rosters and their
caps. An accepted Stop settles only the run it was sent to, provisionally, until
that run reports its own ending. Work the host store has no room to record keeps
the running-tasks strip and its Stop instead of reading as idle.

* fix(chat): open subagent background flag, separate nested group ids, page window skips subagent rows

- background is an open boolean so a later build's value never fails a chat load
- a helper's own helpers fold under a groupId of their own, so the phone opens them apart
- each subagent's own row rides along with the page window instead of counting as conversation

* fix(chat): subagent row writer reads the live turn once, stop acks survive a failed batch

- the row writer reads the journal's live turn at most once per change and only for a
  subagent with no saved, queued or in-flight row; a revision keeps its first scope
- a background stop names each accepted task as its answer lands, so a stop-all batch that
  times out or fails part way still ends the runs it was answered for (Claude and Grok)
- the strip's untargeted Stop rule lives in the shared view (offersStopAll); both strips
  inline it again; incomplete tracking no longer holds commands or rewind
- clients admit a frame whose only change is incomplete tracking
- drop the Claude stop's dead publish; the real-CLI stop test asserts the run's own ending
- list the writer test with the real SQLite tests

* fix(chat): a late Grok report never revives an ended helper; Codex keeps announced helpers

- Grok: once a helper's run ended, a later progress or repeated spawn sends no live evidence,
  so its record and saved row stay ended after the next send retires the record; the test rig
  now retires finished records on each turn open, as the host does
- Codex: past the bound, only a child that ran and ended is evicted, so an announced helper
  that has not started keeps its record, row, Stop and parent
- cut: Codex parentTurnId (written, never read), the Claude identities' turn-end forwarding
  (call the corrections directly), the duplicate Grok outcome map
- update stale comments that described the deleted producers

* test(chat): Claude frames to saved row, kept Codex path and usage rules, agent ingestion exemption

- a captured background Claude helper keeps its saved row working past its spawn result and
  the parent turn's end, through the real adapter, host and child records, until its own end
- restore focused tests for the Codex root path, label trim and placeholder, usage before
  announcement and parent usage
- the per-session live limit test uses tasks: subagents are exempt from it

* fix(chat): incomplete tracking follows the refused children, and the strip's Stop reaches them

- the host remembers which children the store refused for room, by handle; each leaves when a
  later report of it is admitted or it reports its own end, so incomplete clears on a truthful
  signal; the provider session's end still clears all, and past 512 a flag holds until then
- a parent row the store refused counts every live child it reports as refused
- with work no record lists, an untargeted strip Stop while no turn runs ends the agent through
  the idle sweep's stop; under a running turn it leaves the agent alone
- the background Stop moves into its own module

* fix(chat): the incomplete Stop is offered for every agent and decides inside its step

- the strip offers the untargeted Stop for work no row lists whenever no turn runs, whatever
  untargeted stop the provider has (ACP agents, Codex before it proves terminal stops), since the
  host reaches it by ending the agent; never under a running turn
- the Stop reads whether work is unlisted and whether a turn runs inside its own step, after the
  listed stops land, so a wake-up turn opened meanwhile is never ended
- Codex executions past the bound evict the oldest ended child, else the oldest never started
- test the writer's unnamed-subagent label; qualify the writer's retry comment

* refactor(chat): the subagent row writer joins the views with their records

Drops the shared host-only row projection and its hook-server read: the writer's wiring reads
the views every surface reads and the records they come from, and takes residency and the
ending's basis from the record.

* refactor(chat): Grok subagent evidence reads its ending through the one outcome map

* refactor(chat): a subagent's group entry as one object

* fix(chat): a background Stop with nothing listed asks the agent for no task stops

So an agent with no stop route (an ACP agent the probe found none for) never refuses before the
Stop ends the work no row lists. Tests: the unlisted case is untargeted and reads unlisted work
inside the step; the repeat-press rig lists the task its Stops reach; reflow the writer header.

* test(chat): a nested helper's saved parent; describe how incomplete tracking clears

* fix(chat): a closed session claims no Stop; a helper's row follows its own running work

- a session close Orca proves is loss of contact, not a Stop the CLI accepted: Claude no longer
  ends what still ran as stopped, so the host settles it unknown and its row reads unverifiable,
  as before this stack; stop-acknowledged comes only from a Stop the CLI accepted
- a helper that finished while work it started still runs is written working, by the strip's
  own rule; once that work ends, or the session ends, it reads its own ending again
- the CLI starting a completed helper again under the same spawn call is the helper waking when
  its work ends: the decoder reports it as a restart, so its one row revises in place and ends
  with its real duration; after any other ending such a start is still read as late
- tests from a captured 2.1.280 frame order; item 4 Stops pin their stop-acknowledged basis

* refactor(chat): drop the unused Stop basis on a child's own ending

Only the acknowledged-Stop edge writes that basis now; reflow the decoder header.

* fix(chat): an older history page keeps a turn's subagent ordinals and row

A page now reaches back to the first sibling of each subagent group it holds part of, at most one more page of conversation, so loading the older page no longer renumbers same-named helpers or moves (and closes) their folded row.
2026-10-12 22:53:42 -07:00
Brennan Benson 1182168681 feat(crash-reporting): name the code behind a renderer out-of-memory crash (#27814)
* feat(crash-reporting): name the allocation sites behind a renderer heap high-water mark (STA-3565)

* fix(crash-reporting): refresh the allocation profile on each memory sample so a fast leak still leaves one

* style: oxfmt

* fix(crash-reporting): keep every high-water mark when both surfaces also hold a profile

* fix(crash-reporting): name only bundle and builtin frames, sample only while the heap is near full

Frames outside the window's own bundle become fixed labels, so user-derived names, paths and
content cannot reach a report. Sampling is re-derived from every memory reading (starts at 60%,
stops below 50%, on reload or renderer loss), waits for the sampler to start before reading,
leaves no listeners behind, and gives way to the typing-lag capture script.

* fix(crash-reporting): read the profile on main's own 30 s clock; samples carry their window

A refresh asked for while sampling was still starting is no longer lost, and a hidden window's
throttled samples no longer decide how stale the last profile is. Periodic memory crumbs now carry
rendererSurface, so a restarted session keeps replacing the same retained profile.

* Preserve occupied debuggers during lag capture
2026-10-12 22:19:17 -07:00
Neil b5c901747b [CI] Reuse test compilation and reduce avoidable test waits (#28169)
* [CI] Reuse test compilation and reduce avoidable test waits

* [CI] Keep startup observation polls from advancing deadlines
2026-10-12 22:19:00 -07:00
Jinwoo Hong ef890ae2bd refactor(automations): main creates an automation workspace's setup and default tabs (#28130)
* refactor(automations): main provisions an automation workspace's setup and default tabs

An automation's create handed setup and default tabs back to the window, which
launched them through its own background-terminal code. Main now provisions
them through the same provisionWorktreeTerminals the CLI create uses, and
returns neither, so setup runs once and the window's copy is deleted.

* fix(worktree): translate the Setup tab title wherever it is created

Main had no copy of the automation path's translated title, and the window and
CLI creates used the English literal. All three now read worktree.setupTabTitle
through the existing main and renderer translators. The create path's runtime is
required, since every production caller passes it.

* fix(worktree): keep the Setup title in English; runtime code cannot load main's translator

main-i18n imports Electron, which orcad and the runtime must not load. The
title stays the English literal the window and CLI creates already use.

* refactor(worktree): drop the runtime check the required runtime made dead
2026-10-13 01:07:23 -04:00
Neil e713bcbb82 fix(daemon): keep the relocated daemon host's conpty.dll under the Windows path limit (#28156) 2026-10-12 21:15:40 -07:00
NeilandClaude 0a1f99fb3a fix(wsl): recheck watcher-reported paths with full stat on Windows Git drive checkouts (#27817)
* fix(wsl): recheck watcher-reported paths with full stat on Windows Git drive checkouts

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

* fix(wsl): keep rechecked-dirty drive-checkout paths through watcher overflow and restarts

A path the default-stat recheck proved dirty stays a suspect until a recheck proves it clean,
so an overflow, interruption, watcher reinstall, or closed watcher no longer drops it back to
minimal-only status, where it would silently read as clean.

* fix(wsl): stage and discard rechecked drive-checkout paths with full stat

Status rechecks watcher-reported paths with core.checkStat=default, but add and
restore ran with the injected minimal stat, so a same-size rewrite that kept its
mtime showed as modified yet Stage and Discard silently did nothing. Path writes
now redo the tracked suspects inside their scope with default stat; everything
else stays minimal.

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-10-12 20:19:33 -07:00
Brennan Benson 7e0d6dc2f6 fix(diff): stop closed diff editors from leaking through Monaco's gutter menu (STA-3565) (#27793) 2026-10-12 20:01:48 -07:00
Jinwoo Hong 5a77f068b6 Link the Android APK through a stable URL and bump it on release (#28143)
* Link the Android APK through a stable URL and bump it on release

READMEs, the docs site, and the desktop app now link
https://www.onorca.dev/download/android, which the marketing site
redirects to the current APK, so none of them need a per-release edit.
A published Android release opens a PR on the marketing site that bumps
its one ANDROID_RELEASE_VERSION constant; a failed bump is reported in
the job summary and does not fail the release.

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb

* Skip the APK download bump for OTA-shell test builds

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb

* Bump the APK download for every published Android release

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
2026-10-12 23:00:27 -04:00
Jinwoo Hong 4d1001fba6 fix(terminal): show a host-created setup split in the window that holds its tab (#28105)
* fix(terminal): show a host-created setup split in the window that holds its tab (STA-9417)

Main only revealed a split of a saved tab when its copy of the window's graph
listed the source pane. That copy lags the tab the create reveal just added, so
the window never drew the setup split and its first-activation sweep left the
setup terminal with no pane or minted a second tab for it. Reveal unless the
window's graph shows the source pane running a different terminal, and reject
a split into an unknown tab before the window switches workspace.

* refactor(terminal): return only the split-source fields a caller reads

* test(terminal): explain the internals cast in the projected split-source test
2026-10-12 22:26:06 -04:00
Jinwoo Hong 0e4207400b fix(terminal): keep a named terminal's title across an orca serve restart (#28126)
* fix(terminal): keep a host-made terminal's title across a serve restart

A title given to terminal.create was kept only on the live process record, so
after an orca serve restart the tab read "Terminal N" and terminal.list
listed no title. The runtime now writes it as the tab's saved name when it
admits the pane, and terminal.list falls back to that saved name for a
terminal that has no title of its own yet, as a window does.

* test(e2e): record the kept terminal titles in the layout-compat baselines

* fix(terminal): save a created terminal's name only when the window would show it

A background create (a worker the window never reveals) keeps its live title:
a window reads a saved name on hydration and shows it over the agent's own
title. Also corrects two comments and drops a key count.
2026-10-12 22:25:55 -04:00
Brennan Benson 09ae6d225c fix(tab-bar): native chat tabs show their recorded provider, not one guessed from the title (#28082)
A native chat was rendered as a fake terminal tab, so its conversation name
went through terminal agent detection and could replace the chat's provider
once a turn finished (a Codex chat named after Hermes showed Hermes).
SortableTab now takes the agent as a prop: terminal tabs resolve it through
TerminalSortableTab (useTabAgent, unchanged), and native chat tabs pass the
provider recorded on the tab. The chat's session id and provider reach the
session-history menu together instead of riding on a terminal launch hint.

STA-10347
2026-10-12 19:10:01 -07:00
Neil c56e6718b4 ci(windows): build the agent hook before packaging in installer e2e workflows (#28137) 2026-10-12 19:04:02 -07:00
Neil 44fabbda52 fix(runtime): stop a project's terminals before the host forgets the project (#28024)
* fix(runtime): stop a project's terminals before the host forgets the project

Removing a project on a runtime host (an SSH-managed or paired server) sent repo.rm first; the
host resolves terminal.stop through the project's workspace rows, which repo.rm deletes, so the
stop failed and the shells kept running on the host. Terminals stop first now. The removal toast
also drops the IPC wrapper from its message.

* fix(runtime): only stop host terminals when the host will accept the project removal

Probe with repo.show (same resolver as repo.rm) so a removal the host rejects, such as an
ambiguous project id, no longer kills the shells first. Also poll through terminal_not_writable
in the paired-client e2e.

* test(e2e): read the host shell pid through sh so non-POSIX login shells work
2026-10-12 18:43:24 -07:00
Neil a7e24766b7 fix(wsl): run WSL git with Windows Git line-ending settings on checkouts Windows Git wrote (#27778)
* fix(wsl): run WSL git with Windows Git settings on Windows-authored drive checkouts

* fix(wsl): detect Windows Git checkouts from host autocrlf plus a whole-tree sample

* fix(wsl): keep WSL-created worktrees on Linux Git defaults and never commit CRLF on an unproven checkout

- worktree add / clone write a new checkout whose .git points at /mnt/..., which Windows Git
  cannot open; injecting autocrlf=true there produced CRLF files later read unflagged.
- Writes on a host-autocrlf=true checkout whose sample is unproven run with autocrlf=input,
  so staging normalizes CRLF without ever writing CRLF to disk.
- Writes wait for a stale host answer and re-check checkout identity after joining a sample.

* fix(wsl): compare a joined sample with the checkout identity read after it settles
2026-10-12 18:40:06 -07:00