* 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>
Overlap mobile typechecks with tests and avoid repeated filesystem probes and unused device substitutes in recording replays. Keep state isolated between loaders, give exhaustive snapshot cases an explicit execution deadline, and retire obsolete locale keys that blocked repository validation.
* perf(mobile): skip re-rendering unchanged worktree rows
Rows reach the host list freshly parsed and copied on every changed
worktree.ps poll, so the row memo never skipped and every mounted row
re-rendered each poll. The row memo now compares the item by value with
the catalog's areWorktreesEqual, extended to every Worktree and agent row
field, and every other prop by identity.
Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
* refactor(mobile): compare worktree rows by structural equality
The hand-written Worktree field list needed a completeness guard that no
compiler checks, since mobile test files sit outside tsc. A generic
structural equality covers every field by construction, so a new field
can never be skipped and freeze a row. Rows memo on it directly: the item
and repo icon compare by value, callbacks and `now` by identity.
Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
* Remove repeated daemon smoke builds and idle compiler slots
* Test compiler scheduling through its process interface
* Keep preview component below the existing line limit with a type-only split
HtmlDocPreview.tsx reached 403 lines on main (#27608 + #27875), over the
400-line limit for .tsx, so lint and the pre-commit hook fail on any
branch that merges main. The focus handoff effect moves unchanged into
use-doc-preview-guest-focus.ts.
Co-authored-by: Orca Worker <orca-worker@localhost>
* fix(browser): annotation card and outline follow the selected element
The grab overlay keeps re-measuring the picked element after the click
(scroll in any box, resize, element size changes), and the isolated
viewport bridge reports that live box so the pending card follows it,
including page zoom. The card's side pick uses its measured height.
* fix(browser): bind annotation card to selected element
* fix(browser): keep document annotation card on selection
* fix(ci): point the main sync-spawn ratchet at the shared ratchet library (#28054)
#27756's script imported baseline helpers from check-focus-read-ratchet.mjs
after #27788 had moved them into ratchet-baseline.mjs, so it crashed on start
and failed every PR that merges main. Use the library's helpers, and record the
two daemon-process launches from #27769 that the broken check never saw.
Co-authored-by: Orca Worker <orca-worker@localhost>
* fix(browser): follow selected element outer-box size
* refactor(chat): give the start-refusal resumable table its own module (#28085)
#27946 and #26684 together put agent-session-failure-words.ts at 304 lines,
over max-lines, which fails lint and the pre-commit hook on every merge of
main. The refusal-code table is standalone with one reader, so it moves out.
Co-authored-by: Orca Worker <orca-worker@localhost>
---------
Co-authored-by: OrcaWin <alpha-eng@stably.ai>
Co-authored-by: Orca Worker <orca-worker@localhost>
* fix(terminal): keep the tab when a reattach finds its process gone
Co-Authored-By: Claude <noreply@anthropic.com>
* test(e2e): read the paired retention heap after two collections
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(terminal): clear the absent owner through the replaced-pane binding writer
Co-Authored-By: Claude <noreply@anthropic.com>
* test(e2e): fail the paired retention check when the heap cannot be read
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(terminal): refuse the respawn without a Store; tighten the binding fixture and stop docs
Co-Authored-By: Claude <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
* perf(claude): run the Windows Claude hook as a native orca-hook.exe
Each Claude hook event on Windows started cmd.exe (plus the user's cmd
AutoRun) and then curl.exe on top of the agent's own shell. A small signed
Rust binary now does the same synchronous POST in one process, under Git
Bash, PowerShell and direct CreateProcess alike.
- native/windows-agent-hook: std-only crate, same contract as the .cmd hook
(prints {}, env guards before stdin, endpoint re-read, 0.5s/1.5s caps,
waits for the listener reply, always exit 0). Its installed file name
selects the agent profile, so the registration stays a bare path.
- Packaged as resources/bin/orca-hook.exe (SignPath signs it with the other
inner PEs); afterPack fails on a missing or wrong-machine binary.
- Claude install copies it verbatim to ~/.orca/agent-hooks/claude-hook.exe
(temp + rename, running copy moved aside) and registers it; it falls back
to the .cmd hook when the bundle is missing, unsigned or cannot be copied.
Only Claude opts in, via windowsNativeHookExecutable.
- The managed-command matcher sweeps .exe entries alongside .cmd/.ps1/.sh.
- The CLI launcher and hook builds share windows-rust-executable-build.mjs,
which now also checks the PE machine.
Refs #20913
* fix(build): publish native Windows binaries atomically so parallel builds cannot collide
The Windows CI lane runs every orca-hook.exe suite in one vitest
invocation, each building through build-windows-agent-hook.mjs. The build
copied straight onto native/windows-agent-hook/.build/orca-hook.exe, and
CopyFile opens its destination unshared, so a second build (or a scanner
still holding the fresh image) failed with EBUSY.
- windows-rust-executable-build.mjs skips the publish when the output
already has the built bytes, otherwise stages a per-process temp file and
renames it over the output, retrying briefly while another handle holds it.
- The test fixture hands each suite a private copy of the build output, so
no suite reads or starts the shared file while another build replaces it.
Refs #20913
* refactor(claude): simplify the native Windows hook to one Claude binary
Review follow-up on the native hook (#20913): the same behaviour with less
machinery.
- The binary is built, shipped and installed as claude-hook.exe, so the
installed name matches its PE OriginalFilename. It is Claude-only, so the
file-name profile table and the per-agent opt-in flag are gone; the
registration is gated by the existing Claude-only usesWindowsEntry check.
- Install and refresh reuse writeManagedScript / refreshManagedScriptIfPresent
(now accepting bytes): temp file, rename, skip when identical. A copy whose
hook is still running cannot be replaced; install then registers the .cmd
hook and refresh keeps the old build until a later start, instead of the
rename-aside and *.retired sweep.
- The install-time Authenticode-presence check is dropped. Release CI now
requires resources\bin\claude-hook.exe to be signed (release-cut and the
signing rehearsal), next to resources\bin\orca.exe.
- getStatus counts either Windows form through findManagedEventHookForms in
hook-settings.ts, replacing managed-event-hook-status.ts.
- Rust: plain Claude constants, one endpoint-file lookup (last assignment
wins, as under `call`), one request write and a deadline-bounded drain.
Guard order and form encoding are unchanged.
- Build: PR CI builds the hook once before the Windows tests, so the suites
only read it; the launcher publish goes back to copyFileSync and the retry
loop and its test are gone. The PE-machine check lives only in afterPack.
Cargo.lock joins both crates' build fingerprints, and the launcher's alias
exports are removed.
Refs #20913
* test(ci): run the managed hook stdin-lifecycle suite in the Windows lane
Its claude-hook.exe case (and the other Windows-only cases) are gated per
test, so the lane-registration guard did not require the file and it ran
only on Linux, where those cases skip. Register it in both Windows lists.
Refs #20913
* feat(agent-hooks): ship the native hook as orca-agent-hook.exe <agent>
The native Windows hook (#20913) becomes one general binary that stays
Claude-only in behaviour.
- Named orca-agent-hook.exe everywhere: build output, PE OriginalFilename
and InternalName, resources\bin, the afterPack check, the installed copy
in ~/.orca/agent-hooks, and the release signed-target lists.
- It takes the agent as its first argument and Claude registers
`<path> claude`. The CLAUDE_JOB_DIR exit and the Grok/Devin drain belong
to that agent; an unknown or missing agent answers {} without reading
stdin. The managed-command matcher owns `orca-agent-hook.exe claude` and
still owns the old claude-hook.exe entry, so installs migrate; install
deletes a leftover claude-hook.exe when nothing runs it.
- A copy still running from the previous build no longer forces the .cmd
fallback. Identical bytes are not rewritten; otherwise the temp file is
renamed over the target, and when Windows refuses because the image is
mapped, the running copy is renamed to orca-agent-hook.exe.old first.
Install deletes a stale .old; only when that copy is also still running
does the install fall back to .cmd. writeManagedScript and
refreshManagedScriptIfPresent take the aside path, sharing one helper.
Refs #20913
* fix(agent-hooks): roll back a failed aside rename and keep an installed hook exe
Design-review follow-up on the native hook (#20913).
- If the running copy was moved to orca-agent-hook.exe.old but the new
file then could not take its name, the copy is renamed back before the
error is rethrown, so settings.json never points at a missing exe. The
rename helper now owns the `.old` name, next to the comment that
explains it; a mocked-fs unit test covers both the sync and async paths.
- A failed write, or a build with no bundled exe, keeps registering a copy
that is already installed (every build knows `claude`), so a dev checkout
and an installed build no longer flip settings.json between forms.
- The claude-hook.exe migration is gone: no release ever shipped that
name, so the legacy sweep, the `.exe` matcher needle and their test are
removed.
Refs #20913
* fix(agent-hooks): derive the native hook's agent with win32 path rules
getWindowsNativeHookAgent took the host's basename of the managed script
path, so on a POSIX host (Linux CI) a Windows path was not split and the
registered command became `orca-agent-hook.exe C:\...\claude`. It now takes
the path and applies path.win32.basename itself. The install destination
and the `.old` aside stay on host paths: they name files the host opens.
Refs #20913
---------
Co-authored-by: Orca Worker <orca-worker@localhost>