* 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
* 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.
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.
* Show Codex model capacity failures in agent status
* Reduce Codex capacity detection work on terminal output
* Preserve Codex capacity notices across partial terminal frames
* 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#26027Fixes#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>
* 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.
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>
* 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.
* 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>
* 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.
- 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>
* 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>
* 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>
* 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>
* 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
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.
* 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>
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>
* 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>
* 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
* 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
* 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.
* 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.
* 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>
* 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
* 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>
* 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
* 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.
* 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
* 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
* 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>
* 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
* 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.
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
* 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
* 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
* fix(mobile): load markdown tabs from any desktop split group and center load-error Retry
The desktop answered markdown.readTab only for tabs in its focused split
group, so a phone opening a markdown tab from another group got
tab_not_found and showed "Couldn't load markdown". Resolve the tab across
all of the worktree's groups.
Load-error states now share one component that centers the message and its
Retry button; the markdown reader's button was pinned to the left.
Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
* test(renderer): mobile markdown read picks the requested tab across groups
Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
* refactor(renderer): resolve mobile markdown tab to one file id
Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
* perf(runtime): let worktree.ps pollers opt out of per-chunk activity churn
worktree.ps carries `preview` and an exact `lastOutputAt` on every row, both
of which move on each PTY chunk, so the content-addressed snapshot id changed
on every poll while any agent printed and the whole catalog was resent.
Add an optional `coarseActivity: true` param. When set, the host drops
`preview` and floors `lastOutputAt` to 30 s before hashing, so the id holds
while only chunk-level activity moves. Callers without the param get the
byte-identical response they get today; older hosts strip the unknown key.
Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
* perf(mobile): stop re-sending the worktree list on every terminal chunk
The host-screen snapshot poll now asks worktree.ps for `coarseActivity`, so
the catalog's snapshot id survives terminal output and the 3 s poll gets a
tiny `unchanged` reply instead of the whole catalog. `preview` is optional on
mobile rows because a coarse host leaves it out; old hosts ignore the param
and keep sending it.
Re-records the three worktree.catalog-snapshot goldens; the only semantic
change is the new request param.
Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
* perf(runtime): coarsen worktree.ps activity for every snapshot poller
Drop the `coarseActivity` param: the only caller that sends
`afterSnapshotId` is the mobile snapshot poll, which never renders
`preview` and uses `lastOutputAt` only to rank rows. Snapshot polls now
always get a blank `preview` and a 30 s-floored `lastOutputAt`, so
already-shipped apps stop re-downloading the catalog on every terminal
chunk with only a desktop update. Callers without `afterSnapshotId`
still get the byte-identical response.
Reverts the mobile param, its scenario steps and the three re-recorded
goldens, and removes the unread `preview` field from mobile row types.
Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
* fix(runtime): rank coarsened worktree.ps rows so in-bucket swaps keep the snapshot id
Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
* test: drop preview from row fixtures and keep the runtime cast on one line
Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
* fix(orcad): identify instances by kernel start ticks, not drifting wall-clock start time
On Linux the start time is derived from /proc/stat btime, which drifts (WSL2, NTP),
so a live orcad was read as a reused PID and its managed stop reported exited
without ever writing the request. The lock and stop request now carry optional
linuxStartTicks + bootId; readers prefer them, and a wall-clock mismatch on Linux
for records without them is unverifiable, never proof of exit.
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(orcad): match stop receipts without the incarnation an older client drops
A request recovered by an older client loses linuxStartTicks/bootId, which made
the newer host reject its own receipts and decision records as mismatched. Also
exercise the incarnation reader against a real Linux /proc.
---------
Co-authored-by: Claude <noreply@anthropic.com>