* Add board layout support for GitHub project views
Board-layout views render as a kanban. Columns come from the view's
verticalGroupByFields (the host retries without the selection on older
GHES schemas and the renderer falls back to the Status field), with one
column per single-select option in option order — empty ones included —
plus a trailing no-value column whose drop clears the field. Card drops
reuse the table's field mutation path, committed from a document-level
capture listener because the preload's native-drop bridge stops drop
events before React's root ever sees them.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(github-projects): harden board capability probe and drop lifecycle
Review follow-up. The verticalGroupByFields capability probe now matches
parsed GraphQL error messages instead of substring-scanning the whole
response body — partial-error responses echo the field name as a data key
on healthy schemas, so one SAML/FORBIDDEN partial error could permanently
degrade github.com boards for the session. Covered by new
project-view-config tests per the capability-cache testing contract.
Also: only single-select/iteration vertical fields shape columns (a
drifted field kind no longer yields a clear-on-drop no-value column),
optimistic patches resolve the column field through the board/group
config, drag cleanup moved to a document-level dragend listener, the
column dot follows dark mode via the chip CSS variables, the supported-
layout allowlist is a single shared predicate, and the board's edit
handler is stable so its drop listener stops re-registering per render.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(github-projects): serialize board edits and verify rendered drops
* fix(github-projects): preserve refresh baselines and order edits across views
* fix(github-projects): accept source settings projection in cache scope
* fix(github-projects): distinguish view switches from refreshed field baselines
* test(github-projects): type board IPC recordings and verify clear request
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Neil <neil@stably.ai>
A custom launch command in settings made structured native chat unavailable:
the renderer route, the host launch-mode decision, and the host's
create-support all treated it as terminal-only, so a user with the chat
default on silently got a terminal. The command names the CLI binary and now
applies to terminal launches only; native chat ignores it.
A start directory outside the workspace still forces a terminal. The internal
route field and blocker are renamed to that concept; the wire reason
`tui_launch_command` is kept and its receipt text now names the start
directory.
* Match Quick Open queries across path terms and identifier separators
* Honor ignored-file and symlink preferences on file inventory hosts
* Open pasted file locations and remember successful workspace file visits
* Verify bounded directory listing preferences across execution routes
* Preserve symlink preferences on legacy directory fallback
* Preserve bounded Quick Open terms and separator alternatives
* Revoke stale Quick Open selections and preserve literal file locations
* Validate recent Quick Open candidates independently on their host
* Use complete old-host inventory fixtures for Quick Open compatibility
* Keep Quick Open validation current across palette and workspace changes
* Route recent candidates through relay file listing dispatch
* Record Quick Open hook renders as test snapshots
* Localize Quick Open eligibility errors and search aliases
* Validate file inventory with bundled search and runtime preferences
* test: use directory junction fixtures on Windows
* fix: negotiate Quick Open policies with nested SSH hosts
* fix: refresh cached linked folders after target changes
* Retry stale expanded links after reads settle and targets recover
* Scope stale refresh request lifetime to the visible workspace
* test: seed linked-folder fixture before watcher startup
* Keep Quick Open responsive and compatible with older hosts
* Exercise directory discovery with unprivileged Windows junctions
* Settle completed worker assignments and bound recovery retries
* Reset queued explicit recovery before starting its retry budget
* Use current provider identity in the inherited journal fixture
* Keep late worker recovery automatic without repeating persistence work
* Repair inherited validation fixtures after updating main
* Keep the released parser available to compatibility tests
* Limit recovery queries and background process inspection to pending work
* Verify historical repair safety and live SSH process retention
* Preserve the same provider import as main in the merge result
* Reduce historical repair and duplicate workspace recovery work
* Align startup prompt fixture with upstream Windows coverage
* Keep healthy worker repair indexed and read-only
* Align startup prompt tests with current main
* Keep pending terminal releases progressing during recovery retries
* test(remote-runtime): reproduce dead input after a paired host relaunch
A relaunched desktop host accepts RPC before its renderer publishes a window
graph. During that gap session.tabs.list answers with an unpublished empty
graph (publicationEpoch "none", snapshotVersion 0, tabs []). The client's
reconnect inventory treats that as removal and retires the pane
(retireRemoteTerminalId(-1)): the pane goes to "ended", the reconnect overlay
disappears, and keystrokes never reach the surviving daemon PTY.
Both tests are red on main by design; they are the repro for the fix.
- e2e: holds the relaunched host's runtime:syncWindowGraph so the client's
reconnect deterministically meets the unpublished host (4/4 red).
- unit: transport-level repro of the same retirement (red in ~2s).
- restart helper gains a beforeFirstWindow hook; LaunchOptions moves to its
own module to stay under the max-lines budget.
* fix(remote-runtime): a paired terminal survives its host app relaunching
After the host app quit and relaunched (daemon still running), a paired
client's terminal cleared its reconnect overlay and then ignored all input.
The relaunched host answers session.tabs.list before its renderer publishes,
with a synthesized empty frame. A paired client sees that frame through its
navigation projection as epoch "none:client-navigation", which the shared
"does this frame answer for the worktree" check did not recognise, so the
pane read it as removal and retired itself.
- hostSnapshotAffirmsWorktreeContents treats the client-projected placeholder
as no answer at any version (the projection adds its navigation revision).
- The reconnect inventory keeps polling on such a frame instead of retiring;
a published frame lacking the surface still retires the pane.
- Pushed frames that affirm nothing no longer report the surface absent.
- When the bounded inventory wait ends without evidence, the first published
snapshot carrying the same handle now reattaches the pane, rather than
waiting out the ~3 minute auto-recovery deadline.
Client-only: the host already labels the frame, old hosts send the same
frame, and the wire is unchanged. The e2e helper drops its type assertions.
* refactor(remote-runtime): simplify the host-relaunch reconnect fix
- One epoch rule: the placeholder epoch, bare or with the shared
client-navigation suffix, is no answer. Hosts only ever send it at
version 0, so the version check is dropped.
- Unpublished frames are dropped where pushed snapshots enter, instead of
a nullable per-subscriber update.
- A published same-handle snapshot fires the parked retry through
retryNow(), which now also fires a retry parked while still
'recovering' (nothing is in flight then). This replaces state-reading
wiring in the listener and lets online/resume fire it too.
- The Windows e2e runs the fixture through PowerShell's call operator.
* test(remote-runtime): prove the e2e hold beat the relaunched host's first publish
The spec now asks the held host for its tab list and requires the
unpublished placeholder before releasing, so a hold installed too late
fails instead of passing against unfixed code. The helper keeps its gate
in a typed global instead of Reflect lookups.
* fix(remote-runtime): keep a fenced handle's parked retry revivable
A retry parked for a handle that needs a replacement was consumed by an
early external trigger and then refused by the epoch's snapshot-wait
guard, leaving nothing to revive the pane. An external trigger now takes
over that snapshot wait as a fresh attempt, and a republished fenced
handle no longer fires the retry. Also refreshes comments that still
paired the placeholder epoch with version 0.
* fix(remote-runtime): a host answers an empty worktree for real once it has published
The host sent its unpublished placeholder for any worktree it had no
entry for, including one its window had never opened, long after
startup. With the placeholder now read as "ask me later", a paired client
opening such a worktree never got its first terminal.
The host now sends the placeholder only until its graph first publishes.
Afterwards a worktree with no tabs gets a real empty answer, and a client
that was told "ask me later" during startup is sent that answer once on
publication. Against an older host the client withholds the automatic
first terminal for such a worktree, which the user can still create.
A host push now fires only a parked retry, never cutting a scheduled
backoff short.
* chore(reliability-gates): reference the empty-worktree tests in gate commands
* test(remote-runtime): find the never-opened worktree by repo id on every platform
* test(cross-version): stand in for packages a release declares but main dropped
The newest release (v1.4.221) imports @streamparser/json, which main removed.
The harness runs release source against the current install, and the release's
RPC dispatcher imports its whole method table, so every wire suite failed at
import time. Extraction now gives each package the release declares and the
current tree no longer declares a stand-in in the checkout's own node_modules
that loads but throws, naming the package, on any use.
Co-Authored-By: Claude <noreply@anthropic.com>
* test(cross-version): decide stand-ins by package resolution, not manifest diff
A package gets a stand-in only when release source imports it at runtime,
the current tree does not declare it, and it does not resolve from the
checkout. A package still reachable from the checkout keeps the real install,
and one the current tree declares but cannot resolve stays a loud failure.
Type-only imports, comment prose and scripts in strings are ignored.
Co-Authored-By: Claude <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
* refactor(native-chat): keep the provider resume handle opaque to shared code
Shared structured-chat code parsed each provider's resume handle: Claude's
session id and branch leaf, Codex's thread id, through a 'claude' | 'codex'
union every new agent had to widen. The in-memory handle is now
{ transport, agent, nativeId, providerData? }: shared readers use nativeId,
lease and handle-chain checks compare transport and agent, and only the
Claude adapter reads its leaf (providerData).
Stored and wire forms are unchanged for Claude and Codex. One encoding
module writes their typed shapes and decodes both those and the neutral
shape a new transport uses, which an older build refuses as unreadable
rather than reading as Codex. Key and root strings, which fork seeds,
superseded creations and resume offers persist, stay byte-identical.
The journal's own handle type becomes the journal-row and attach-wire
encoding of the same handle, and the journal identity carries the
neutral handle (null before the provider proves one).
No user-visible change.
* fix(native-chat): derive journal-row provider handles from the journal identity
The journal row converter now takes the identity every caller already holds,
so a row's handle has one obvious constructor. Tests that wrote the in-memory
handle straight into journal rows now build it through that converter, and the
processless Claude fixture names a not-yet-proved handle as null.
* fix(native-chat): refuse a stored provider handle written in both forms
A typed Claude or Codex handle that also carries the neutral form's
transport, agent, native id or provider data named two identities; it
was read as Claude or Codex and the next write dropped the other one.
Such a row now stays unreadable and is set aside untouched.
* refactor(native-chat): route structured agents through registered definitions
The structured-chat shared layer named Claude and Codex in a closed router
type, in option reads, in frame classification, and in renderer label and
validation branches, so adding an agent meant editing shared types. Each
agent now has a definition (capabilities, resting option rules) owned by its
own module; the router is a registry of definition + adapter pairs built at
runtime setup, and shared code reads the definition instead of the name.
No behavior change for Claude or Codex; no wire or stored shape change.
* refactor(native-chat): name the structured agent list once in host types
The host types, conversation commands, provider-session ownership and the
runtime's create path spelled the structured agent list inline; they now use
the one shared type the record and wire already validate against.
* fix(native-chat): narrow the record before reading its agent's option rules
* refactor(native-chat): make the router's registrations the only agent definition lookup
The at-rest option reads looked definitions up in a second, separately populated map, so a
registered definition could route and declare capabilities while the static map decided which
options a chat at rest accepted. The router now answers `definition(agent)` from its
registrations, the at-rest readers take it through the host's adapter, and the built-in
definitions are listed only where the runtime composes the router.
* test(native-chat): use opaque handle in queued rejection fixture
* test(native-chat): share one Codex journal identity in the integration suite
Main grew the suite to the 800-line limit; the opaque-handle import pushed it
over. The two tests built the same identity inline.
* refactor(agent-session): name the handle's adapter state resumeCursor
Rename the neutral provider handle's providerData to resumeCursor before any
row persists the neutral form: it is an adapter-owned resume position (Claude's
transcript leaf), never identity. Claude/Codex stored and wire bytes are
unchanged; their typed shapes never carried the field.
State the stored-form contract (a handle's field set is closed; later per-link
data goes on the chain link, which every build preserves) and pin it with a
record round-trip test. Document that transport records the id space the
native id was minted in, which can differ from the agent's current transport.
* refactor(agent-session): one required agent registry; declarations admit what they claim
A StructuredAgentRegistry, built once from the {definition, adapter}
registrations, is now a required host dependency and the router routes with
it. The adapter interface loses its optional router-only capabilities?() and
definition?(); every reader (options at rest, thread goal, rewind, the
/compact handover) asks the registry. A live session still narrows rewind
through the adapter, and the host combines declared and narrowed in one
helper. The registry refuses a registration that declares compact, a thread
goal or rewind without the adapter method behind it.
/compact is admitted by the declared capability, so an agent that declares
compact:false gets the commandRefused fact instead of a thrown error.
Also: the cut-turn notice names the agent from the catalog, create-support
builds the account home through agentSessionAccountHome, and the turn
status text older clients read names the session's agent instead of
defaulting to Codex (Claude/Codex text unchanged).
* refactor(agent-session): the router applies the declared rewind itself
The router holds the registry, so its rewindSupport answers the owner's
declared rewind narrowed by the adapter, in one place no reader can bypass.
The host-side combining helper is gone; readers ask the adapter they hold.
* chore(agent-session): the registry is the one lookup; drop the test-only empty capability record
* test(native-chat): build the Stop-opened-turn test's identity and turn context the current way
The test (#25056) landed before the opaque provider handle (#24991), so main still built
the old {kind, threadId} handle; the turn context also needs this branch's agent registry.
* test(ratchet): require src/main/provider-process now that it has landed
* test(native-chat): keep main's opaque-handle import in the Stop-opened-turn test
Main's #25706 and this branch both added the import at different lines; the merge kept both.
* feat(reveal): reveal changed files and terminal file links
Reveal only existed on file explorer rows. Every reveal menu now shares
one action, so a missing file or a remote workspace behaves the same
in all of them.
Fixes#24003
* fix(reveal): address review findings on missing links and SSH files
A hovered hyperlink to a missing file saved "missing" in the cache plain
path links trust, hiding the path once the file existed. A tab holding an
SSH file from outside its workspace still offered reveal.
* fix(reveal): drop reveal from check-details tabs
Their path is a synthetic id, so reveal could only fail.
* fix(reveal): use scoped file access for directory checks
* fix(reveal): retire superseded Spanish menu labels
* test: preserve explicit file access and untranslated-locale contracts
* refactor(terminal): reveal a file link from its click popover, not the right-click menu
The terminal "Reveal in Finder" action now lives in the file-link popover
(the one with "Open file" and "Open with default app"), as a row with no
click shortcut. It goes through the shared reveal, so a folder link (including
a macOS .app or .xcodeproj bundle) is selected in its parent and never opened.
The row is omitted, not disabled, when the file is not on this machine: an
SSH or runtime-owned file, a pane whose shell runs on another host, or while a
remote runtime is focused. Workspace-root links keep their own "Open in
Finder"/"Open folder" row and get no reveal row.
Removes the right-click path: the context-menu item, the hovered-link tracking
and its OSC 8 / path-existence probe refactors, the directory stat (and its
user-named file-access grant, which only served the open-a-folder behaviour),
and the right-click e2e case. Terminal files the PR touched are back to main
apart from the popover row.
Co-authored-by: Kelvin Amoaba <97001695+AmoabaKelvin@users.noreply.github.com>
* test(terminal): type the reveal settings fixture without assertion
* test(startup): account for encoded Windows prompt lines
* test(compatibility): retain the released journal parser dependency
* test(journal): retain one canonical provider-handle import
* fix(terminal): give the popover's Reveal in Finder row the reveal icon
Every other Reveal in Finder item shows the external-link icon; the link
popover draws it for rows that open outside Orca, so mark the row that way.
---------
Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com>
* fix(feedback): shrink oversized screenshots instead of refusing them
Fixes#22776
* fix(feedback): release each shrink canvas and skip the PNG ladder for a JPEG
Two defects in the attach-time shrink ladder:
- Every step allocated a fresh full-size canvas and left it for the garbage
collector, so a 32-megapixel source could keep six of them alive at once.
Past the renderer's canvas memory budget Chromium hands back a blank canvas,
which would upload as a blank screenshot with nothing to notice it. Each
step's canvas is now released once toBlob has answered.
- A JPEG source ran two full-size PNG encodes first. A PNG of decoded JPEG
noise is many times larger than the source it has to undercut, so those
steps could only ever burn time and a large buffer before the first JPEG
attempt. A lossy source now starts at the JPEG steps.
* fix(feedback): keep the image read queue usable after a settle callback throws
The queue tail is what the next batch awaits. A throw in either settle callback
left it rejected, so every later attach short-circuited into "Could not read the
attached images" without reading anything — and the rejection went unhandled.
* feat(feedback): say when an attachment is still being prepared
Shrinking a full-screen capture takes long enough that the gap between picking
the file and its thumbnail appearing reads as a dropped attachment: the only
other signal was the Send button quietly staying disabled. The attachment hint
now reads "Preparing attachments…" while a batch is in flight.
* fix(feedback): report an undecodable oversized image as invalid, not too large
* fix(feedback): detect an animated PNG by walking its chunks, not a 64 KB text scan
* docs(feedback): stop claiming toBlob encodes off the renderer thread
* refactor(feedback): seed the image read queue ref directly
* test(feedback): cover the real decode, downscale and release path of the shrink
* perf(feedback): skip image batches still queued when the dialog unmounts
* fix(feedback): show the preparing hint only once a read outlasts a short delay
* test(feedback): pin the animated-PNG walk on corrupt lengths and a refused image's budget
* fix(feedback): blame the shared budget, not the file size, when a shrinkable image has no room
* fix(feedback): cap each shrunk screenshot at half the attachment budget
A shrink kept the largest step that fit everything left, so the first
full-screen screenshot took a 3.8 MB PNG, the second fell to a barely
readable JPEG and the third was refused. Re-encodes now target at most
2 MB; an image that already fits is still attached untouched. The
"larger than 4.0 MB" refusal now applies whenever the capped target an
empty budget would give was the one that refused it.
* test(feedback): pin the half-budget shrink cap independently of its constant
* fix(feedback): reserve a shrinking screenshot's capped size, not its file size, for the paste gate
A 6 MB screenshot still shrinking reserved 6 MB, more than the whole budget,
so a second paste during the shrink fell through to the textarea even though
it attached. Each pending file now reserves what the reader can commit under
the same fit and shrink-target rules. Also corrects the minimum shrink target
comment to the measured smallest step.
* docs(feedback): correct the canvas-release and shrink-queue test comments; mock toast.info in the dialog test
* Revert "refactor(feedback): seed the image read queue ref directly"
This reverts commit e2272b825e.
* fix(feedback): keep preparing hint compatible with current main
* fix(feedback): preserve mixed paste text and seed read queue on attach
* fix(feedback): preserve screenshot detail and pending paste text
Optimize oversized attachments only as full-resolution lossless PNG; refuse them with existing warnings when that cannot fit. Bound each pending batch independently so earlier optimization slack, rejected files, or removed drafts cannot under-reserve bytes and swallow co-pasted text. Regression tests demonstrate the old mixed-batch text loss.
* test(journal): retain provider handle import across main integration
* test(startup): account for encoded Windows prompt lines
* test(compatibility): retain the released journal parser dependency
---------
Co-authored-by: Neil <neil@stably.ai>
Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com>
Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
* feat(browser): add an eraser to screenshot markup
Click or drag across marks to remove them whole, one undo step per drag.
Fixes#23258
* fix(browser): drop the in-flight markup gesture on undo and redo
Undo mid-erase left marks hidden against the restored drawing, so release removed them and cleared redo.
* fix(browser): settle a markup gesture whose release was lost
A gesture now blocks any new press so a second finger cannot steer it.
If its own release never arrived, that block also swallowed the next
press from the same pointer, so the next drag continued the stale
gesture: a stroke jumped from its old end, and an erase swept from the
old point, removing marks along a line the user never touched.
A pointer cannot press twice without releasing, so a press from the
gesture's own pointer now settles the old gesture as its release would
have, then starts the new one.
* fix(browser): an eraser click takes only the top mark, a drag takes all
A click on overlapping marks now erases only the one drawn last, matching
what a user expects from pointing at a mark. Once the pointer really moves
the press becomes a drag, whose first sweep starts at the press point, so
every mark under it and along the path is erased. A move that reports the
press point again does not turn a click into a drag.
* fix(browser): undo mid-gesture takes back only the gesture
Undo pressed while a stroke or an erase is still held now drops just that
gesture, as the newest step; the committed marks are untouched, so the next
Undo takes back the last of them. Redo and Clear drop the gesture, then apply
as before. With the gesture gone, the rest of that drag neither draws nor
erases until the next press. Undo is enabled while a gesture is in flight,
since it now has something to take back.
* fix(browser): keep an eraser tap a click within a 4px slop
A touch or pen tap often reports a move a fraction of a pixel from the
press point. The eraser treated any move to a different point as a
drag, so such a tap erased every mark under the finger instead of only
the top one. The erase gesture now records its press point and a
pressed/dragging phase: it stays a click until the pointer travels 4 CSS
px, then sweeps from the press point as a drag.
* fix(browser): undo over an empty erase undoes the last mark
Pressing Undo while an eraser was held over empty space only dropped
the erase, which had hidden nothing, so Undo looked dead and the Undo
button was enabled for a step with no visible effect. Undo now treats
a held gesture as a step only when releasing it would change the
markup (a stroke being drawn, or an erase that hides at least one
mark). Otherwise Undo takes back the last committed mark and ends the
erase; with nothing to undo it leaves the erase alone. The Undo button
is enabled exactly when pressing it would change something.
* fix(browser): redo with nothing to redo keeps a held markup gesture
The Redo button is disabled when there is nothing to redo, but the
Cmd/Ctrl+Shift+Z shortcut is not. Pressed while a stroke or erase was
held down, it replaced the document with itself and dropped the
gesture, so the stroke or erase silently stopped. Redo now leaves the
state untouched when there is nothing to redo. Clear still always
drops the gesture.
* fix(browser): discard a markup gesture whose pointer was cancelled
pointercancel was wired to the release handler, so when the system took
a touch or pen pointer away mid-gesture (an OS gesture or palm
rejection), the stroke was saved and an erase was applied as if the
user had lifted deliberately. pointercancel now discards the in-flight
gesture owned by that pointer: the stroke is not saved and the marks an
erase was hiding come back, with no undo step. Lost pointer capture and
a press that reveals a missed release still commit, since both stand in
for a release that did happen.
* test(browser): pin that losing the pointer commits a markup gesture
The canvas commits a stroke or erase when it loses pointer capture
without a release, so a gesture can never be left open. No test covered
that wiring: removing it, or routing it to the cancel handler, left every
test green. The new test ends a stroke with only the lost capture and
checks that Undo then leaves it to redo, which only a committed stroke
does.
* fix(browser): do not save a markup shape pressed without dragging
A Rectangle, Ellipse or Arrow press released without dragging paints
nothing, but it was saved as a zero-size mark. With the eraser, a click
takes only the topmost mark, so that invisible mark made the next eraser
click on a visible mark at the same spot appear to do nothing. It also
added an undo step with no visible effect, and while such a press was
held Undo was enabled but only dropped the invisible shape.
A shape with no size is now not saved, and a held unmoved shape press
is not something Undo takes back. Pen and Highlighter taps still save
the dot they draw; text is unchanged.
* test(browser): drop a cancelled-stroke test the overlay test already covers
MarkupOverlay.test.tsx drives the same press, move, pointercancel and
following lost capture through the real canvas wiring and asserts Undo
stays disabled, which already proves nothing was committed or held.
* docs(browser): note the erase drag re-rasterizes the markup layer
The committed layer now renders the visible marks, so an erase drag
re-rasterizes it once per newly hit mark; the comment said never.
---------
Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com>
One host tab on a folder this client lacks marked every worktree on the host unverifiable, so reopening an emptied one never got a terminal.
Fixes#22015
Co-authored-by: mmarabel <166927047+mmarabel@users.noreply.github.com>
* fix(ai-vault): resume sessions whose worktree was deleted
SSH resumes typed `cd <deleted folder> &&`, so the agent never started, and
Codex preselected the deleted folder when resumed anywhere else.
Fixes#17745
Co-authored-by: kaluli123123 <295758798+kaluli123123@users.noreply.github.com>
* fix(ai-vault): keep folder-scoped SSH resumes in their recorded folder
Kimi resumed from another folder opens a new empty session under the same id.
* refactor(ai-vault): type the deleted-cwd drop with the resume session shape it feeds
Replaces the generic `T | Omit<T,'resumeCommand'>` return (and the
`Parameters<typeof ...>` leak it forced onto the launch hook) with the
exported session shape every resume builder already takes.
Adds the two uncovered branches: a live SSH folder keeps its scanner
command, and a local session is never probed at all.
---------
Co-authored-by: kaluli123123 <295758798+kaluli123123@users.noreply.github.com>
Co-authored-by: Neil <neil@stably.ai>
* perf(tab-bar): move scroll thumb position out of TabBar state
The thumb moved every scroll and resize frame, re-rendering every tab with it.
Fixes#24012
* perf(tab-bar): reuse the strip's resize observers for the scroll thumb
The thumb had its own observer on every tab, on top of the tab bar's.
* test(e2e): run the tab strip scroll spec without waiting on frames
CI's hidden window draws about one frame a second, so real wheel input timed out. Also drops Reflect.get for the anti-slop audit.
* test(tab-bar): pin that a re-render leaves the scroll thumb's geometry alone
The thumb's width and transform live outside React, so the element must survive a
hover re-render with its geometry intact.
* test(e2e): set the tab strip scroll start after the strip stops retitling
Opening the tabs leaves the strip pinned to its end, and the scroll event that
releases that pin only lands with the next frame - about a second in CI's hidden
window. A tab retitling inside that window re-pinned the strip to the end, so the
wheel sweep would start at an edge and every step would flip the edge flags.
---------
Co-authored-by: Neil <neil@stably.ai>
* feat(native-chat): publish each submission's journal positions
A client never learned where in the journal a send was accepted or taken back,
so drawing a stopped send had to guess from its own row and from clocks, and the
guesses misfired after restarts. Each projected submission now carries
`submittedSequence` (its submission row) and `resolvedSequence` (the dispatch row
that resolved it; absent while pending): two optional fields, computed on every
fold from rows already stored, never stored themselves. Older clients ignore
them. The failure-fact schema moves to its own module to keep the journal schema
file within its size limit.
* test(native-chat): pin a submission's resolved position on the provider-echo accept path
Also say which rows can resolve a submission, and that the submission schema, not the shared type, omits acceptedSequence.
* refactor(native-chat): publish only where each submission was sent
A rejected send's own row now moves to its rejection (#24710), so the row that
resolved a send is the item's own position and needs no second copy. Keep
submittedSequence, the one journal-order record of where a send was sent.
* feat(native-chat): publish the turn a withdrawn Codex send was answered into
A send Codex answered into a turn that then ended without taking it is
rejected by that turn's end. The rejection row now names that turn's
record, and the submission carries it as answeredInTurnItemId, so a
client can tell which turn the send belonged to without guessing from
journal order or clocks. Absent on every other send.
* feat(native-chat): say how a withdrawn Codex send joined the turn it names
The answered turn becomes { turnItemId, via }: 'start' when Codex
answered the send's turn/start with that turn, 'steer' when Orca steered
it into that running turn. A client can then tell the turn's opener from
a send steered into it even when the opener is on an older page.
* feat(native-chat): state on every rejection whether it names a turn
A rejection a host writes now always carries answeredInTurn: the turn
Codex answered the send into, or null for none. A reader can then tell
'answered into no turn' from a row written before the field existed,
which keeps no answer and is placed by the older rules.
* refactor(native-chat): give the answered-turn schema its own module
Keeps the journal schema file within its line limit.
* docs(native-chat): say an unreadable answered turn reads as none
* test(native-chat): cover paired runtime launch compatibility
* test(native-chat): use production prompt response fingerprints
* test(native-chat): pass journal items to the paired-runtime outbox hook
Main made journalItems a required outbox input; mirror production by
passing the read state's items.
* test(native-chat): pin paired-runtime routing and capability questions
- Move the paired-runtime chat test into its own file and render the real
session hook against a paired server that advertises today's capabilities
while this machine advertises none, so Stop, prompt cancel, repeated Stop
and question answers fail if their capability question goes to the wrong
runtime. Add a local and a paired session sharing one id.
- Cross-version: state that the file pins the capability strings a
v1.4.219 server really advertises, use the desktop's real handshake list,
and keep the current server only as the control for the refusal.
- agent-launch-routing: give the released-server case a capable client so
it is refused by the server gate, not the client one.
* test(native-chat): route the paired launch suite and cover the outline read
- Start the cross-version job when the launch route or the client list the
desktop sends a paired host changes, since the paired launch suite runs
them.
- Check the rail's conversation outline is read from the paired server, and
narrow the hook test's header to what it covers.
* refactor(native-chat): keep the provider resume handle opaque to shared code
Shared structured-chat code parsed each provider's resume handle: Claude's
session id and branch leaf, Codex's thread id, through a 'claude' | 'codex'
union every new agent had to widen. The in-memory handle is now
{ transport, agent, nativeId, providerData? }: shared readers use nativeId,
lease and handle-chain checks compare transport and agent, and only the
Claude adapter reads its leaf (providerData).
Stored and wire forms are unchanged for Claude and Codex. One encoding
module writes their typed shapes and decodes both those and the neutral
shape a new transport uses, which an older build refuses as unreadable
rather than reading as Codex. Key and root strings, which fork seeds,
superseded creations and resume offers persist, stay byte-identical.
The journal's own handle type becomes the journal-row and attach-wire
encoding of the same handle, and the journal identity carries the
neutral handle (null before the provider proves one).
No user-visible change.
* fix(native-chat): derive journal-row provider handles from the journal identity
The journal row converter now takes the identity every caller already holds,
so a row's handle has one obvious constructor. Tests that wrote the in-memory
handle straight into journal rows now build it through that converter, and the
processless Claude fixture names a not-yet-proved handle as null.
* fix(native-chat): refuse a stored provider handle written in both forms
A typed Claude or Codex handle that also carries the neutral form's
transport, agent, native id or provider data named two identities; it
was read as Claude or Codex and the next write dropped the other one.
Such a row now stays unreadable and is set aside untouched.
* test(native-chat): use opaque handle in queued rejection fixture
* test(native-chat): share one Codex journal identity in the integration suite
Main grew the suite to the 800-line limit; the opaque-handle import pushed it
over. The two tests built the same identity inline.
* refactor(agent-session): name the handle's adapter state resumeCursor
Rename the neutral provider handle's providerData to resumeCursor before any
row persists the neutral form: it is an adapter-owned resume position (Claude's
transcript leaf), never identity. Claude/Codex stored and wire bytes are
unchanged; their typed shapes never carried the field.
State the stored-form contract (a handle's field set is closed; later per-link
data goes on the chain link, which every build preserves) and pin it with a
record round-trip test. Document that transport records the id space the
native id was minted in, which can differ from the agent's current transport.
* test(terminal): restore the live fish fixture prerequisites (#24947)
A restored pane waits for the initial status replay before subscribing to
PTY output. This fixture never settled that replay, so fish printed its
mode-2031 arm before the renderer connected. Its PTY API also omitted the
reset-input listener required by the serializer, aborting attachment.
Settle and dispose the existing startup-snapshot registration and provide
the same reset-listener mock used by the other PTY tests. The real fish
child-stdin assertions and timeouts remain unchanged. No production change.
* fix(shortcuts): defer TUI editing chords in terminal-first mode (#24640)
Restack the original focused change onto current main, preserving every owned source and test blob and the merged CI contract and journal cleanup fixes.
Original-commit: f707cde14a
fix(shortcuts): defer TUI editing chords in terminal-first mode
Original-commit: 0c6348e49e
docs(shortcuts): describe deferred preview terminal chords
Original-commit: be62c1b6c6
Align worktree history shortcut metadata with terminal conflict policy
Restacked-from: be62c1b6c6
Restacked-onto: f7b1f9d8be
* Register supervised Qoder China and Qwen Code (#24616)
* Add Qoder session history and search with real CLI coverage
* Allow the real Qoder marker file to end with a newline
* Keep Qoder tool output out of history previews and search
* Keep Qoder search pages readable by older clients
* Verify persisted Qoder history after a real generated and resumed task
* Negotiate Qoder filters before searching an older execution host
* Combine search client imports for the CI plugin gate
* Keep the relay search oracle aligned with legacy agent filtering
* Register supervised Qoder China and Qwen lifecycle integration
* Cover Qoder China mobile assets and mixed-host resume gates
* Verify Qoder provider tags against the older released wire parser
* Verify China and Qwen keep independent Windows hook scripts
* Verify Qoder registrations against the installed older Windows release
* test(qoder): align search capability contracts and pin old-host fencing
* fix(qoder): rank exact picker identities and command aliases first
* test(qoder): preserve the regional CLI shared icon expectation
Keep the full bundled-asset and no-remote-image checks, with an explicit
shared-logo basename for Qoder China. The map also works with older
catalog type unions.
* fix(qoder): align China catalog entry with fallback order
---------
Co-authored-by: Orca Integration Recovery <orca-validation@invalid.example>
* Use the measured pnpm lookup policy automatically in hosted root CI (#24951)
* Select lookup automatically for the measured hosted root-install profile
* Record hosted automatic-mode cold cache publication proof
* Continue Antigravity IDE and 2.0 history in new CLI conversations (#24692)
* feat(antigravity): bridge IDE history into new CLI conversations
* fix(antigravity): preserve fresh-launch model and environment for IDE references
* fix(antigravity): forward IDE history opt-in through desktop IPC
* fix(antigravity): rebuild remote IDE reference startup on its host
* fix(antigravity): register IDE continuation action labels
* fix(antigravity): confine IDE references and bound metadata reads
* fix(antigravity): localize IDE continuation badges
* Preserve scanner service cache assertions and refresh Antigravity opening metadata
* Preserve Antigravity opening joins and target folder runtime authority
* fix(opencode): retry timed-out SSH plugin updates (#24666)
Preserve bounded retry behavior and the current-main status-envelope fields.
Original-PR: #24124
Reviewed-source: 103144f9c5
Co-authored-by: Justas Brazauskas <brazauskasjustas@gmail.com>
* fix(opencode): keep Go credentials private and resolve backend keys (#24615)
Preserve the complete credential storage, migration, IPC, Settings and rate-limit refresh change alongside standalone GLM plans, current-main database diagnostics and the reviewed unknown-backend environment correction. Keep native discovery cancellation third and selected environment fourth.
Original-topic-commit: 7903f1cddb
Original-topic-commit: 588117b5cf
Original-topic-commit: dedd4f8c86
Original-topic-commit: 6645dae104
Original-topic-commit: a25b80c02c1af7830b0e6a65e72d965b3ad98276
Restacked-from: a25b80c02c1af7830b0e6a65e72d965b3ad98276
Restacked-onto: b032867021
Co-authored-by: kespineira <kespineira@users.noreply.github.com>
Co-authored-by: kevimux <kevimux@users.noreply.github.com>
Reported-by: pullfrog
Reviewed-full-source: 849fe093073f4c1606bd65d79a0c725d955d0d1f
Native-helper-source: 80dbe23237
Reviewed-full-current-source: 36acb57d44adb3d378c0289c8c15f7da0fda214c
* Skip store notifications when refreshed work items are unchanged (#24530)
An identical forced refresh previously returned an empty Zustand patch, creating a new root state and notifying every subscriber. It now returns the current state after renewing the existing freshness timestamp. Real row or metadata changes still publish once.
* Read project activity once while sorting the sidebar (#24549)
Reuse the existing pure recent rank once per actual entry tuple within each synchronous sort; discard the lookup after sorting.
* Skip impossible link and tag matches in docs search results (#24646)
* Skip impossible HTML matches when formatting docs search results
After the existing link-removal phase, skip the unchanged HTML regex only when the current string has no closing delimiter.
* Skip impossible link and tag matches in docs search results
Skip the five unchanged link regexes when their protected-code input lacks ]; skip the unchanged HTML regex when its post-link input lacks >.
* Decode complete transcript lines without copying their bytes (#24546)
Decode each single owned Buffer synchronously; preserve concatenation for multipart records and remove a redundant tail array copy.
* Clear unused speech worker timers after errors and exit (#24924)
Call the existing idle timer cleanup when private current-worker state is cleared; keep current-worker guards, transcript/error order, deliberate warmth and stop deadline unchanged.
* Reuse documentation search excerpts during result navigation (#24574)
Memoize the existing pure excerpt renderer by complete raw text for the current result array, retaining original rendering as a miss fallback.
* Append subagent handoffs without recopying their message list (#24672)
Append each message reference to its private call-local scope list; retain the final merge and existing ordering.
* Cancel plugin retry timers when their fetch is replaced (#24700)
Reuse the existing timer clear block immediately after the fetch generation advances; preserve live retries and all state publications.
* Avoid rebuilding visible file paths for single-row selection (#24743)
Skip the visible path array only for keyboard replacement selection; the existing replacement branch never reads that order.
* Reuse prepared reads while searching saved agent conversations (#24535)
Use schema-complete session columns to enable the existing bounded statement cache without caching result rows.
* Reuse discovered mobile chunks during static traversal (#24774)
Iterate the existing live visited Set in FIFO discovery order instead of maintaining a second shifted queue; both actual callers own untouched esbuild JSON metadata.
* Skip unused image-size calculations in mobile web browser requests (#24941)
Use the existing mobile density budget only for mobile view; pass the existing constant to the existing assembler for web/default mode, where that argument is discarded. No cache, policy, request or native path changes.
* Skip diff analysis after a result has been discarded (#24711)
Move the unchanged pure render-limit calculation after both existing generation and section-token rejection fences.
* Skip late browser grab toasts after their surface closes (#24727)
Reuse the existing mounted-owner ref at the toast presentation entry while preserving all admitted extraction/screenshot and clipboard completion.
* Classify native chat waiting messages once (#24771)
Use the existing native filter traversal to populate a fresh waiting array, keeping the original predicate, policy, projection and output ordering while classifying each actual private slot once.
* Skip discarded Kanban pruning for an empty selection (#24782)
Guard only the existing open pruning branch when both its private selected Set and nullable anchor are empty; retain all original pre-guard projections and nonempty/anchor-only/public-helper behavior.
* Index discovered test files once during shard selection validation (#24532)
Verified selection plans previously validated each selected filename with a scan of all discovered files. One per-call Set now handles membership checks. Exact source/discovery checks, nonempty selection, fallback to all tests, shard balancing and manifests remain intact.
* Clear the watchdog benchmark heartbeat when CPU sampling fails (#24802)
Move the existing initial CPU observation and histogram enable inside the existing try/finally so their failures clear the sampler's owned heartbeat, preserving successful operation order and original errors.
* Reuse the parsed notification when leasing a push delivery (#24645)
* Reuse the parsed notification when leasing a push delivery
Pass the notification already parsed for the dismissal check into the existing private delivery builder.
* Reuse the parsed notification when leasing a push delivery
Pass the notification already parsed for the dismissal check into the existing private delivery builder.
* Update README downloads badge
* Let retired-cache GC observations settle across the existing six-turn budget (#24967)
* Collect test-selection evidence when full unit tests fail (#24955)
* Collect advisory unit-selection evidence from failed full runs
* Trigger checks after retargeting the evidence fix to main
* Check each project repository once while filtering mobile cards (#24540)
Reuse exact raw source/slug matching decisions within one project filter call, preserving the matcher, membership, negative matches, output order and identity.
* Skip renderer callbacks after their request has ended (#24631)
Reuse the existing pending-request identity check before starting its deferred renderer callback.
* Decode single-piece saved-session tails without copying them (#24632)
Decode the existing owned Buffer directly when the EOF tail has one piece; preserve the existing concatenation for multiple pieces.
* Avoid irrelevant scroll-action searches in Mac snapshots (#24731)
Check the current pure action name before searching for vertical scroll actions in the same immutable array.
* Stop copying every retained browser page before attachment (#24553)
Find the first page/generation match directly in the existing insertion-ordered Map instead of copying all page references into an array.
* Reuse event byte sizes when relay watchers notify several clients (#24558)
Store the existing grouped numeric byte-size array on the already call-local watcher batch sizing object, for reuse by later chunking clients.
* Stop building unused import candidates in the test planner (#24611)
Replace the existing ordered extension map/find with a loop that forms each candidate only when its existing lookup is reached.
* docs(terminal): explain local macOS/Linux shell startup files
Document the actual login/interactive shell startup-file order and existing shell setup behavior.
Co-authored-by: brynnclaw <261708852+brynnclaw@users.noreply.github.com>
Co-authored-by: Neil <neil@stably.ai>
* fix(editor): recognize Ruby task and configuration files
Add Ruby task/configuration filenames to the existing generated language associations.
Co-authored-by: ggbdpq <ggbdpq@gmail.com>
Co-authored-by: Neil <neil@stably.ai>
* Speed up large Markdown Find and render oversized tables (#24948)
* Speed up large Markdown Find and render oversized tables
* Poll table preview geometry outside hidden renderers
* Preserve Markdown navigation through refresh and tab restoration
* Confirm Markdown restoration when refreshed content is ready
* Trace table refresh positions and update Unicode search reference
* Recognize queued measurement scrolls before restoring Markdown anchors
* Rebuild Markdown Find ranges after renderer components change
* fix(source-control): generate clean OpenCode messages locally and over SSH (#24613)
* fix(source-control): generate clean OpenCode answers on local and SSH hosts
Restack the original focused change onto current main, preserving every owned source and test blob and the merged CI contract and journal cleanup fixes.
Original-commit: 64bb15e3f2
fix(source-control): generate clean OpenCode answers on local and SSH hosts
Use configured models and JSON answer/error events, preserve run-first arguments, and handle the precise v2 variant rejection. Hydrate SSH execution-host PATH through the existing bounded login environment resolver before direct spawning.
Credits: andy-murr (PR #5197 SSH environment intent) and coelho-doti (PR #13065 argument-order intent).
Original-commit: 1b60ec5d11
fix(source-control): retry inline OpenCode model and variant options
Original-commit: 98fdecc7a3
Preserve OpenCode named errors without a data message
Restacked-from: 98fdecc7a3
Restacked-onto: f7b1f9d8be
* fix(ci): prevent concurrent pnpm refresh during mobile typechecks (#24776)
* fix(ci): run mobile typechecks without concurrent dependency refresh
* test(ci): check effective Linux E2E package list
* test(ci): preserve the mobile production compiler barrier
---------
Co-authored-by: Orca Integration Recovery <orca-validation@invalid.example>
* test(terminal): restore the live fish fixture prerequisites (#24947)
A restored pane waits for the initial status replay before subscribing to
PTY output. This fixture never settled that replay, so fish printed its
mode-2031 arm before the renderer connected. Its PTY API also omitted the
reset-input listener required by the serializer, aborting attachment.
Settle and dispose the existing startup-snapshot registration and provide
the same reset-listener mock used by the other PTY tests. The real fish
child-stdin assertions and timeouts remain unchanged. No production change.
* fix(shortcuts): defer TUI editing chords in terminal-first mode (#24640)
Restack the original focused change onto current main, preserving every owned source and test blob and the merged CI contract and journal cleanup fixes.
Original-commit: f707cde14a
fix(shortcuts): defer TUI editing chords in terminal-first mode
Original-commit: 0c6348e49e
docs(shortcuts): describe deferred preview terminal chords
Original-commit: be62c1b6c6
Align worktree history shortcut metadata with terminal conflict policy
Restacked-from: be62c1b6c6
Restacked-onto: f7b1f9d8be
* Register supervised Qoder China and Qwen Code (#24616)
* Add Qoder session history and search with real CLI coverage
* Allow the real Qoder marker file to end with a newline
* Keep Qoder tool output out of history previews and search
* Keep Qoder search pages readable by older clients
* Verify persisted Qoder history after a real generated and resumed task
* Negotiate Qoder filters before searching an older execution host
* Combine search client imports for the CI plugin gate
* Keep the relay search oracle aligned with legacy agent filtering
* Register supervised Qoder China and Qwen lifecycle integration
* Cover Qoder China mobile assets and mixed-host resume gates
* Verify Qoder provider tags against the older released wire parser
* Verify China and Qwen keep independent Windows hook scripts
* Verify Qoder registrations against the installed older Windows release
* test(qoder): align search capability contracts and pin old-host fencing
* fix(qoder): rank exact picker identities and command aliases first
* test(qoder): preserve the regional CLI shared icon expectation
Keep the full bundled-asset and no-remote-image checks, with an explicit
shared-logo basename for Qoder China. The map also works with older
catalog type unions.
* fix(qoder): align China catalog entry with fallback order
---------
Co-authored-by: Orca Integration Recovery <orca-validation@invalid.example>
* Use the measured pnpm lookup policy automatically in hosted root CI (#24951)
* Select lookup automatically for the measured hosted root-install profile
* Record hosted automatic-mode cold cache publication proof
* fix: bound remote generation setup and honor OpenCode option terminators
Count execution-host profile resolution inside the existing request deadline, cancel its waiter promptly, and pass only the remaining time to the child. Shared bounded profile probes keep their existing cache lifetime; the SSH transport margin is unchanged.
Read OpenCode output format from active final-argv options before -- so literal prompt arguments cannot select the JSON finalizer.
Fresh exact-source controls reproduce nine failures before; 139 related checks pass after, including primary/fallback delays, deadline boundaries, cancellation, parser metadata, and SSH lanes. Node typecheck and strict changed-file lint pass.
* Continue Antigravity IDE and 2.0 history in new CLI conversations (#24692)
* feat(antigravity): bridge IDE history into new CLI conversations
* fix(antigravity): preserve fresh-launch model and environment for IDE references
* fix(antigravity): forward IDE history opt-in through desktop IPC
* fix(antigravity): rebuild remote IDE reference startup on its host
* fix(antigravity): register IDE continuation action labels
* fix(antigravity): confine IDE references and bound metadata reads
* fix(antigravity): localize IDE continuation badges
* Preserve scanner service cache assertions and refresh Antigravity opening metadata
* Preserve Antigravity opening joins and target folder runtime authority
* fix(opencode): retry timed-out SSH plugin updates (#24666)
Preserve bounded retry behavior and the current-main status-envelope fields.
Original-PR: #24124
Reviewed-source: 103144f9c5
Co-authored-by: Justas Brazauskas <brazauskasjustas@gmail.com>
* fix(opencode): keep Go credentials private and resolve backend keys (#24615)
Preserve the complete credential storage, migration, IPC, Settings and rate-limit refresh change alongside standalone GLM plans, current-main database diagnostics and the reviewed unknown-backend environment correction. Keep native discovery cancellation third and selected environment fourth.
Original-topic-commit: 7903f1cddb
Original-topic-commit: 588117b5cf
Original-topic-commit: dedd4f8c86
Original-topic-commit: 6645dae104
Original-topic-commit: a25b80c02c1af7830b0e6a65e72d965b3ad98276
Restacked-from: a25b80c02c1af7830b0e6a65e72d965b3ad98276
Restacked-onto: b032867021
Co-authored-by: kespineira <kespineira@users.noreply.github.com>
Co-authored-by: kevimux <kevimux@users.noreply.github.com>
Reported-by: pullfrog
Reviewed-full-source: 849fe093073f4c1606bd65d79a0c725d955d0d1f
Native-helper-source: 80dbe23237
Reviewed-full-current-source: 36acb57d44adb3d378c0289c8c15f7da0fda214c
* fix(opencode): enforce deadline through executable startup
Keep synchronous Windows PATH resolution and child startup within the existing request budget.
---------
Co-authored-by: Orca Integration Recovery <orca-validation@invalid.example>
Co-authored-by: Justas Brazauskas <brazauskasjustas@gmail.com>
* fix(persistence): preserve projectGroupOrder on restart for flat folder-scan groups
Retain saved project ranks when the project remains in its folder-scan group.
Co-authored-by: lurunzi <lurunzi@gmail.com>
Co-authored-by: Neil <neil@stably.ai>
* fix(runtime): reclaim temp files orphaned by interrupted mobile store writes
Reuse the existing stale temporary-file cleanup policy when each mobile store opens.
Co-authored-by: LDH1103 <ldh517525@gmail.com>
Co-authored-by: Neil <neil@stably.ai>
* fix(terminal): show actionable copy for missing folder workspace paths
Show the existing folder-path failure with concrete recovery instructions.
Co-authored-by: Wayn_Liu <wayntingliu@gmail.com>
Co-authored-by: Neil <neil@stably.ai>
* docs: reference local orca.yaml and .worktreeinclude
Document the existing workspace configuration, include/copy rules and sharing behavior.
Co-authored-by: Neil <neil@stably.ai>
* fix(orchestration): allow model selection for OMP workers
Pass an explicitly requested model through the existing OMP worker launch catalog.
Co-authored-by: Neil <neil@stably.ai>
* fix(cli): preserve primitive success results in JSON output
Check for an object before inspecting screenshot fields so primitive success results remain printable.
Related: https://github.com/stablyai/orca/pull/14735
Co-authored-by: Neil <neil@stably.ai>
Co-authored-by: VXNCXNX <VXNCXNX@users.noreply.github.com>
* Show loaded file changes before deleting a workspace
Expose up to ten already loaded changed paths without altering deletion counts, hydration or authorization.
Related: https://github.com/stablyai/orca/pull/22778
Co-authored-by: Neil <neil@stably.ai>
Co-authored-by: Michiel de Gooijer <mdgooijer@gmail.com>
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix(keybindings): record and match Option+digit shortcuts on macOS
Use the physical digit key for explicitly assigned Mac Option+digit shortcuts while retaining modifier checks.
Co-authored-by: marcuslannister <marcus@lannister.cc>
Co-authored-by: Neil <neil@stably.ai>
* fix(editor): don't throw closing a stale active file
Recover stale active-file selection within the owning workspace before choosing a surviving editor.
Related: https://github.com/stablyai/orca/pull/24715
Co-authored-by: m4air <m4air@m4airs-Air.localdomain>
Co-authored-by: Neil <neil@stably.ai>
* Fix Project Settings targeting for multiple local checkouts
Carry the selected checkout identity into the existing project Settings action.
Co-authored-by: fsmeier <1506919+fsmeier@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: Neil <neil@stably.ai>
* feat(documents): open CSV and TSV files from the OS
Extend existing OS document associations and delivery to CSV/TSV, preserving restoration and authorization.
Co-authored-by: Neil <neil@stably.ai>
* feat(cli): link GitHub and GitLab items on worktree create and set
Expose and validate the existing workspace link fields, preserving omitted values and provider identity checks.
Related: https://github.com/stablyai/orca/pull/22609
Co-authored-by: marco song <marco.song@mvlchain.io>
Co-authored-by: SongMarco <20613630+SongMarco@users.noreply.github.com>
Co-authored-by: Luca Critelli <lucacri@gmail.com>
Co-authored-by: Neil <neil@stably.ai>
* feat(sidebar): include folder workspaces in keyboard navigation
Use the rendered sidebar row order and host identity when cycling through folder and Git workspaces.
Related: https://github.com/stablyai/orca/pull/10555
Co-authored-by: Neil <neil@stably.ai>
Co-authored-by: JeongUk Park <jeongph.dev@gmail.com>
* fix(build): turn off MSBuild file tracking for Windows native rebuilds
Default Windows native rebuilds to TrackFileAccess=false while preserving explicit caller preferences.
Co-authored-by: B1nh M1nh <43268322+b1nhm1nh@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: Neil <neil@stably.ai>
* Fix Ctrl+M in Linux and Windows terminals
Keep the Minimize menu item but disable its accelerator registration on Linux and Windows.
Co-authored-by: Zhichang Yu <yuzhichang@gmail.com>
Co-authored-by: Neil <neil@stably.ai>
Co-authored-by: Ahmed Nagy <ahmednagy25t@gmail.com>
Related contribution: https://github.com/stablyai/orca/pull/24143
* fix(startup): read a nushell login PATH from $env.PATH
Use Nushell login command syntax and preserve the actual login PATH value.
Related: https://github.com/stablyai/orca/pull/22677
Co-authored-by: Kh05ifr4nD <meandSSH0219@gmail.com>
Co-authored-by: Neil <neil@stably.ai>
* fix(cursor): run local hooks through sh for non-POSIX login shells
Keep POSIX hook syntax inside one quoted sh command so the login shell can invoke it safely.
Related: https://github.com/stablyai/orca/pull/22663
Co-authored-by: Kh05ifr4nD <meandSSH0219@gmail.com>
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Neil <neil@stably.ai>
* Fix word wrap for both panes in side-by-side diffs
Forward wrapping to both diff panes through the existing editor option path and clean up listeners.
Co-authored-by: Wooseong Kim <innocarpe@gmail.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Neil <neil@stably.ai>
* fix(monaco): highlight Svelte block closers inside markup
Return Svelte block closers to the existing markup tokenizer state.
Co-authored-by: Wooseong Kim <innocarpe@gmail.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Neil <neil@stably.ai>
* fix(repos): keep active clone dialog open on outside clicks
Ignore accidental outside dismissal only while cloning; Escape, Close and Back still cancel.
Related: https://github.com/stablyai/orca/pull/24581, https://github.com/stablyai/orca/pull/23430
Co-authored-by: Neil <neil@stably.ai>
Co-authored-by: Nawapat Buakoet <nawapat.b@covest.finance>
* fix(editor): keep chat visible after closing Markdown tabs
Count remaining unified chat tabs before clearing workspace selection during editor close.
Related: https://github.com/stablyai/orca/pull/24273, https://github.com/stablyai/orca/pull/23760
Co-authored-by: Neil <neil@stably.ai>
Co-authored-by: Wooseong Kim <innocarpe@gmail.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
* Honor typed starting numbers in chat ordered lists
Forward the existing parsed ordered-list start attribute to both chat Markdown renderers.
Related: https://github.com/stablyai/orca/pull/19765
Co-authored-by: Neil <neil@stably.ai>
Co-authored-by: Frederic Barthelemy <git@fbartho.com>
* Normalize base source once while checking changed-code diagnostics (#24557)
Reuse the exact existing moved-code matching algorithm with run-local lazy normalization of immutable base source blocks across diagnostics.
* Support real OpenCode sessions in native Chat (#24647)
* Use bounded OpenCode context for vault session continuation
OpenCode database and synthetic row paths are not text transcripts. Use the
vault preview or captured pane context, preserving actual transcript paths
containing a hash and supporting both OpenCode lanes and Windows paths.
Adapted the intent of #11859 and extended it to actual installed v2 vault rows.
Co-authored-by: mrcha033 <mrcha033@users.noreply.github.com>
* Read real OpenCode sessions in terminal-backed native Chat
Reuse the bounded AI Vault SQLite worker for v1 and v2 session pages and live updates. Keep terminal input as the real execution path and pace OpenCode Stop through its two-Escape interrupt.
Co-authored-by: xodmd45-ctrl <xodmd45-ctrl@users.noreply.github.com>
* fix(opencode): publish approval cards for permission requests
* Send OpenCode native approval through its Enter selector
* Resolve mobile Chat readability for folder workspaces
* Bound OpenCode part batches and preserve v2 image attachments
* Prefer live migrated OpenCode sessions over legacy copies
* Consolidate mobile Chat eligibility test imports
* Consolidate OpenCode SQLite protocol type imports
* Update native chat settings contract for both OpenCode agents
* fix(native-chat): reconcile bounded OpenCode transcript reads
* fix(native-chat): dispatch OpenCode questions safely
* fix(native-chat): keep native discovery and transcript windows current
* feat(accounts): link standalone GLM Coding Plans (#24618)
* feat(accounts): link standalone GLM Coding Plans
Adapt the reviewed GLM accounts contribution to current main, retain Antigravity behavior, guard late credential results, expose storage protection, and redact quota errors.
Co-authored-by: Luchong <lu740528977@gmail.com>
* fix(accounts): retain GLM credential results during quota refresh
* fix(accounts): make GLM credential editing desktop-only
* fix(accounts): mirror the host GLM site in paired clients
* fix(accounts): report unknown GLM host details and split web settings tests
Apply the independently reviewed Accounts correction from697284a without the v2 adapter commits. Preserve the saved-key store and serialized write behavior.
* fix(zcode): ship required GLM account translation entries
* chore: record GLM reconciliation hook validation
* chore: validate installed GLM commit hooks
* test: complete GLM account fixtures and web API inventory
---------
Co-authored-by: Luchong <lu740528977@gmail.com>
* fix(native-chat): route transcript requests through shared SQLite worker
* fix(ci): prevent concurrent pnpm refresh during mobile typechecks (#24776)
* fix(ci): run mobile typechecks without concurrent dependency refresh
* test(ci): check effective Linux E2E package list
* test(ci): preserve the mobile production compiler barrier
---------
Co-authored-by: Orca Integration Recovery <orca-validation@invalid.example>
* test(terminal): restore the live fish fixture prerequisites (#24947)
A restored pane waits for the initial status replay before subscribing to
PTY output. This fixture never settled that replay, so fish printed its
mode-2031 arm before the renderer connected. Its PTY API also omitted the
reset-input listener required by the serializer, aborting attachment.
Settle and dispose the existing startup-snapshot registration and provide
the same reset-listener mock used by the other PTY tests. The real fish
child-stdin assertions and timeouts remain unchanged. No production change.
* fix(shortcuts): defer TUI editing chords in terminal-first mode (#24640)
Restack the original focused change onto current main, preserving every owned source and test blob and the merged CI contract and journal cleanup fixes.
Original-commit: f707cde14a
fix(shortcuts): defer TUI editing chords in terminal-first mode
Original-commit: 0c6348e49e
docs(shortcuts): describe deferred preview terminal chords
Original-commit: be62c1b6c6
Align worktree history shortcut metadata with terminal conflict policy
Restacked-from: be62c1b6c6
Restacked-onto: f7b1f9d8be
* Register supervised Qoder China and Qwen Code (#24616)
* Add Qoder session history and search with real CLI coverage
* Allow the real Qoder marker file to end with a newline
* Keep Qoder tool output out of history previews and search
* Keep Qoder search pages readable by older clients
* Verify persisted Qoder history after a real generated and resumed task
* Negotiate Qoder filters before searching an older execution host
* Combine search client imports for the CI plugin gate
* Keep the relay search oracle aligned with legacy agent filtering
* Register supervised Qoder China and Qwen lifecycle integration
* Cover Qoder China mobile assets and mixed-host resume gates
* Verify Qoder provider tags against the older released wire parser
* Verify China and Qwen keep independent Windows hook scripts
* Verify Qoder registrations against the installed older Windows release
* test(qoder): align search capability contracts and pin old-host fencing
* fix(qoder): rank exact picker identities and command aliases first
* test(qoder): preserve the regional CLI shared icon expectation
Keep the full bundled-asset and no-remote-image checks, with an explicit
shared-logo basename for Qoder China. The map also works with older
catalog type unions.
* fix(qoder): align China catalog entry with fallback order
---------
Co-authored-by: Orca Integration Recovery <orca-validation@invalid.example>
* Use the measured pnpm lookup policy automatically in hosted root CI (#24951)
* Select lookup automatically for the measured hosted root-install profile
* Record hosted automatic-mode cold cache publication proof
* fix(native-chat): keep OpenCode history usable at read limits
Continue past failed database probes while preserving discovery cancellation.
Verify rows displaced by a capped tail before deciding whether to replace history.
Represent oversized v1/v2 rows with the existing omission text and stable cursors.
* Continue Antigravity IDE and 2.0 history in new CLI conversations (#24692)
* feat(antigravity): bridge IDE history into new CLI conversations
* fix(antigravity): preserve fresh-launch model and environment for IDE references
* fix(antigravity): forward IDE history opt-in through desktop IPC
* fix(antigravity): rebuild remote IDE reference startup on its host
* fix(antigravity): register IDE continuation action labels
* fix(antigravity): confine IDE references and bound metadata reads
* fix(antigravity): localize IDE continuation badges
* Preserve scanner service cache assertions and refresh Antigravity opening metadata
* Preserve Antigravity opening joins and target folder runtime authority
* fix(opencode): retry timed-out SSH plugin updates (#24666)
Preserve bounded retry behavior and the current-main status-envelope fields.
Original-PR: #24124
Reviewed-source: 103144f9c5
Co-authored-by: Justas Brazauskas <brazauskasjustas@gmail.com>
* fix(opencode): keep Go credentials private and resolve backend keys (#24615)
Preserve the complete credential storage, migration, IPC, Settings and rate-limit refresh change alongside standalone GLM plans, current-main database diagnostics and the reviewed unknown-backend environment correction. Keep native discovery cancellation third and selected environment fourth.
Original-topic-commit: 7903f1cddb
Original-topic-commit: 588117b5cf
Original-topic-commit: dedd4f8c86
Original-topic-commit: 6645dae104
Original-topic-commit: a25b80c02c1af7830b0e6a65e72d965b3ad98276
Restacked-from: a25b80c02c1af7830b0e6a65e72d965b3ad98276
Restacked-onto: b032867021
Co-authored-by: kespineira <kespineira@users.noreply.github.com>
Co-authored-by: kevimux <kevimux@users.noreply.github.com>
Reported-by: pullfrog
Reviewed-full-source: 849fe093073f4c1606bd65d79a0c725d955d0d1f
Native-helper-source: 80dbe23237
Reviewed-full-current-source: 36acb57d44adb3d378c0289c8c15f7da0fda214c
---------
Co-authored-by: mrcha033 <mrcha033@users.noreply.github.com>
Co-authored-by: xodmd45-ctrl <xodmd45-ctrl@users.noreply.github.com>
Co-authored-by: Luchong <lu740528977@gmail.com>
Co-authored-by: Orca Integration Recovery <orca-validation@invalid.example>
Co-authored-by: Justas Brazauskas <brazauskasjustas@gmail.com>
* Drain removal fixture jobs before resetting and deleting their records (#24977)
* Reuse measured Electron preparation for current Terminal Perf refs (#24968)
* feat(jcode): add Jcode as a supported TUI agent with managed hooks
Ports PR #10521 onto current main: agent catalog, managed hook service,
agent-status listener, session resume, AI Vault parser, per-pane daemon
isolation, and Source Control AI support.
Co-authored-by: Neil <neil@stably.ai>
* feat(jcode): evidence-backed status pipeline (turn_start, live pre_tool, questions)
Co-authored-by: czzczz <chanzrz_zbf@foxmail.com>
* fix(jcode): register vault fixture, dynamic model discovery, script refresher
Co-authored-by: czzczz <chanzrz_zbf@foxmail.com>
* fix(jcode): keep finished-turn detail so completion notifications fire
Co-authored-by: czzczz <chanzrz_zbf@foxmail.com>
* fix(jcode): show the prompt in agent rows instead of jcode's repainting title
Co-authored-by: czzczz <chanzrz_zbf@foxmail.com>
* fix(jcode): pre-warm the per-pane daemon so a cold runtime dir cannot time out
Co-authored-by: czzczz <chanzrz_zbf@foxmail.com>
* fix(jcode): stop the OSC color skip from crashing every pane connect
Co-authored-by: czzczz <chanzrz_zbf@foxmail.com>
* refactor(jcode): fold three reverse-scan copies into one, reuse the shared hook POST
The jcode journal reader, the Claude transcript reader and the Command Code
transcript reader each carried their own copy of the same reverse chunked line
scan; they now share one tested helper. jcode's managed hook script drops its
hand-rolled curl for buildPosixAgentHookPostCommand, which also gains it the
raw-JSON transport and the --noproxy guard the bespoke copy was missing.
Co-authored-by: czzczz <chanzrz_zbf@foxmail.com>
* fix(jcode): narrow dynamic reads with predicates, pin the Windows hook shape
CI's anti-slop audit rejects Reflect.get: parse dynamic input into a named type
instead. Adds Windows script-shape tests too, since Windows is the platform this
change could not be exercised on directly.
Co-authored-by: czzczz <chanzrz_zbf@foxmail.com>
* test(jcode): measure the gate against the synchronous path, not the clock
The absolute 1s bound was the flake CI shard 3/8 hit: it is tight enough to catch
a synchronous gate on an idle laptop and too tight on a loaded runner. Measuring
the same POST both ways on the same machine makes the claim a ratio, which is what
the test is actually about. Mutation-checked: a synchronous gate reads 6120ms
against a 1517ms observer.
Co-authored-by: czzczz <chanzrz_zbf@foxmail.com>
* test(jcode): drop the wall-clock gate assertion
The bound was machine-speed sensitive and flaked on CI shard 3/8 at 1525ms. The
structural assertions (detached gate branch, foreground observer branch) and the
no-hang stdin drain cover the same contract without a timer.
Co-authored-by: czzczz <chanzrz_zbf@foxmail.com>
* fix(jcode): address CodeRabbit review on #22539
- Windows posted no payload at all: the shared builder reads `payload@-` from
stdin, which the gate has already drained and observer hooks never receive, so
every Windows event was dropped. Write the env var to a temp file and pipe it.
- The daemon pre-warm never fired for daemon-host spawns, which is the default
local path; it now runs there too, and from the final env so the daemon gets the
hook port and token.
- A failed runtime-dir mkdir took down every local terminal, jcode or not.
- removeJcodeManagedHooks matched the raw line, so a user hook whose comment
mentioned the managed script was deleted; matching on Windows never worked.
- A managed entry left by a copied home or a platform switch is now repointed
instead of being reported as user-owned forever.
- The OSC colour skip only checked launchAgent, so a command- or telemetry-named
jcode pane still leaked the reply into its composer.
- `['hooks']` and a commented scalar are recognised, instead of appending a
second [hooks] table that makes jcode reject the whole config.
- A failed tool's error is marked as tool output rather than agent prose.
- The vault keeps a session's stored name, counts its tokens, and skips
background_task and [Scheduled task] turns.
Co-authored-by: czzczz <chanzrz_zbf@foxmail.com>
* fix(jcode): read the journal once per turn, key prompts by byte offset
The jcode prompt reader ran a synchronous bounded file scan plus a JSON
parse on every hook event. jcode blocks on pre_tool, so a turn that ran
four tools charged the user eight scans of latency it did not need — the
prompt cannot change inside a turn.
- Cache the journal read per pane, refreshed on the turn boundary that
can change it. The cache holds the whole evidence record, since
hasExplicitUserPrompt needs the transcript-evidence flag and not just
the text, and it joins the existing pane-scoped lifecycle (close,
rename, reset) rather than living in a module singleton.
- Key a journal prompt by its absolute byte offset instead of its
region-local line index. The backward scan windows the file from EOF,
so appending shifted every boundary and reminted the key for a prompt
that never moved; a repeated turn_end then slipped past the same-hash
dedupe as a second done event with duplicate telemetry.
- Pin the platform in the daemon pre-warm tests. The pre-warm is a no-op
off POSIX, so the dedupe and retry cases would have passed vacuously
on a Windows runner.
Co-authored-by: czzczz <chanzrz_zbf@foxmail.com>
* fix(jcode): quote the managed hook path, drop tools from patch prompts
Three fixes, all on paths this PR could not exercise locally.
The managed hook command was stored as a bare path. jcode tokenizes that
string shell-style before exec'ing it directly (parse_hook_command,
crates/jcode-terminal-launch/src/lib.rs): unquoted whitespace splits, and
every unquoted backslash is consumed as an escape. So on Windows
`C:\Users\me\.orca\agent-hooks\jcode-hook.cmd` reached exec as
`C:Usersme.orcaagent-hooksjcode-hook.cmd` and no hook fired at all, and a
POSIX home with a space split into two arguments. Store the path
single-quoted (verbatim, backslashes included), falling back to double
quotes for a path containing a single quote. Existing bare entries are
already repointed by the stale-key path, and getStatus accepts both forms
so the repair is not reported as a user-owned hook. The quoting helper was
previously dead code that only tests called; the three production sites
now use it. isJcodeManagedCommand also normalizes separators, since a
`/`-only needle never matched a Windows entry.
Commit-message generation feeds a staged patch to `jcode run` as the
prompt — attacker-influenced text — while jcode's default profile exposes
shell, read, write, and MCP. Pass `--tool-profile none`, which resolves to
an empty allowed-tool set in jcode's config (base_allowed_tools), matching
the read-only posture claude (plan) and codex (read-only) already take.
docs/reference/jcode-hook-events.md was never actually in this PR: the
repo ignores docs/** and tracks reference docs by allow-list only, so the
captured-payload evidence four source comments point at was silently
dropped. Allow-list it.
Co-authored-by: czzczz <chanzrz_zbf@foxmail.com>
* test(jcode): pin the tool-profile flag in the generation plan too
The argv assertion lives in two places; --tool-profile none only landed in
one, so the plan test still expected the unrestricted argv.
Co-authored-by: czzczz <chanzrz_zbf@foxmail.com>
* fix(text-generation): refuse an oversized argv prompt on Linux too
The pre-spawn size guard only ran on Windows. Linux caps a single argv
entry at MAX_ARG_STRLEN (32 pages, 128 KiB on a 4-KiB-page host) and
execve fails with E2BIG past it, so an agent that delivers the whole
prompt as one argument — jcode, and the other argv-delivery agents —
failed on a large staged diff with an error the user could not act on.
The cap is per-argument and in bytes, which is why it is not the Windows
line budget: 40k chars trips Windows and is nowhere near the Linux limit,
so folding them together would have refused prompts Linux runs fine.
Co-authored-by: czzczz <chanzrz_zbf@foxmail.com>
* fix(jcode): register in main's remote-installer guard, drop our duplicate
Rebasing onto 853 commits of main surfaced two things the earlier branch
had hidden.
main already owns a guard for the issue-#7253 bug class
(`remote-hook-service-registry-coverage.test.ts`). This branch had added a
second, near-identical one — a parallel implementation of a test that
already existed, which is what AGENTS.md's reuse rule is about. Deleted
ours and registered jcode in main's, which is the one that has kept pace
with every agent added since.
Also fixes a missing separator in the mobile icon map. `pnpm tc` does not
cover `mobile/`, so only the session-route closure suite caught it.
Co-authored-by: czzczz <chanzrz_zbf@foxmail.com>
* fix(jcode): decompose the four files jcode pushed over max-lines
Adding an agent tipped four modules past their line budget. AGENTS.md
forbids a `max-lines` disable or a per-file bump, so each is split on a
real seam rather than silenced:
- agent-catalog.tsx keeps `AgentIcon`, which 70+ files import, and the
rows move out. The rows alone exceed the 300-line budget a `.ts` file
gets, so they follow the primary/secondary split this repo already uses
for commit-message agent specs.
- getAgentResumeArgv -> agent-resume-argv.ts, re-exported so the 18 call
sites keep one import path.
- isDiscoverableSessionFile/pathSegments -> session-file-discovery.ts.
- remoteCodexSources -> remote-session-scanner-codex-sources.ts; Codex is
the one remote agent with two CODEX_HOME roots.
Also:
- Records the readiness-census baseline jcode now needs. main added that
gate while this branch was out; the fixture is the recorded 74-case
matrix, not a hand-written one.
- Restores two entries a rebase resolution silently dropped from
config/tsconfig.cli.json (gitlab/project-ref-parser,
startup/shell-path-probe). Nothing to do with jcode; losing them was a
conflict-resolution mistake on this branch.
Co-authored-by: czzczz <chanzrz_zbf@foxmail.com>
* feat(native-chat): open structured chats on the paired Orca server that owns the workspace (#24205)
* fix(native-chat): a host admits structured sessions by client capability, not its own chat setting
A host's experimentalStructuredNativeChat decided whether any paired client could reach
agentSession.* at all, and whether session.tabs.* showed it structured tabs. That setting is the
host user's own launch preference: whether a new agent opens as a chat or a terminal is decided by
whoever launches it. Using it as admission control meant a client whose own preference was
"structured chat" was refused on a host whose preference was "terminal", and chats opened while
the setting was on were withheld from mobile once it was turned off.
The gate now asks one thing: did the client advertise agent-session.structured.v1 (in-process
callers negotiate nothing and are always admitted). Tab projection and restore follow the same
rule. With the setting no longer gating anything, the separate cleanup gate (close, cancel,
unsubscribe, release), which existed only so those kept working after the setting was switched
off, is identical to the main gate and is folded into it. The settings listener that republished
tabs when the setting changed is removed, since projection no longer depends on it.
The host setting still picks the default for launches that start on the host itself
(agent.launch from mobile, orchestration worker-start).
* fix(native-chat): the desktop declares structured chat support to paired hosts
The desktop renderer advertised agent-session.structured.v1 (and the Claude, turn-item and
background-task capabilities that go with it) to its own main process but not to a paired Orca
server. The server therefore refused every agentSession.* call from the desktop and stripped
structured chat tabs out of the tab list it published to it, so a structured chat running on a
paired server never appeared on the desktop, even though the renderer already mirrors a host's
agent-session tabs and drives each one against the server that owns its workspace.
The same renderer reads structured chats on either host, so the remote Electron list now carries
the same structured-session capabilities as the local one, and the capability test pins that
nothing is advertised only locally.
* feat(native-chat): open structured chats on the paired server that owns the workspace
With the structured-chat default on, an agent launched in a workspace that lives on a paired Orca
server always opened as a terminal (or the terminal-backed chat view). Three things kept it off
the structured path: the launch check refused every host but this machine, a remote workspace was
handed to the host-published terminal path before the structured route was even considered, and
the structured launch pipeline sent create and every follow-up call to this machine's runtime.
A workspace's owning runtime is fixed, so the pipeline now derives it from the workspace instead
of assuming this machine (structured-agent-session-owner.ts, the same derivation the chat pane
already uses to read a session). The launch intent carries that target; the pre-create support
check, create, the publication check and fence read, the launch prompt send, held option picks,
the "focus this chat" marker, the placeholder tab's host, and tab close/purge all use it.
The launch check now accepts a paired server and asks that server's own capabilities (read from
the status the client already cached for it) rather than this machine's. An SSH workspace stays
terminal-backed: no Orca runtime runs there. The host still answers createSupport before
anything is created, so an older server that refuses shows the failure in the chat tab.
A chat the user closed before its create landed is now also retired on the paired server when it
publishes, as the local sync already does. Orchestration workers placed on another runtime are
unchanged: federation creates terminal agents only.
* fix(native-chat): negotiate client-chosen launch mode so released phones and old servers keep terminals
Hosts advertise agent-session.structured.client-launch-mode.v1: they admit
structured sessions by client capability alone. A remote client that does
not advertise it (phones released before agent.launch) asks createSupport
to pick the launch mode, so the host keeps answering that with its own
setting, exactly as before. Cleanup methods keep their own named gate so a
future admission condition cannot make close or cancel refusable.
* refactor(runtime): keep the Electron client capability list in its own module
protocol-version.ts is at its line budget; the list is what the desktop
advertises to paired hosts, not the host's own contract.
* fix(native-chat): the desktop declares it picks each launch mode itself
Paired hosts and the desktop's own main process then answer createSupport
by the workspace rather than by their own chat setting.
* fix(native-chat): pin each structured chat to the host it was launched on
- Route: a paired server opens a chat only when it advertises the
client-chosen launch mode; an older server keeps its terminal. Its
capabilities come from the store's host status, not the compatibility
cache that is empty after boot or reconnect.
- A launch command override is this machine's: the route applies it only
locally, and a host's createSupport refuses on its own override.
- The owning host is resolved once, from the same value the route used,
and carried on the launch intent, its persisted record (legacy records
load as local), the provisional tab and every mirrored chat tab. Close,
purge, retry and reload read it instead of re-deriving it from a
worktree id two hosts can share; an owner that cannot be named refuses.
- Cancellation tombstones record their host: only that host's
authoritative inventory retires one, restored cleanup closes it there,
and a paired host's tombstone expires after 30 days if it never answers.
- A paired server's frame settles launches it published, as the local
inventory already does for this machine.
* fix(native-chat): a paired server that declines a chat opens its terminal instead
createSupport only reads, so both of its non-answers are settled before
anything is created:
- A paired server that answers it cannot run the chat (a WSL repo, a
Claude account mismatch, its own launch command override) closes the
chat tab and opens the terminal the route would have chosen, with a
notice saying why. This machine's own decline stays a failed chat.
- A host that could not be asked closes the chat tab and leaves one
failure toast, instead of a lingering "could not confirm" chat.
* test(native-chat): a provisional chat carries its launch's host and hands pre-create failures on
* chore(native-chat): justify the two type assertions this change's lines touch
* test(native-chat): state why each staged test fixture is cast
* fix(native-chat): chats that already exist keep showing whatever the chat setting says
The structured chat setting decides only what new agents open as. With it
off, this machine's structured chats used to be hidden while the host,
which no longer reads the setting, still reported them to the workspace
activation gate, so a workspace holding only a chat opened empty. The
local chat mirror and its startup restore now run whatever the setting
says, the continue-after-restart offer follows the chats that exist, and
the setting's copy says it applies to new agents.
* fix(native-chat): the browser client keeps its host terminal on paired servers
A browser client whose own preferences turn structured chat on took the
structured route for every paired-server workspace, but its handshake
never says it reads structured sessions, so the server refused the chat
and the user got a failed chat tab where a host terminal used to open.
The route for a paired host now also asks what this client advertises to
it: the desktop's list does, the browser client's does not. Its handshake
list is now a named constant the route reads, so the two cannot drift.
The chat setting's copy now says it runs on paired Orca servers too;
WSL and SSH hosts still use terminal chat.
* fix(native-chat): a retried launch a paired server declines opens its terminal too
A launch restored after a reload settles only through its Retry, so a
declining paired server left a failed chat there while a first launch got
the server's terminal and a notice. The chat's Retry now hands the same
pre-create failures to the same replacement, carrying the prompt the
launch had staged.
* refactor(native-chat): a paired host's cancelled-chat record ends on its 30-day TTL
The paired census re-read a host's whole inventory after every
authoritative frame to retire tombstones, and a tombstone restored after
a reload needed a second such frame, so in practice it retired nothing.
A tombstone guards a random session id and is inert once stale; the chat
is already closed on its host whenever a frame shows it. The census, its
trigger in the mirror layer and its cleanup are removed; the owner-scoped
tombstones, close-on-sight, the TTL and publication marking from frames
stay.
* fix(native-chat): a chat's pane and status read from the host recorded on its tab
The chat pane and its sidebar status still derived the host from the
workspace id, which two hosts can share; a paired chat in a non-active
same-id workspace was read from this machine. Both now read the owner
stamped on the tab, as close, purge and publication already do.
* fix(native-chat): "Resume in chat" follows the terminal resume's host rule
Agent Session History offered "Resume in chat" for a conversation
recorded on this machine into a paired server's workspace, where its
transcript does not exist. A chat now resumes a conversation only on the
host that recorded it, as the terminal resume does, and that host is the
one asked whether it can resume history.
* fix(native-chat): the chat setting says older paired servers keep terminal chat
* test(native-chat): pin that a host advertises the client-chosen launch mode
* fix(native-chat): mirror this machine's chats only where it holds them
Round 1 ran the local chat mirror for everyone so existing chats show
whatever the setting says. That gave every desktop a permanent
session-tabs listener, which turns on the runtime's phone replication
paths, plus two full session-tab censuses at startup, and made the
browser client mirror its remote host a second time.
The runtime now says whether it holds structured chats: its structured
host is built only when saved chats were restored at startup or a client
created one here, and it announces the moment one is built. The mirror,
the startup restore and the continue-after-restart offer run only when
the setting launches chats or the host holds some, and never in the
browser client. A chat a paired client creates here with the setting off
still appears at once. The chat behaviour settings show wherever chats
exist, and the setting's copy says it picks what new agents open as. The
toggle-off teardown this made dead is removed.
* test(native-chat): route a paired-server launch over the capability lists both sides really advertise
* test(native-chat): record install listeners without a cast
* fix(native-chat): a paired server admits a chat before any of it exists here
The desktop opened a paired server's chat tab, launch record, queued
prompt and focus intent before asking the server, so a "no" needed a
replacement that undid and redid all of it, and every piece it missed
was a bug: the workspace deselected, the caller told "failed" while a
terminal ran its prompt, the caller's arguments and other queued prompts
lost, and a create whose reply was lost treated as never sent.
A paired launch now asks the server first and commits nothing until it
answers. Admitted opens the chat as before. Declined runs the caller's
own launch as the server's terminal, with the existing notice (a resume
fails instead, having no terminal equivalent). Unreachable opens nothing
and names the server in one toast. The new-tab launcher reports the
host's surface for paired workspaces, as it did before paired chats,
with the prompt delivery of whichever surface got the prompt. The
replacement and its error classes are gone, and the probe inside a
launch is back to its old meaning: a "no" is a failed chat with Retry,
and no answer leaves "Could not confirm" with Retry and the prompt kept,
here as on this machine.
* fix(native-chat): mirror this machine's chats only once it holds one, not once its host is built
Session history, resume preparation, terminal resume commands and replay-safe phone launches all
build the structured host for users who never had a chat, which turned on the chat mirror and the
structured-only settings rows until the next restart. The signal is now derived from the host's
records (or a records file still owed its import) and pushed when the first chat is restored or
created. A throwing listener no longer fails the install that fired it.
* fix(native-chat): a fork's reveal never seeds a terminal beside the surface the launcher opens
Forking into a paired-server workspace revealed it as if nothing would open there, so the reveal
created a blank host terminal beside the forked chat (and beside a forked agent terminal on main).
The launcher always opens the fork's surface itself, so the reveal now says so for every surface,
as the fix-checks launch already does.
* fix(native-chat): a declined direct launch keeps the caller's CLI args; an unreachable resume toasts once
When a paired server declines a "Fix checks" chat in a new workspace, the terminal that opens
instead now carries the recipe's saved CLI arguments, launch platform and launch source, as the
terminal route did. "Resume in chat" to a server that cannot be reached showed the admission's
"Could not reach" toast and the vault's generic one; the admission marks its failure notified and
the vault adds nothing.
* fix(native-chat): a declined background create opens its terminal without switching workspaces
Since #23974 a worktree create the user moved away from must not pull them onto the new
workspace. When a paired server declined that create's chat, the fallback terminal opened as a new
agent tab, whose host create selects the workspace. The create now opens its own agent terminal the
way main's background branch does: in place from the request's startup plan (so its CLI args carry),
without selecting the workspace. A create the user is still watching keeps the new-tab fallback.
* test(native-chat): name the launch's host in main's new outbox fence test
Main's new staging-failure test calls settleStructuredAgentLaunchPrompt without the target this PR
made required; it is a local launch, as in the sibling tests.
* fix(native-chat): a paired server's new chat shows no model until the server reports the one it started
A chat on a paired server starts with the server's saved model and options, but the picker showed
this desktop's saved selection (or the catalog default) until the server reported a model, and a
pick made in that window was remembered on the server under that guessed model. A paired launch
now carries no desktop seed, and until the server reports its model the picker names no model and
takes no picks. Local chats are unchanged.
* test(native-chat): seed the paired repo without a cast
The repo literal already satisfies Repo, so the changed-lines cast gate has nothing to excuse.
* feat(native-chat): createSupport reports the saved selection a new chat on this host starts with
A chat on a paired server starts with the server's saved model and options, which the desktop could
not read, so its picker showed a guess. createSupport's answer, which the desktop already waits for
before a paired launch, now also carries that seed as a new optional field (older clients ignore it).
Create and createSupport read it through one resolver so they cannot drift.
* fix(native-chat): a paired server's new chat shows the selection the server will start it with
The paired server now names its saved model and options in the admission answer the desktop
already waits for. That seed goes into the launch intent and its persisted record, so the picker
shows the server's model at once, stays pickable like a local chat, and remembers picks on the
server under that model; a reload shows the same. The locked picker remains only for a server too
old to name a seed.
Also moves host admission and launch-outcome tracking into their own modules: the latest main
merge left structured-agent-session-launch.ts over the max-lines limit.
* test(native-chat): expect the launch intent's new seed argument in exact-call assertions
* refactor(protocol): move the Electron remote client capability list into its own module
Merging main left protocol-version.ts one line over the max-lines limit on this branch. The list of
capabilities the desktop advertises to a paired host moves, unchanged, into
electron-remote-runtime-client-capabilities.ts, the module the next PR in the stack already uses
for it; importers point there.
* fix(native-chat): a paired chat with no saved server model is pickable; Retry shows the server's current seed
A server whose user never saved a chat model sends no seed, and the desktop showed a locked,
model-only picker for it, although that is the common case: no server that can admit a paired chat
predates the seed field. Such a chat now behaves like a local chat with no saved model: the CLI
default, pickable. The lock and its snapshot helper are gone.
Retry kept the first admission's seed while the create probe, which already runs on every attempt,
reported the server's current one and dropped it. The probe's seed now replaces a paired launch's
seed and the picker's, so a retried chat shows what its create will run.
* test(cross-version): stub the launch seed resolver createSupport now reads
* test(protocol): pin the desktop capability divergence against what a paired server receives
Every paired transport sends the shared remote base plus the Electron list, so the
divergence test now compares that union with the renderer's local list instead of
the declared Electron list. A capability added only to the shared base can no
longer slip past it. The two base-only capabilities it surfaced are recorded:
skills.install-result.v2 has no local caller; the authoritative-inventory label is
read by the local tabs sync but dropped by main, and is marked unsettled.
The turn-item and both background-task-stop capabilities were already sent through
the shared base, so the Electron list no longer repeats them. The wire set is
unchanged; this PR's real change on the wire is structured.v1, the Claude
structured capability and the client launch-mode capability.
* fix(native-chat): the desktop tells its own host it picks each launch mode, so retrying an existing chat works with the setting off
* docs(native-chat): name the real exit for the released-phone createSupport rule
* fix(native-chat): the route reads the capabilities a paired host actually receives
The renderer decided whether a paired host would admit a chat from the desktop's Electron list, but
every desktop transport sends that list plus the shared remote base. They agreed only because the
route's checks happened to sit in both. The route input is now built with the same
remoteRuntimeClientCapabilities the transports use (the browser client already sends its list as is),
and a test pins each against the real handshake.
* test(cross-version): a released client still gets the host-setting createSupport answer; a launch-mode client gets supported plus the seed
* test(native-chat): let main's child-records test resolve each chat's owner
Main's new test mocks worktree-runtime-owner with only the runtime environment id, but the status
projection in this PR also resolves each structured chat's owner from the worktree. The mock keeps
the module's real exports and overrides only what the test pins.
* fix(deps): take #24204's lockfile that the merge reverted
* test(native-chat): let the Codex child-approval e2e unit test resolve each chat's owner
Its worktree-runtime-owner mock exported only the runtime environment id, but this PR's status
projection also resolves each structured chat's owner from the worktree. The mock now keeps the
module's real exports and overrides only that id, as structured-child-records-switch does.
* test(native-chat): move the close-race launch cases into their own file
Merging main added launch tests on both sides and took structured-agent-session-launch.test.ts past
the 800-line limit. The three cases where a tab close races a launch move to
structured-agent-session-launch-close-race.test.ts, with the same setup the other split launch
suites copy.
* feat(cli): add --unread/--read to orca worktree set
Pass validated read/unread flags through the existing workspace metadata update.
Contributor attribution correction: the earlier Ctrl+M squash message placed its related link after the co-author footer, so GitHub did not recognize all contributors. Preserve that credit here for https://github.com/stablyai/orca/pull/24100 and its related contribution https://github.com/stablyai/orca/pull/24143 .
Related CLI link validation retained from https://github.com/stablyai/orca/pull/20036 .
Co-authored-by: Raz Shlomo <12373339+razshlomo@users.noreply.github.com>
Co-authored-by: Neil <neil@stably.ai>
Co-authored-by: Zhichang Yu <yuzhichang@gmail.com>
Co-authored-by: Ahmed Nagy <ahmednagy25t@gmail.com>
* feat(cli): configure external worktree visibility per repo
Pass show/hide/inherit to the existing repository visibility update.
Related: https://github.com/stablyai/orca/pull/23025
Preserves CLI link validation from https://github.com/stablyai/orca/pull/20036 and read controls from https://github.com/stablyai/orca/pull/22714 .
Co-authored-by: Neil <neil@stably.ai>
Co-authored-by: KAPUIST <thsxornjs12@gmail.com>
* perf(git): reuse queries and stop canceled catalog scans (#24923)
* perf(git): reuse queries and stop canceled catalog scans
* refactor(git): check relay filesystem error codes
* test(e2e): require exact Linux package tokens (#24995)
* Allow cold node-pty setup on Windows ARM CI (#24997)
Keep a ten-minute bound only for node-pty rebuilt on Windows ARM CI hosts; all other node-gyp calls retain five minutes. Cold setup in two completed ARM jobs left compilation less than a minute.
* fix(jcode): harden Windows hooks and negotiate remote history (#24998)
Redirect the managed Windows payload file into curl instead of starting
pipeline shells, register native Windows delivery coverage, and document
Jcode v0.89.0+ as the upstream launcher requirement for invisible hooks.
Negotiate Jcode history in both directions with mixed-version Orca hosts,
preserving supported search filters and old-client response compatibility.
Co-authored-by: czzczz <chanzrz_zbf@foxmail.com>
Co-authored-by: JianJia2018 <39438074+JianJia2018@users.noreply.github.com>
* fix(git): preserve SSH review context and worktree ownership (#24945)
* fix(git): preserve SSH arguments and guard background writes
* test(terminal): settle fish fixture startup readiness
* fix(git): preserve bare UNC SSH paths
* fix(git): unescape shell operators in Windows SSH paths
* test(runtime): settle removal writes before fixture cleanup
* test(git): skip optional OpenSSH probe when unavailable
* perf(git): skip equal-tip reads and bound relay discovery
* test(processes): ratchet the removed relay Git spawn
* test(shells): wait for initial zsh output before sending input
* Fix SSH review context and recover Git maintenance cleanup safely
* Keep relay Git compatibility fixtures outside shared client projects
* Fence superseded maintenance and preserve mixed-version session search
* test: model Git child termination and search catalogs
* test: retain catalog authority over history flags
* fix(opencode): keep shared-session TUI activity with its owning pane (#24962)
* fix(opencode): bind legacy attach status to its TUI pane
Adapt the official v1 TUI API to the existing pane reporter and learn structural session identity only after execution-host admission. Preserve known creators, gate new reports to capable hosts, and register the legacy TUI entry without changing user settings or overlay targets.
Credit werlang for the same-directory investigation in #22838 and #22847; no source from that PR is copied.
(cherry picked from commit d9eeb028a23a66b78b08538a5ebf6052499ec24f)
* fix(opencode): fence legacy TUI viewers before creator attribution
Use the execution host's existing admission policy on the physical TUI
pane and launch token before rewriting to a session creator or mutating
the launch-token cache. Preserve live viewers, frozen server stamps and
OpenCode 2 behavior.
Add HTTP regressions for retired panes, closed tabs, replaced tokens and
relay retirement, plus current-launch, alias and compatibility controls.
Follow-up to #22838 and @werlang's original #22847; preserves the original
intentional creator attribution without copying that PR's source.
* fix(opencode): preserve current plugin exports in legacy TUI wrapper
Keep the current server, setup, id and other default export entries when
adding the legacy TUI entry. Cover their preservation and seed the ACL
fixtures with the separate installed TUI/config bytes.
This completes the current-main port of the legacy identity replacement
for issue #22838, carrying @werlang's original PR #22847 contribution.
* fix(opencode): retry timed-out SSH plugin updates (#24666)
Preserve bounded retry behavior and the current-main status-envelope fields.
Original-PR: #24124
Reviewed-source: 103144f9c5
Co-authored-by: Justas Brazauskas <brazauskasjustas@gmail.com>
* fix(opencode): keep Go credentials private and resolve backend keys (#24615)
Preserve the complete credential storage, migration, IPC, Settings and rate-limit refresh change alongside standalone GLM plans, current-main database diagnostics and the reviewed unknown-backend environment correction. Keep native discovery cancellation third and selected environment fourth.
Original-topic-commit: 7903f1cddb
Original-topic-commit: 588117b5cf
Original-topic-commit: dedd4f8c86
Original-topic-commit: 6645dae104
Original-topic-commit: a25b80c02c1af7830b0e6a65e72d965b3ad98276
Restacked-from: a25b80c02c1af7830b0e6a65e72d965b3ad98276
Restacked-onto: b032867021
Co-authored-by: kespineira <kespineira@users.noreply.github.com>
Co-authored-by: kevimux <kevimux@users.noreply.github.com>
Reported-by: pullfrog
Reviewed-full-source: 849fe093073f4c1606bd65d79a0c725d955d0d1f
Native-helper-source: 80dbe23237
Reviewed-full-current-source: 36acb57d44adb3d378c0289c8c15f7da0fda214c
* Skip store notifications when refreshed work items are unchanged (#24530)
An identical forced refresh previously returned an empty Zustand patch, creating a new root state and notifying every subscriber. It now returns the current state after renewing the existing freshness timestamp. Real row or metadata changes still publish once.
* Read project activity once while sorting the sidebar (#24549)
Reuse the existing pure recent rank once per actual entry tuple within each synchronous sort; discard the lookup after sorting.
* Skip impossible link and tag matches in docs search results (#24646)
* Skip impossible HTML matches when formatting docs search results
After the existing link-removal phase, skip the unchanged HTML regex only when the current string has no closing delimiter.
* Skip impossible link and tag matches in docs search results
Skip the five unchanged link regexes when their protected-code input lacks ]; skip the unchanged HTML regex when its post-link input lacks >.
* Decode complete transcript lines without copying their bytes (#24546)
Decode each single owned Buffer synchronously; preserve concatenation for multipart records and remove a redundant tail array copy.
* Clear unused speech worker timers after errors and exit (#24924)
Call the existing idle timer cleanup when private current-worker state is cleared; keep current-worker guards, transcript/error order, deliberate warmth and stop deadline unchanged.
* Reuse documentation search excerpts during result navigation (#24574)
Memoize the existing pure excerpt renderer by complete raw text for the current result array, retaining original rendering as a miss fallback.
* Append subagent handoffs without recopying their message list (#24672)
Append each message reference to its private call-local scope list; retain the final merge and existing ordering.
* Cancel plugin retry timers when their fetch is replaced (#24700)
Reuse the existing timer clear block immediately after the fetch generation advances; preserve live retries and all state publications.
* Avoid rebuilding visible file paths for single-row selection (#24743)
Skip the visible path array only for keyboard replacement selection; the existing replacement branch never reads that order.
* Reuse prepared reads while searching saved agent conversations (#24535)
Use schema-complete session columns to enable the existing bounded statement cache without caching result rows.
* Reuse discovered mobile chunks during static traversal (#24774)
Iterate the existing live visited Set in FIFO discovery order instead of maintaining a second shifted queue; both actual callers own untouched esbuild JSON metadata.
* Skip unused image-size calculations in mobile web browser requests (#24941)
Use the existing mobile density budget only for mobile view; pass the existing constant to the existing assembler for web/default mode, where that argument is discarded. No cache, policy, request or native path changes.
* Skip diff analysis after a result has been discarded (#24711)
Move the unchanged pure render-limit calculation after both existing generation and section-token rejection fences.
* Skip late browser grab toasts after their surface closes (#24727)
Reuse the existing mounted-owner ref at the toast presentation entry while preserving all admitted extraction/screenshot and clipboard completion.
* Classify native chat waiting messages once (#24771)
Use the existing native filter traversal to populate a fresh waiting array, keeping the original predicate, policy, projection and output ordering while classifying each actual private slot once.
* Skip discarded Kanban pruning for an empty selection (#24782)
Guard only the existing open pruning branch when both its private selected Set and nullable anchor are empty; retain all original pre-guard projections and nonempty/anchor-only/public-helper behavior.
* Index discovered test files once during shard selection validation (#24532)
Verified selection plans previously validated each selected filename with a scan of all discovered files. One per-call Set now handles membership checks. Exact source/discovery checks, nonempty selection, fallback to all tests, shard balancing and manifests remain intact.
* Clear the watchdog benchmark heartbeat when CPU sampling fails (#24802)
Move the existing initial CPU observation and histogram enable inside the existing try/finally so their failures clear the sampler's owned heartbeat, preserving successful operation order and original errors.
* Reuse the parsed notification when leasing a push delivery (#24645)
* Reuse the parsed notification when leasing a push delivery
Pass the notification already parsed for the dismissal check into the existing private delivery builder.
* Reuse the parsed notification when leasing a push delivery
Pass the notification already parsed for the dismissal check into the existing private delivery builder.
* Update README downloads badge
* Let retired-cache GC observations settle across the existing six-turn budget (#24967)
* Collect test-selection evidence when full unit tests fail (#24955)
* Collect advisory unit-selection evidence from failed full runs
* Trigger checks after retargeting the evidence fix to main
* Check each project repository once while filtering mobile cards (#24540)
Reuse exact raw source/slug matching decisions within one project filter call, preserving the matcher, membership, negative matches, output order and identity.
* Skip renderer callbacks after their request has ended (#24631)
Reuse the existing pending-request identity check before starting its deferred renderer callback.
* Decode single-piece saved-session tails without copying them (#24632)
Decode the existing owned Buffer directly when the EOF tail has one piece; preserve the existing concatenation for multiple pieces.
* Avoid irrelevant scroll-action searches in Mac snapshots (#24731)
Check the current pure action name before searching for vertical scroll actions in the same immutable array.
* Stop copying every retained browser page before attachment (#24553)
Find the first page/generation match directly in the existing insertion-ordered Map instead of copying all page references into an array.
* Reuse event byte sizes when relay watchers notify several clients (#24558)
Store the existing grouped numeric byte-size array on the already call-local watcher batch sizing object, for reuse by later chunking clients.
* Stop building unused import candidates in the test planner (#24611)
Replace the existing ordered extension map/find with a loop that forms each candidate only when its existing lookup is reached.
* docs(terminal): explain local macOS/Linux shell startup files
Document the actual login/interactive shell startup-file order and existing shell setup behavior.
Co-authored-by: brynnclaw <261708852+brynnclaw@users.noreply.github.com>
Co-authored-by: Neil <neil@stably.ai>
* fix(editor): recognize Ruby task and configuration files
Add Ruby task/configuration filenames to the existing generated language associations.
Co-authored-by: ggbdpq <ggbdpq@gmail.com>
Co-authored-by: Neil <neil@stably.ai>
* Speed up large Markdown Find and render oversized tables (#24948)
* Speed up large Markdown Find and render oversized tables
* Poll table preview geometry outside hidden renderers
* Preserve Markdown navigation through refresh and tab restoration
* Confirm Markdown restoration when refreshed content is ready
* Trace table refresh positions and update Unicode search reference
* Recognize queued measurement scrolls before restoring Markdown anchors
* Rebuild Markdown Find ranges after renderer components change
* fix(source-control): generate clean OpenCode messages locally and over SSH (#24613)
* fix(source-control): generate clean OpenCode answers on local and SSH hosts
Restack the original focused change onto current main, preserving every owned source and test blob and the merged CI contract and journal cleanup fixes.
Original-commit: 64bb15e3f2
fix(source-control): generate clean OpenCode answers on local and SSH hosts
Use configured models and JSON answer/error events, preserve run-first arguments, and handle the precise v2 variant rejection. Hydrate SSH execution-host PATH through the existing bounded login environment resolver before direct spawning.
Credits: andy-murr (PR #5197 SSH environment intent) and coelho-doti (PR #13065 argument-order intent).
Original-commit: 1b60ec5d11
fix(source-control): retry inline OpenCode model and variant options
Original-commit: 98fdecc7a3
Preserve OpenCode named errors without a data message
Restacked-from: 98fdecc7a3
Restacked-onto: f7b1f9d8be
* fix(ci): prevent concurrent pnpm refresh during mobile typechecks (#24776)
* fix(ci): run mobile typechecks without concurrent dependency refresh
* test(ci): check effective Linux E2E package list
* test(ci): preserve the mobile production compiler barrier
---------
Co-authored-by: Orca Integration Recovery <orca-validation@invalid.example>
* test(terminal): restore the live fish fixture prerequisites (#24947)
A restored pane waits for the initial status replay before subscribing to
PTY output. This fixture never settled that replay, so fish printed its
mode-2031 arm before the renderer connected. Its PTY API also omitted the
reset-input listener required by the serializer, aborting attachment.
Settle and dispose the existing startup-snapshot registration and provide
the same reset-listener mock used by the other PTY tests. The real fish
child-stdin assertions and timeouts remain unchanged. No production change.
* fix(shortcuts): defer TUI editing chords in terminal-first mode (#24640)
Restack the original focused change onto current main, preserving every owned source and test blob and the merged CI contract and journal cleanup fixes.
Original-commit: f707cde14a
fix(shortcuts): defer TUI editing chords in terminal-first mode
Original-commit: 0c6348e49e
docs(shortcuts): describe deferred preview terminal chords
Original-commit: be62c1b6c6
Align worktree history shortcut metadata with terminal conflict policy
Restacked-from: be62c1b6c6
Restacked-onto: f7b1f9d8be
* Register supervised Qoder China and Qwen Code (#24616)
* Add Qoder session history and search with real CLI coverage
* Allow the real Qoder marker file to end with a newline
* Keep Qoder tool output out of history previews and search
* Keep Qoder search pages readable by older clients
* Verify persisted Qoder history after a real generated and resumed task
* Negotiate Qoder filters before searching an older execution host
* Combine search client imports for the CI plugin gate
* Keep the relay search oracle aligned with legacy agent filtering
* Register supervised Qoder China and Qwen lifecycle integration
* Cover Qoder China mobile assets and mixed-host resume gates
* Verify Qoder provider tags against the older released wire parser
* Verify China and Qwen keep independent Windows hook scripts
* Verify Qoder registrations against the installed older Windows release
* test(qoder): align search capability contracts and pin old-host fencing
* fix(qoder): rank exact picker identities and command aliases first
* test(qoder): preserve the regional CLI shared icon expectation
Keep the full bundled-asset and no-remote-image checks, with an explicit
shared-logo basename for Qoder China. The map also works with older
catalog type unions.
* fix(qoder): align China catalog entry with fallback order
---------
Co-authored-by: Orca Integration Recovery <orca-validation@invalid.example>
* Use the measured pnpm lookup policy automatically in hosted root CI (#24951)
* Select lookup automatically for the measured hosted root-install profile
* Record hosted automatic-mode cold cache publication proof
* fix: bound remote generation setup and honor OpenCode option terminators
Count execution-host profile resolution inside the existing request deadline, cancel its waiter promptly, and pass only the remaining time to the child. Shared bounded profile probes keep their existing cache lifetime; the SSH transport margin is unchanged.
Read OpenCode output format from active final-argv options before -- so literal prompt arguments cannot select the JSON finalizer.
Fresh exact-source controls reproduce nine failures before; 139 related checks pass after, including primary/fallback delays, deadline boundaries, cancellation, parser metadata, and SSH lanes. Node typecheck and strict changed-file lint pass.
* Continue Antigravity IDE and 2.0 history in new CLI conversations (#24692)
* feat(antigravity): bridge IDE history into new CLI conversations
* fix(antigravity): preserve fresh-launch model and environment for IDE references
* fix(antigravity): forward IDE history opt-in through desktop IPC
* fix(antigravity): rebuild remote IDE reference startup on its host
* fix(antigravity): register IDE continuation action labels
* fix(antigravity): confine IDE references and bound metadata reads
* fix(antigravity): localize IDE continuation badges
* Preserve scanner service cache assertions and refresh Antigravity opening metadata
* Preserve Antigravity opening joins and target folder runtime authority
* fix(opencode): retry timed-out SSH plugin updates (#24666)
Preserve bounded retry behavior and the current-main status-envelope fields.
Original-PR: #24124
Reviewed-source: 103144f9c5
Co-authored-by: Justas Brazauskas <brazauskasjustas@gmail.com>
* fix(opencode): keep Go credentials private and resolve backend keys (#24615)
Preserve the complete credential storage, migration, IPC, Settings and rate-limit refresh change alongside standalone GLM plans, current-main database diagnostics and the reviewed unknown-backend environment correction. Keep native discovery cancellation third and selected environment fourth.
Original-topic-commit: 7903f1cddb
Original-topic-commit: 588117b5cf
Original-topic-commit: dedd4f8c86
Original-topic-commit: 6645dae104
Original-topic-commit: a25b80c02c1af7830b0e6a65e72d965b3ad98276
Restacked-from: a25b80c02c1af7830b0e6a65e72d965b3ad98276
Restacked-onto: b032867021
Co-authored-by: kespineira <kespineira@users.noreply.github.com>
Co-authored-by: kevimux <kevimux@users.noreply.github.com>
Reported-by: pullfrog
Reviewed-full-source: 849fe093073f4c1606bd65d79a0c725d955d0d1f
Native-helper-source: 80dbe23237
Reviewed-full-current-source: 36acb57d44adb3d378c0289c8c15f7da0fda214c
* fix(opencode): enforce deadline through executable startup
Keep synchronous Windows PATH resolution and child startup within the existing request budget.
---------
Co-authored-by: Orca Integration Recovery <orca-validation@invalid.example>
Co-authored-by: Justas Brazauskas <brazauskasjustas@gmail.com>
* fix(persistence): preserve projectGroupOrder on restart for flat folder-scan groups
Retain saved project ranks when the project remains in its folder-scan group.
Co-authored-by: lurunzi <lurunzi@gmail.com>
Co-authored-by: Neil <neil@stably.ai>
* fix(runtime): reclaim temp files orphaned by interrupted mobile store writes
Reuse the existing stale temporary-file cleanup policy when each mobile store opens.
Co-authored-by: LDH1103 <ldh517525@gmail.com>
Co-authored-by: Neil <neil@stably.ai>
* fix(terminal): show actionable copy for missing folder workspace paths
Show the existing folder-path failure with concrete recovery instructions.
Co-authored-by: Wayn_Liu <wayntingliu@gmail.com>
Co-authored-by: Neil <neil@stably.ai>
* docs: reference local orca.yaml and .worktreeinclude
Document the existing workspace configuration, include/copy rules and sharing behavior.
Co-authored-by: Neil <neil@stably.ai>
* fix(orchestration): allow model selection for OMP workers
Pass an explicitly requested model through the existing OMP worker launch catalog.
Co-authored-by: Neil <neil@stably.ai>
* fix(cli): preserve primitive success results in JSON output
Check for an object before inspecting screenshot fields so primitive success results remain printable.
Related: https://github.com/stablyai/orca/pull/14735
Co-authored-by: Neil <neil@stably.ai>
Co-authored-by: VXNCXNX <VXNCXNX@users.noreply.github.com>
* Show loaded file changes before deleting a workspace
Expose up to ten already loaded changed paths without altering deletion counts, hydration or authorization.
Related: https://github.com/stablyai/orca/pull/22778
Co-authored-by: Neil <neil@stably.ai>
Co-authored-by: Michiel de Gooijer <mdgooijer@gmail.com>
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix(keybindings): record and match Option+digit shortcuts on macOS
Use the physical digit key for explicitly assigned Mac Option+digit shortcuts while retaining modifier checks.
Co-authored-by: marcuslannister <marcus@lannister.cc>
Co-authored-by: Neil <neil@stably.ai>
* fix(editor): don't throw closing a stale active file
Recover stale active-file selection within the owning workspace before choosing a surviving editor.
Related: https://github.com/stablyai/orca/pull/24715
Co-authored-by: m4air <m4air@m4airs-Air.localdomain>
Co-authored-by: Neil <neil@stably.ai>
* Fix Project Settings targeting for multiple local checkouts
Carry the selected checkout identity into the existing project Settings action.
Co-authored-by: fsmeier <1506919+fsmeier@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: Neil <neil@stably.ai>
* feat(documents): open CSV and TSV files from the OS
Extend existing OS document associations and delivery to CSV/TSV, preserving restoration and authorization.
Co-authored-by: Neil <neil@stably.ai>
* feat(cli): link GitHub and GitLab items on worktree create and set
Expose and validate the existing workspace link fields, preserving omitted values and provider identity checks.
Related: https://github.com/stablyai/orca/pull/22609
Co-authored-by: marco song <marco.song@mvlchain.io>
Co-authored-by: SongMarco <20613630+SongMarco@users.noreply.github.com>
Co-authored-by: Luca Critelli <lucacri@gmail.com>
Co-authored-by: Neil <neil@stably.ai>
* feat(sidebar): include folder workspaces in keyboard navigation
Use the rendered sidebar row order and host identity when cycling through folder and Git workspaces.
Related: https://github.com/stablyai/orca/pull/10555
Co-authored-by: Neil <neil@stably.ai>
Co-authored-by: JeongUk Park <jeongph.dev@gmail.com>
* fix(build): turn off MSBuild file tracking for Windows native rebuilds
Default Windows native rebuilds to TrackFileAccess=false while preserving explicit caller preferences.
Co-authored-by: B1nh M1nh <43268322+b1nhm1nh@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: Neil <neil@stably.ai>
* Fix Ctrl+M in Linux and Windows terminals
Keep the Minimize menu item but disable its accelerator registration on Linux and Windows.
Co-authored-by: Zhichang Yu <yuzhichang@gmail.com>
Co-authored-by: Neil <neil@stably.ai>
Co-authored-by: Ahmed Nagy <ahmednagy25t@gmail.com>
Related contribution: https://github.com/stablyai/orca/pull/24143
* fix(startup): read a nushell login PATH from $env.PATH
Use Nushell login command syntax and preserve the actual login PATH value.
Related: https://github.com/stablyai/orca/pull/22677
Co-authored-by: Kh05ifr4nD <meandSSH0219@gmail.com>
Co-authored-by: Neil <neil@stably.ai>
* fix(cursor): run local hooks through sh for non-POSIX login shells
Keep POSIX hook syntax inside one quoted sh command so the login shell can invoke it safely.
Related: https://github.com/stablyai/orca/pull/22663
Co-authored-by: Kh05ifr4nD <meandSSH0219@gmail.com>
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Neil <neil@stably.ai>
* Fix word wrap for both panes in side-by-side diffs
Forward wrapping to both diff panes through the existing editor option path and clean up listeners.
Co-authored-by: Wooseong Kim <innocarpe@gmail.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Neil <neil@stably.ai>
* fix(monaco): highlight Svelte block closers inside markup
Return Svelte block closers to the existing markup tokenizer state.
Co-authored-by: Wooseong Kim <innocarpe@gmail.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Neil <neil@stably.ai>
* fix(repos): keep active clone dialog open on outside clicks
Ignore accidental outside dismissal only while cloning; Escape, Close and Back still cancel.
Related: https://github.com/stablyai/orca/pull/24581, https://github.com/stablyai/orca/pull/23430
Co-authored-by: Neil <neil@stably.ai>
Co-authored-by: Nawapat Buakoet <nawapat.b@covest.finance>
* fix(editor): keep chat visible after closing Markdown tabs
Count remaining unified chat tabs before clearing workspace selection during editor close.
Related: https://github.com/stablyai/orca/pull/24273, https://github.com/stablyai/orca/pull/23760
Co-authored-by: Neil <neil@stably.ai>
Co-authored-by: Wooseong Kim <innocarpe@gmail.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
* Honor typed starting numbers in chat ordered lists
Forward the existing parsed ordered-list start attribute to both chat Markdown renderers.
Related: https://github.com/stablyai/orca/pull/19765
Co-authored-by: Neil <neil@stably.ai>
Co-authored-by: Frederic Barthelemy <git@fbartho.com>
* Normalize base source once while checking changed-code diagnostics (#24557)
Reuse the exact existing moved-code matching algorithm with run-local lazy normalization of immutable base source blocks across diagnostics.
* Support real OpenCode sessions in native Chat (#24647)
* Use bounded OpenCode context for vault session continuation
OpenCode database and synthetic row paths are not text transcripts. Use the
vault preview or captured pane context, preserving actual transcript paths
containing a hash and supporting both OpenCode lanes and Windows paths.
Adapted the intent of #11859 and extended it to actual installed v2 vault rows.
Co-authored-by: mrcha033 <mrcha033@users.noreply.github.com>
* Read real OpenCode sessions in terminal-backed native Chat
Reuse the bounded AI Vault SQLite worker for v1 and v2 session pages and live updates. Keep terminal input as the real execution path and pace OpenCode Stop through its two-Escape interrupt.
Co-authored-by: xodmd45-ctrl <xodmd45-ctrl@users.noreply.github.com>
* fix(opencode): publish approval cards for permission requests
* Send OpenCode native approval through its Enter selector
* Resolve mobile Chat readability for folder workspaces
* Bound OpenCode part batches and preserve v2 image attachments
* Prefer live migrated OpenCode sessions over legacy copies
* Consolidate mobile Chat eligibility test imports
* Consolidate OpenCode SQLite protocol type imports
* Update native chat settings contract for both OpenCode agents
* fix(native-chat): reconcile bounded OpenCode transcript reads
* fix(native-chat): dispatch OpenCode questions safely
* fix(native-chat): keep native discovery and transcript windows current
* feat(accounts): link standalone GLM Coding Plans (#24618)
* feat(accounts): link standalone GLM Coding Plans
Adapt the reviewed GLM accounts contribution to current main, retain Antigravity behavior, guard late credential results, expose storage protection, and redact quota errors.
Co-authored-by: Luchong <lu740528977@gmail.com>
* fix(accounts): retain GLM credential results during quota refresh
* fix(accounts): make GLM credential editing desktop-only
* fix(accounts): mirror the host GLM site in paired clients
* fix(accounts): report unknown GLM host details and split web settings tests
Apply the independently reviewed Accounts correction from697284a without the v2 adapter commits. Preserve the saved-key store and serialized write behavior.
* fix(zcode): ship required GLM account translation entries
* chore: record GLM reconciliation hook validation
* chore: validate installed GLM commit hooks
* test: complete GLM account fixtures and web API inventory
---------
Co-authored-by: Luchong <lu740528977@gmail.com>
* fix(native-chat): route transcript requests through shared SQLite worker
* fix(ci): prevent concurrent pnpm refresh during mobile typechecks (#24776)
* fix(ci): run mobile typechecks without concurrent dependency refresh
* test(ci): check effective Linux E2E package list
* test(ci): preserve the mobile production compiler barrier
---------
Co-authored-by: Orca Integration Recovery <orca-validation@invalid.example>
* test(terminal): restore the live fish fixture prerequisites (#24947)
A restored pane waits for the initial status replay before subscribing to
PTY output. This fixture never settled that replay, so fish printed its
mode-2031 arm before the renderer connected. Its PTY API also omitted the
reset-input listener required by the serializer, aborting attachment.
Settle and dispose the existing startup-snapshot registration and provide
the same reset-listener mock used by the other PTY tests. The real fish
child-stdin assertions and timeouts remain unchanged. No production change.
* fix(shortcuts): defer TUI editing chords in terminal-first mode (#24640)
Restack the original focused change onto current main, preserving every owned source and test blob and the merged CI contract and journal cleanup fixes.
Original-commit: f707cde14a
fix(shortcuts): defer TUI editing chords in terminal-first mode
Original-commit: 0c6348e49e
docs(shortcuts): describe deferred preview terminal chords
Original-commit: be62c1b6c6
Align worktree history shortcut metadata with terminal conflict policy
Restacked-from: be62c1b6c6
Restacked-onto: f7b1f9d8be
* Register supervised Qoder China and Qwen Code (#24616)
* Add Qoder session history and search with real CLI coverage
* Allow the real Qoder marker file to end with a newline
* Keep Qoder tool output out of history previews and search
* Keep Qoder search pages readable by older clients
* Verify persisted Qoder history after a real generated and resumed task
* Negotiate Qoder filters before searching an older execution host
* Combine search client imports for the CI plugin gate
* Keep the relay search oracle aligned with legacy agent filtering
* Register supervised Qoder China and Qwen lifecycle integration
* Cover Qoder China mobile assets and mixed-host resume gates
* Verify Qoder provider tags against the older released wire parser
* Verify China and Qwen keep independent Windows hook scripts
* Verify Qoder registrations against the installed older Windows release
* test(qoder): align search capability contracts and pin old-host fencing
* fix(qoder): rank exact picker identities and command aliases first
* test(qoder): preserve the regional CLI shared icon expectation
Keep the full bundled-asset and no-remote-image checks, with an explicit
shared-logo basename for Qoder China. The map also works with older
catalog type unions.
* fix(qoder): align China catalog entry with fallback order
---------
Co-authored-by: Orca Integration Recovery <orca-validation@invalid.example>
* Use the measured pnpm lookup policy automatically in hosted root CI (#24951)
* Select lookup automatically for the measured hosted root-install profile
* Record hosted automatic-mode cold cache publication proof
* fix(native-chat): keep OpenCode history usable at read limits
Continue past failed database probes while preserving discovery cancellation.
Verify rows displaced by a capped tail before deciding whether to replace history.
Represent oversized v1/v2 rows with the existing omission text and stable cursors.
* Continue Antigravity IDE and 2.0 history in new CLI conversations (#24692)
* feat(antigravity): bridge IDE history into new CLI conversations
* fix(antigravity): preserve fresh-launch model and environment for IDE references
* fix(antigravity): forward IDE history opt-in through desktop IPC
* fix(antigravity): rebuild remote IDE reference startup on its host
* fix(antigravity): register IDE continuation action labels
* fix(antigravity): confine IDE references and bound metadata reads
* fix(antigravity): localize IDE continuation badges
* Preserve scanner service cache assertions and refresh Antigravity opening metadata
* Preserve Antigravity opening joins and target folder runtime authority
* fix(opencode): retry timed-out SSH plugin updates (#24666)
Preserve bounded retry behavior and the current-main status-envelope fields.
Original-PR: #24124
Reviewed-source: 103144f9c5
Co-authored-by: Justas Brazauskas <brazauskasjustas@gmail.com>
* fix(opencode): keep Go credentials private and resolve backend keys (#24615)
Preserve the complete credential storage, migration, IPC, Settings and rate-limit refresh change alongside standalone GLM plans, current-main database diagnostics and the reviewed unknown-backend environment correction. Keep native discovery cancellation third and selected environment fourth.
Original-topic-commit: 7903f1cddb
Original-topic-commit: 588117b5cf
Original-topic-commit: dedd4f8c86
Original-topic-commit: 6645dae104
Original-topic-commit: a25b80c02c1af7830b0e6a65e72d965b3ad98276
Restacked-from: a25b80c02c1af7830b0e6a65e72d965b3ad98276
Restacked-onto: b032867021
Co-authored-by: kespineira <kespineira@users.noreply.github.com>
Co-authored-by: kevimux <kevimux@users.noreply.github.com>
Reported-by: pullfrog
Reviewed-full-source: 849fe093073f4c1606bd65d79a0c725d955d0d1f
Native-helper-source: 80dbe23237
Reviewed-full-current-source: 36acb57d44adb3d378c0289c8c15f7da0fda214c
---------
Co-authored-by: mrcha033 <mrcha033@users.noreply.github.com>
Co-authored-by: xodmd45-ctrl <xodmd45-ctrl@users.noreply.github.com>
Co-authored-by: Luchong <lu740528977@gmail.com>
Co-authored-by: Orca Integration Recovery <orca-validation@invalid.example>
Co-authored-by: Justas Brazauskas <brazauskasjustas@gmail.com>
* Drain removal fixture jobs before resetting and deleting their records (#24977)
* Reuse measured Electron preparation for current Terminal Perf refs (#24968)
* feat(jcode): add Jcode as a supported TUI agent with managed hooks
Ports PR #10521 onto current main: agent catalog, managed hook service,
agent-status listener, session resume, AI Vault parser, per-pane daemon
isolation, and Source Control AI support.
Co-authored-by: Neil <neil@stably.ai>
* feat(jcode): evidence-backed status pipeline (turn_start, live pre_tool, questions)
Co-authored-by: czzczz <chanzrz_zbf@foxmail.com>
* fix(jcode): register vault fixture, dynamic model discovery, script refresher
Co-authored-by: czzczz <chanzrz_zbf@foxmail.com>
* fix(jcode): keep finished-turn detail so completion notifications fire
Co-authored-by: czzczz <chanzrz_zbf@foxmail.com>
* fix(jcode): show the prompt in agent rows instead of jcode's repainting title
Co-authored-by: czzczz <chanzrz_zbf@foxmail.com>
* fix(jcode): pre-warm the per-pane daemon so a cold runtime dir cannot time out
Co-authored-by: czzczz <chanzrz_zbf@foxmail.com>
* fix(jcode): stop the OSC color skip from crashing every pane connect
Co-authored-by: czzczz <chanzrz_zbf@foxmail.com>
* refactor(jcode): fold three reverse-scan copies into one, reuse the shared hook POST
The jcode journal reader, the Claude transcript reader and the Command Code
transcript reader each carried their own copy of the same reverse chunked line
scan; they now share one tested helper. jcode's managed hook script drops its
hand-rolled curl for buildPosixAgentHookPostCommand, which also gains it the
raw-JSON transport and the --noproxy guard the bespoke copy was missing.
Co-authored-by: czzczz <chanzrz_zbf@foxmail.com>
* fix(jcode): narrow dynamic reads with predicates, pin the Windows hook shape
CI's anti-slop audit rejects Reflect.get: parse dynamic input into a named type
instead. Adds Windows script-shape tests too, since Windows is the platform this
change could not be exercised on directly.
Co-authored-by: czzczz <chanzrz_zbf@foxmail.com>
* test(jcode): measure the gate against the synchronous path, not the clock
The absolute 1s bound was the flake CI shard 3/8 hit: it is tight enough to catch
a synchronous gate on an idle laptop and too tight on a loaded runner. Measuring
the same POST both ways on the same machine makes the claim a ratio, which is what
the test is actually about. Mutation-checked: a synchronous gate reads 6120ms
against a 1517ms observer.
Co-authored-by: czzczz <chanzrz_zbf@foxmail.com>
* test(jcode): drop the wall-clock gate assertion
The bound was machine-speed sensitive and flaked on CI shard 3/8 at 1525ms. The
structural assertions (detached gate branch, foreground observer branch) and the
no-hang stdin drain cover the same contract without a timer.
Co-authored-by: czzczz <chanzrz_zbf@foxmail.com>
* fix(jcode): address CodeRabbit review on #22539
- Windows posted no payload at all: the shared builder reads `payload@-` from
stdin, which the gate has already drained and observer hooks never receive, so
every Windows event was dropped. Write the env var to a temp file and pipe it.
- The daemon pre-warm never fired for daemon-host spawns, which is the default
local path; it now runs there too, and from the final env so the daemon gets the
hook port and token.
- A failed runtime-dir mkdir took down every local terminal, jcode or not.
- removeJcodeManagedHooks matched the raw line, so a user hook whose comment
mentioned the managed script was deleted; matching on Windows never worked.
- A managed entry left by a copied home or a platform switch is now repointed
instead of being reported as user-owned forever.
- The OSC colour skip only checked launchAgent, so a command- or telemetry-named
jcode pane still leaked the reply into its composer.
- `['hooks']` and a commented scalar are recognised, instead of appending a
second [hooks] table that makes jcode reject the whole config.
- A failed tool's error is marked as tool output rather than agent prose.
- The vault keeps a session's stored name, counts its tokens, and skips
background_task and [Scheduled task] turns.
Co-authored-by: czzczz <chanzrz_zbf@foxmail.com>
* fix(jcode): read the journal once per turn, key prompts by byte offset
The jcode prompt reader ran a synchronous bounded file scan plus a JSON
parse on every hook event. jcode blocks on pre_tool, so a turn that ran
four tools charged the user eight scans of latency it did not need — the
prompt cannot change inside a turn.
- Cache the journal read per pane, refreshed on the turn boundary that
can change it. The cache holds the whole evidence record, since
hasExplicitUserPrompt needs the transcript-evidence flag and not just
the text, and it joins the existing pane-scoped lifecycle (close,
rename, reset) rather than living in a module singleton.
- Key a journal prompt by its absolute byte offset instead of its
region-local line index. The backward scan windows the file from EOF,
so appending shifted every boundary and reminted the key for a prompt
that never moved; a repeated turn_end then slipped past the same-hash
dedupe as a second done event with duplicate telemetry.
- Pin the platform in the daemon pre-warm tests. The pre-warm is a no-op
off POSIX, so the dedupe and retry cases would have passed vacuously
on a Windows runner.
Co-authored-by: czzczz <chanzrz_zbf@foxmail.com>
* fix(jcode): quote the managed hook path, drop tools from patch prompts
Three fixes, all on paths this PR could not exercise locally.
The managed hook command was stored as a bare path. jcode tokenizes that
string shell-style before exec'ing it directly (parse_hook_command,
crates/jcode-terminal-launch/src/lib.rs): unquoted whitespace splits, and
every unquoted backslash is consumed as an escape. So on Windows
`C:\Users\me\.orca\agent-hooks\jcode-hook.cmd` reached exec as
`C:Usersme.orcaagent-hooksjcode-hook.cmd` and no hook fired at all, and a
POSIX home with a space split into two arguments. Store the path
single-quoted (verbatim, backslashes included), falling back to double
quotes for a path containing a single quote. Existing bare entries are
already repointed by the stale-key path, and getStatus accepts both forms
so the repair is not reported as a user-owned hook. The quoting helper was
previously dead code that only tests called; the three production sites
now use it. isJcodeManagedCommand also normalizes separators, since a
`/`-only needle never matched a Windows entry.
Commit-message generation feeds a staged patch to `jcode run` as the
prompt — attacker-influenced text — while jcode's default profile exposes
shell, read, write, and MCP. Pass `--tool-profile none`, which resolves to
an empty allowed-tool set in jcode's config (base_allowed_tools), matching
the read-only posture claude (plan) and codex (read-only) already take.
docs/reference/jcode-hook-events.md was never actually in this PR: the
repo ignores docs/** and tracks reference docs by allow-list only, so the
captured-payload evidence four source comments point at was silently
dropped. Allow-list it.
Co-authored-by: czzczz <chanzrz_zbf@foxmail.com>
* test(jcode): pin the tool-profile flag in the generation plan too
The argv assertion lives in two places; --tool-profile none only landed in
one, so the plan test still expected the unrestricted argv.
Co-authored-by: czzczz <chanzrz_zbf@foxmail.com>
* fix(text-generation): refuse an oversized argv prompt on Linux too
The pre-spawn size guard only ran on Windows. Linux caps a single argv
entry at MAX_ARG_STRLEN (32 pages, 128 KiB on a 4-KiB-page host) and
execve fails with E2BIG past it, so an agent that delivers the whole
prompt as one argument — jcode, and the other argv-delivery agents —
failed on a large staged diff with an error the user could not act on.
The cap is per-argument and in bytes, which is why it is not the Windows
line budget: 40k chars trips Windows and is nowhere near the Linux limit,
so folding them together would have refused prompts Linux runs fine.
Co-authored-by: czzczz <chanzrz_zbf@foxmail.com>
* fix(jcode): register in main's remote-installer guard, drop our duplicate
Rebasing onto 853 commits of main surfaced two things the earlier branch
had hidden.
main already owns a guard for the issue-#7253 bug class
(`remote-hook-service-registry-coverage.test.ts`). This branch had added a
second, near-identical one — a parallel implementation of a test that
already existed, which is what AGENTS.md's reuse rule is about. Deleted
ours and registered jcode in main's, which is the one that has kept pace
with every agent added since.
Also fixes a missing separator in the mobile icon map. `pnpm tc` does not
cover `mobile/`, so only the session-route closure suite caught it.
Co-authored-by: czzczz <chanzrz_zbf@foxmail.com>
* fix(jcode): decompose the four files jcode pushed over max-lines
Adding an agent tipped four modules past their line budget. AGENTS.md
forbids a `max-lines` disable or a per-file bump, so each is split on a
real seam rather than silenced:
- agent-catalog.tsx keeps `AgentIcon`, which 70+ files import, and the
rows move out. The rows alone exceed the 300-line budget a `.ts` file
gets, so they follow the primary/secondary split this repo already uses
for commit-message agent specs.
- getAgentResumeArgv -> agent-resume-argv.ts, re-exported so the 18 call
sites keep one import path.
- isDiscoverableSessionFile/pathSegments -> session-file-discovery.ts.
- remoteCodexSources -> remote-session-scanner-codex-sources.ts; Codex is
the one remote agent with two CODEX_HOME roots.
Also:
- Records the readiness-census baseline jcode now needs. main added that
gate while this branch was out; the fixture is the recorded 74-case
matrix, not a hand-written one.
- Restores two entries a rebase resolution silently dropped from
config/tsconfig.cli.json (gitlab/project-ref-parser,
startup/shell-path-probe). Nothing to do with jcode; losing them was a
conflict-resolution mistake on this branch.
Co-authored-by: czzczz <chanzrz_zbf@foxmail.com>
* feat(native-chat): open structured chats on the paired Orca server that owns the workspace (#24205)
* fix(native-chat): a host admits structured sessions by client capability, not its own chat setting
A host's experimentalStructuredNativeChat decided whether any paired client could reach
agentSession.* at all, and whether session.tabs.* showed it structured tabs. That setting is the
host user's own launch preference: whether a new agent opens as a chat or a terminal is decided by
whoever launches it. Using it as admission control meant a client whose own preference was
"structured chat" was refused on a host whose preference was "terminal", and chats opened while
the setting was on were withheld from mobile once it was turned off.
The gate now asks one thing: did the client advertise agent-session.structured.v1 (in-process
callers negotiate nothing and are always admitted). Tab projection and restore follow the same
rule. With the setting no longer gating anything, the separate cleanup gate (close, cancel,
unsubscribe, release), which existed only so those kept working after the setting was switched
off, is identical to the main gate and is folded into it. The settings listener that republished
tabs when the setting changed is removed, since projection no longer depends on it.
The host setting still picks the default for launches that start on the host itself
(agent.launch from mobile, orchestration worker-start).
* fix(native-chat): the desktop declares structured chat support to paired hosts
The desktop renderer advertised agent-session.structured.v1 (and the Claude, turn-item and
background-task capabilities that go with it) to its own main process but not to a paired Orca
server. The server therefore refused every agentSession.* call from the desktop and stripped
structured chat tabs out of the tab list it published to it, so a structured chat running on a
paired server never appeared on the desktop, even though the renderer already mirrors a host's
agent-session tabs and drives each one against the server that owns its workspace.
The same renderer reads structured chats on either host, so the remote Electron list now carries
the same structured-session capabilities as the local one, and the capability test pins that
nothing is advertised only locally.
* feat(native-chat): open structured chats on the paired server that owns the workspace
With the structured-chat default on, an agent launched in a workspace that lives on a paired Orca
server always opened as a terminal (or the terminal-backed chat view). Three things kept it off
the structured path: the launch check refused every host but this machine, a remote workspace was
handed to the host-published terminal path before the structured route was even considered, and
the structured launch pipeline sent create and every follow-up call to this machine's runtime.
A workspace's owning runtime is fixed, so the pipeline now derives it from the workspace instead
of assuming this machine (structured-agent-session-owner.ts, the same derivation the chat pane
already uses to read a session). The launch intent carries that target; the pre-create support
check, create, the publication check and fence read, the launch prompt send, held option picks,
the "focus this chat" marker, the placeholder tab's host, and tab close/purge all use it.
The launch check now accepts a paired server and asks that server's own capabilities (read from
the status the client already cached for it) rather than this machine's. An SSH workspace stays
terminal-backed: no Orca runtime runs there. The host still answers createSupport before
anything is created, so an older server that refuses shows the failure in the chat tab.
A chat the user closed before its create landed is now also retired on the paired server when it
publishes, as the local sync already does. Orchestration workers placed on another runtime are
unchanged: federation creates terminal agents only.
* fix(native-chat): negotiate client-chosen launch mode so released phones and old servers keep terminals
Hosts advertise agent-session.structured.client-launch-mode.v1: they admit
structured sessions by client capability alone. A remote client that does
not advertise it (phones released before agent.launch) asks createSupport
to pick the launch mode, so the host keeps answering that with its own
setting, exactly as before. Cleanup methods keep their own named gate so a
future admission condition cannot make close or cancel refusable.
* refactor(runtime): keep the Electron client capability list in its own module
protocol-version.ts is at its line budget; the list is what the desktop
advertises to paired hosts, not the host's own contract.
* fix(native-chat): the desktop declares it picks each launch mode itself
Paired hosts and the desktop's own main process then answer createSupport
by the workspace rather than by their own chat setting.
* fix(native-chat): pin each structured chat to the host it was launched on
- Route: a paired server opens a chat only when it advertises the
client-chosen launch mode; an older server keeps its terminal. Its
capabilities come from the store's host status, not the compatibility
cache that is empty after boot or reconnect.
- A launch command override is this machine's: the route applies it only
locally, and a host's createSupport refuses on its own override.
- The owning host is resolved once, from the same value the route used,
and carried on the launch intent, its persisted record (legacy records
load as local), the provisional tab and every mirrored chat tab. Close,
purge, retry and reload read it instead of re-deriving it from a
worktree id two hosts can share; an owner that cannot be named refuses.
- Cancellation tombstones record their host: only that host's
authoritative inventory retires one, restored cleanup closes it there,
and a paired host's tombstone expires after 30 days if it never answers.
- A paired server's frame settles launches it published, as the local
inventory already does for this machine.
* fix(native-chat): a paired server that declines a chat opens its terminal instead
createSupport only reads, so both of its non-answers are settled before
anything is created:
- A paired server that answers it cannot run the chat (a WSL repo, a
Claude account mismatch, its own launch command override) closes the
chat tab and opens the terminal the route would have chosen, with a
notice saying why. This machine's own decline stays a failed chat.
- A host that could not be asked closes the chat tab and leaves one
failure toast, instead of a lingering "could not confirm" chat.
* test(native-chat): a provisional chat carries its launch's host and hands pre-create failures on
* chore(native-chat): justify the two type assertions this change's lines touch
* test(native-chat): state why each staged test fixture is cast
* fix(native-chat): chats that already exist keep showing whatever the chat setting says
The structured chat setting decides only what new agents open as. With it
off, this machine's structured chats used to be hidden while the host,
which no longer reads the setting, still reported them to the workspace
activation gate, so a workspace holding only a chat opened empty. The
local chat mirror and its startup restore now run whatever the setting
says, the continue-after-restart offer follows the chats that exist, and
the setting's copy says it applies to new agents.
* fix(native-chat): the browser client keeps its host terminal on paired servers
A browser client whose own preferences turn structured chat on took the
structured route for every paired-server workspace, but its handshake
never says it reads structured sessions, so the server refused the chat
and the user got a failed chat tab where a host terminal used to open.
The route for a paired host now also asks what this client advertises to
it: the desktop's list does, the browser client's does not. Its handshake
list is now a named constant the route reads, so the two cannot drift.
The chat setting's copy now says it runs on paired Orca servers too;
WSL and SSH hosts still use terminal chat.
* fix(native-chat): a retried launch a paired server declines opens its terminal too
A launch restored after a reload settles only through its Retry, so a
declining paired server left a failed chat there while a first launch got
the server's terminal and a notice. The chat's Retry now hands the same
pre-create failures to the same replacement, carrying the prompt the
launch had staged.
* refactor(native-chat): a paired host's cancelled-chat record ends on its 30-day TTL
The paired census re-read a host's whole inventory after every
authoritative frame to retire tombstones, and a tombstone restored after
a reload needed a second such frame, so in practice it retired nothing.
A tombstone guards a random session id and is inert once stale; the chat
is already closed on its host whenever a frame shows it. The census, its
trigger in the mirror layer and its cleanup are removed; the owner-scoped
tombstones, close-on-sight, the TTL and publication marking from frames
stay.
* fix(native-chat): a chat's pane and status read from the host recorded on its tab
The chat pane and its sidebar status still derived the host from the
workspace id, which two hosts can share; a paired chat in a non-active
same-id workspace was read from this machine. Both now read the owner
stamped on the tab, as close, purge and publication already do.
* fix(native-chat): "Resume in chat" follows the terminal resume's host rule
Agent Session History offered "Resume in chat" for a conversation
recorded on this machine into a paired server's workspace, where its
transcript does not exist. A chat now resumes a conversation only on the
host that recorded it, as the terminal resume does, and that host is the
one asked whether it can resume history.
* fix(native-chat): the chat setting says older paired servers keep terminal chat
* test(native-chat): pin that a host advertises the client-chosen launch mode
* fix(native-chat): mirror this machine's chats only where it holds them
Round 1 ran the local chat mirror for everyone so existing chats show
whatever the setting says. That gave every desktop a permanent
session-tabs listener, which turns on the runtime's phone replication
paths, plus two full session-tab censuses at startup, and made the
browser client mirror its remote host a second time.
The runtime now says whether it holds structured chats: its structured
host is built only when saved chats were restored at startup or a client
created one here, and it announces the moment one is built. The mirror,
the startup restore and the continue-after-restart offer run only when
the setting launches chats or the host holds some, and never in the
browser client. A chat a paired client creates here with the setting off
still appears at once. The chat behaviour settings show wherever chats
exist, and the setting's copy says it picks what new agents open as. The
toggle-off teardown this made dead is removed.
* test(native-chat): route a paired-server launch over the capability lists both sides really advertise
* test(native-chat): record install listeners without a cast
* fix(native-chat): a paired server admits a chat before any of it exists here
The desktop opened a paired server's chat tab, launch record, queued
prompt and focus intent before asking the server, so a "no" needed a
replacement that undid and redid all of it, and every piece it missed
was a bug: the workspace deselected, the caller told "failed" while a
terminal ran its prompt, the caller's arguments and other queued prompts
lost, and a create whose reply was lost treated as never sent.
A paired launch now asks the server first and commits nothing until it
answers. Admitted opens the chat as before. Declined runs the caller's
own launch as the server's terminal, with the existing notice (a resume
fails instead, having no terminal equivalent). Unreachable opens nothing
and names the server in one toast. The new-tab launcher reports the
host's surface for paired workspaces, as it did before paired chats,
with the prompt delivery of whichever surface got the prompt. The
replacement and its error classes are gone, and the probe inside a
launch is back to its old meaning: a "no" is a failed chat with Retry,
and no answer leaves "Could not confirm" with Retry and the prompt kept,
here as on this machine.
* fix(native-chat): mirror this machine's chats only once it holds one, not once its host is built
Session history, resume preparation, terminal resume commands and replay-safe phone launches all
build the structured host for users who never had a chat, which turned on the chat mirror and the
structured-only settings rows until the next restart. The signal is now derived from the host's
records (or a records file still owed its import) and pushed when the first chat is restored or
created. A throwing listener no longer fails the install that fired it.
* fix(native-chat): a fork's reveal never seeds a terminal beside the surface the launcher opens
Forking into a paired-server workspace revealed it as if nothing would open there, so the reveal
created a blank host terminal beside the forked chat (and beside a forked agent terminal on main).
The launcher always opens the fork's surface itself, so the reveal now says so for every surface,
as the fix-checks launch already does.
* fix(native-chat): a declined direct launch keeps the caller's CLI args; an unreachable resume toasts once
When a paired server declines a "Fix checks" chat in a new workspace, the terminal that opens
instead now carries the recipe's saved CLI arguments, launch platform and launch source, as the
terminal route did. "Resume in chat" to a server that cannot be reached showed the admission's
"Could not reach" toast and the vault's generic one; the admission marks its failure notified and
the vault adds nothing.
* fix(native-chat): a declined background create opens its terminal without switching workspaces
Since #23974 a worktree create the user moved away from must not pull them onto the new
workspace. When a paired server declined that create's chat, the fallback terminal opened as a new
agent tab, whose host create selects the workspace. The create now opens its own agent terminal the
way main's background branch does: in place from the request's startup plan (so its CLI args carry),
without selecting the workspace. A create the user is still watching keeps the new-tab fallback.
* test(native-chat): name the launch's host in main's new outbox fence test
Main's new staging-failure test calls settleStructuredAgentLaunchPrompt without the target this PR
made required; it is a local launch, as in the sibling tests.
* fix(native-chat): a paired server's new chat shows no model until the server reports the one it started
A chat on a paired server starts with the server's saved model and options, but the picker showed
this desktop's saved selection (or the catalog default) until the server reported a model, and a
pick made in that window was remembered on the server under that guessed model. A paired launch
now carries no desktop seed, and until the server reports its model the picker names no model and
takes no picks. Local chats are unchanged.
* test(native-chat): seed the paired repo without a cast
The repo literal already satisfies Repo, so the changed-lines cast gate has nothing to excuse.
* feat(native-chat): createSupport reports the saved selection a new chat on this host starts with
A chat on a paired server starts with the server's saved model and options, which the desktop could
not read, so its picker showed a guess. createSupport's answer, which the desktop already waits for
before a paired launch, now also carries that seed as a new optional field (older clients ignore it).
Create and createSupport read it through one resolver so they cannot drift.
* fix(native-chat): a paired server's new chat shows the selection the server will start it with
The paired server now names its saved model and options in the admission answer the desktop
already waits for. That seed goes into the launch intent and its persisted record, so the picker
shows the server's model at once, stays pickable like a local chat, and remembers picks on the
server under that model; a reload shows the same. The locked picker remains only for a server too
old to name a seed.
Also moves host admission and launch-outcome tracking into their own modules: the latest main
merge left structured-agent-session-launch.ts over the max-lines limit.
* test(native-chat): expect the launch intent's new seed argument in exact-call assertions
* refactor(protocol): move the Electron remote client capability list into its own module
Merging main left protocol-version.ts one line over the max-lines limit on this branch. The list of
capabilities the desktop advertises to a paired host moves, unchanged, into
electron-remote-runtime-client-capabilities.ts, the module the next PR in the stack already uses
for it; importers point there.
* fix(native-chat): a paired chat with no saved server model is pickable; Retry shows the server's current seed
A server whose user never saved a chat model sends no seed, and the desktop showed a locked,
model-only picker for it, although that is the common case: no server that can admit a paired chat
predates the seed field. Such a chat now behaves like a local chat with no saved model: the CLI
default, pickable. The lock and its snapshot helper are gone.
Retry kept the first admission's seed while the create probe, which already runs on every attempt,
reported the server's current one and dropped it. The probe's seed now replaces a paired launch's
seed and the picker's, so a retried chat shows what its create will run.
* test(cross-version): stub the launch seed resolver createSupport now reads
* test(protocol): pin the desktop capability divergence against what a paired server receives
Every paired transport sends the shared remote base plus the Electron list, so the
divergence test now compares that union with the renderer's local list instead of
the declared Electron list. A capability added only to the shared base can no
longer slip past it. The two base-only capabilities it surfaced are recorded:
skills.install-result.v2 has no local caller; the authoritative-inventory label is
read by the local tabs sync but dropped by main, and is marked unsettled.
The turn-item and both background-task-stop capabilities were already sent through
the shared base, so the Electron list no longer repeats them. The wire set is
unchanged; this PR's real change on the wire is structured.v1, the Claude
structured capability and the client launch-mode capability.
* fix(native-chat): the desktop tells its own host it picks each launch mode, so retrying an existing chat works with the setting off
* docs(native-chat): name the real exit for the released-phone createSupport rule
* fix(native-chat): the route reads the capabilities a paired host actually receives
The renderer decided whether a paired host would admit a chat from the desktop's Electron list, but
every desktop transport sends that list plus the shared remote base. They agreed only because the
route's checks happened to sit in both. The route input is now built with the same
remoteRuntimeClientCapabilities the transports use (the browser client already sends its list as is),
and a test pins each against the real handshake.
* test(cross-version): a released client still gets the host-setting createSupport answer; a launch-mode client gets supported plus the seed
* test(native-chat): let main's child-records test resolve each chat's owner
Main's new test mocks worktree-runtime-owner with only the runtime environment id, but the status
projection in this PR also resolves each structured chat's owner from the worktree. The mock keeps
the module's real exports and overrides only what the test pins.
* fix(deps): take #24204's lockfile that the merge reverted
* test(native-chat): let the Codex child-approval e2e unit test resolve each chat's owner
Its worktree-runtime-owner mock exported only the runtime environment id, but this PR's status
projection also resolves each structured chat's owner from the worktree. The mock now keeps the
module's real exports and overrides only that id, as structured-child-records-switch does.
* test(native-chat): move the close-race launch cases into their own file
Merging main added launch tests on both sides and took structured-agent-session-launch.test.ts past
the 800-line limit. The three cases where a tab close races a launch move to
structured-agent-session-launch-close-race.test.ts, with the same setup the other split launch
suites copy.
* feat(cli): add --unread/--read to orca worktree set
Pass validated read/unread flags through the existing workspace metadata update.
Contributor attribution correction: the earlier Ctrl+M squash message placed its related link after the co-author footer, so GitHub did not recognize all contributors. Preserve that credit here for https://github.com/stablyai/orca/pull/24100 and its related contribution https://github.com/stablyai/orca/pull/24143 .
Related CLI link validation retained from https://github.com/stablyai/orca/pull/20036 .
Co-authored-by: Raz Shlomo <1237333…
* fix(ci): wait for the fragment navigation policy report (#25017)
* Add host-owned OpenCode and Devin managed account profiles (#24636)
* Add host-owned OpenCode and Devin account profiles
* Manage OpenCode and Devin profiles in account Settings
* Expose registered account roots to host transcript readers
* Clarify managed profile provider flags
* Retain isolated Electron home in browser sidecars
* Restore inherited account environment and preserve cleanup retries
* Check relay environment values before merging
* Consolidate managed account type imports
* fix(accounts): use existing localized provider names
Align the new Japanese account copy with the existing catalog repair policy.
* Keep managed account baselines private to the execution host
* Align account enrollment help with accepted providers and flags
* Show the active System account in managed profile lists
* Document the validated Linux managed account scope
* Fix managed account removal, credential audits, and runtime bundling
* Quarantine removed accounts and audit captured credential snapshots
* Continue Antigravity IDE and 2.0 history in new CLI conversations (#24692)
* feat(antigravity): bridge IDE history into new CLI conversations
* fix(antigravity): preserve fresh-launch model and environment for IDE references
* fix(antigravity): forward IDE history opt-in through desktop IPC
* fix(antigravity): rebuild remote IDE reference startup on its host
* fix(antigravity): register IDE continuation action labels
* fix(antigravity): confine IDE references and bound metadata reads
* fix(antigravity): localize IDE continuation badges
* Preserve scanner service cache assertions and refresh Antigravity opening metadata
* Preserve Antigravity opening joins and target folder runtime authority
* fix(opencode): retry timed-out SSH plugin updates (#24666)
Preserve bounded retry behavior and the current-main status-envelope fields.
Original-PR: #24124
Reviewed-source: 103144f9c5
Co-authored-by: Justas Brazauskas <brazauskasjustas@gmail.com>
* fix(opencode): keep Go credentials private and resolve backend keys (#24615)
Preserve the complete credential storage, migration, IPC, Settings and rate-limit refresh change alongside standalone GLM plans, current-main database diagnostics and the reviewed unknown-backend environment correction. Keep native discovery cancellation third and selected environment fourth.
Original-topic-commit: 7903f1cddb
Original-topic-commit: 588117b5cf
Original-topic-commit: dedd4f8c86
Original-topic-commit: 6645dae104
Original-topic-commit: a25b80c02c1af7830b0e6a65e72d965b3ad98276
Restacked-from: a25b80c02c1af7830b0e6a65e72d965b3ad98276
Restacked-onto: b032867021
Co-authored-by: kespineira <kespineira@users.noreply.github.com>
Co-authored-by: kevimux <kevimux@users.noreply.github.com>
Reported-by: pullfrog
Reviewed-full-source: 849fe093073f4c1606bd65d79a0c725d955d0d1f
Native-helper-source: 80dbe23237
Reviewed-full-current-source: 36acb57d44adb3d378c0289c8c15f7da0fda214c
* Skip store notifications when refreshed work items are unchanged (#24530)
An identical forced refresh previously returned an empty Zustand patch, creating a new root state and notifying every subscriber. It now returns the current state after renewing the existing freshness timestamp. Real row or metadata changes still publish once.
* Read project activity once while sorting the sidebar (#24549)
Reuse the existing pure recent rank once per actual entry tuple within each synchronous sort; discard the lookup after sorting.
* Skip impossible link and tag matches in docs search results (#24646)
* Skip impossible HTML matches when formatting docs search results
After the existing link-removal phase, skip the unchanged HTML regex only when the current string has no closing delimiter.
* Skip impossible link and tag matches in docs search results
Skip the five unchanged link regexes when their protected-code input lacks ]; skip the unchanged HTML regex when its post-link input lacks >.
* Decode complete transcript lines without copying their bytes (#24546)
Decode each single owned Buffer synchronously; preserve concatenation for multipart records and remove a redundant tail array copy.
* Clear unused speech worker timers after errors and exit (#24924)
Call the existing idle timer cleanup when private current-worker state is cleared; keep current-worker guards, transcript/error order, deliberate warmth and stop deadline unchanged.
* Reuse documentation search excerpts during result navigation (#24574)
Memoize the existing pure excerpt renderer by complete raw text for the current result array, retaining original rendering as a miss fallback.
* Append subagent handoffs without recopying their message list (#24672)
Append each message reference to its private call-local scope list; retain the final merge and existing ordering.
* Cancel plugin retry timers when their fetch is replaced (#24700)
Reuse the existing timer clear block immediately after the fetch generation advances; preserve live retries and all state publications.
* Avoid rebuilding visible file paths for single-row selection (#24743)
Skip the visible path array only for keyboard replacement selection; the existing replacement branch never reads that order.
* Reuse prepared reads while searching saved agent conversations (#24535)
Use schema-complete session columns to enable the existing bounded statement cache without caching result rows.
* Reuse discovered mobile chunks during static traversal (#24774)
Iterate the existing live visited Set in FIFO discovery order instead of maintaining a second shifted queue; both actual callers own untouched esbuild JSON metadata.
* Skip unused image-size calculations in mobile web browser requests (#24941)
Use the existing mobile density budget only for mobile view; pass the existing constant to the existing assembler for web/default mode, where that argument is discarded. No cache, policy, request or native path changes.
* Skip diff analysis after a result has been discarded (#24711)
Move the unchanged pure render-limit calculation after both existing generation and section-token rejection fences.
* Skip late browser grab toasts after their surface closes (#24727)
Reuse the existing mounted-owner ref at the toast presentation entry while preserving all admitted extraction/screenshot and clipboard completion.
* Classify native chat waiting messages once (#24771)
Use the existing native filter traversal to populate a fresh waiting array, keeping the original predicate, policy, projection and output ordering while classifying each actual private slot once.
* Skip discarded Kanban pruning for an empty selection (#24782)
Guard only the existing open pruning branch when both its private selected Set and nullable anchor are empty; retain all original pre-guard projections and nonempty/anchor-only/public-helper behavior.
* Index discovered test files once during shard selection validation (#24532)
Verified selection plans previously validated each selected filename with a scan of all discovered files. One per-call Set now handles membership checks. Exact source/discovery checks, nonempty selection, fallback to all tests, shard balancing and manifests remain intact.
* Clear the watchdog benchmark heartbeat when CPU sampling fails (#24802)
Move the existing initial CPU observation and histogram enable inside the existing try/finally so their failures clear the sampler's owned heartbeat, preserving successful operation order and original errors.
* Reuse the parsed notification when leasing a push delivery (#24645)
* Reuse the parsed notification when leasing a push delivery
Pass the notification already parsed for the dismissal check into the existing private delivery builder.
* Reuse the parsed notification when leasing a push delivery
Pass the notification already parsed for the dismissal check into the existing private delivery builder.
* fix(accounts): canonicalize account removal to rm
Use account rm as the canonical removal command and keep account remove as an alias. Preserve the existing accounts.removeData RPC and the full original 63-path account component.
Original-Account-Source: cf71ae4cb6
Frozen-Account-Base: 08ee7ba9ef
Frozen-Integrated-Main: 53f9ea7839
Private validation source only; no ref or publication.
* Update README downloads badge
* Let retired-cache GC observations settle across the existing six-turn budget (#24967)
* Collect test-selection evidence when full unit tests fail (#24955)
* Collect advisory unit-selection evidence from failed full runs
* Trigger checks after retargeting the evidence fix to main
* Check each project repository once while filtering mobile cards (#24540)
Reuse exact raw source/slug matching decisions within one project filter call, preserving the matcher, membership, negative matches, output order and identity.
* Skip renderer callbacks after their request has ended (#24631)
Reuse the existing pending-request identity check before starting its deferred renderer callback.
* Decode single-piece saved-session tails without copying them (#24632)
Decode the existing owned Buffer directly when the EOF tail has one piece; preserve the existing concatenation for multiple pieces.
* Avoid irrelevant scroll-action searches in Mac snapshots (#24731)
Check the current pure action name before searching for vertical scroll actions in the same immutable array.
* Stop copying every retained browser page before attachment (#24553)
Find the first page/generation match directly in the existing insertion-ordered Map instead of copying all page references into an array.
* Reuse event byte sizes when relay watchers notify several clients (#24558)
Store the existing grouped numeric byte-size array on the already call-local watcher batch sizing object, for reuse by later chunking clients.
* Stop building unused import candidates in the test planner (#24611)
Replace the existing ordered extension map/find with a loop that forms each candidate only when its existing lookup is reached.
* docs(terminal): explain local macOS/Linux shell startup files
Document the actual login/interactive shell startup-file order and existing shell setup behavior.
Co-authored-by: brynnclaw <261708852+brynnclaw@users.noreply.github.com>
Co-authored-by: Neil <neil@stably.ai>
* fix(editor): recognize Ruby task and configuration files
Add Ruby task/configuration filenames to the existing generated language associations.
Co-authored-by: ggbdpq <ggbdpq@gmail.com>
Co-authored-by: Neil <neil@stably.ai>
* Speed up large Markdown Find and render oversized tables (#24948)
* Speed up large Markdown Find and render oversized tables
* Poll table preview geometry outside hidden renderers
* Preserve Markdown navigation through refresh and tab restoration
* Confirm Markdown restoration when refreshed content is ready
* Trace table refresh positions and update Unicode search reference
* Recognize queued measurement scrolls before restoring Markdown anchors
* Rebuild Markdown Find ranges after renderer components change
* fix(source-control): generate clean OpenCode messages locally and over SSH (#24613)
* fix(source-control): generate clean OpenCode answers on local and SSH hosts
Restack the original focused change onto current main, preserving every owned source and test blob and the merged CI contract and journal cleanup fixes.
Original-commit: 64bb15e3f2
fix(source-control): generate clean OpenCode answers on local and SSH hosts
Use configured models and JSON answer/error events, preserve run-first arguments, and handle the precise v2 variant rejection. Hydrate SSH execution-host PATH through the existing bounded login environment resolver before direct spawning.
Credits: andy-murr (PR #5197 SSH environment intent) and coelho-doti (PR #13065 argument-order intent).
Original-commit: 1b60ec5d11
fix(source-control): retry inline OpenCode model and variant options
Original-commit: 98fdecc7a3
Preserve OpenCode named errors without a data message
Restacked-from: 98fdecc7a3
Restacked-onto: f7b1f9d8be
* fix(ci): prevent concurrent pnpm refresh during mobile typechecks (#24776)
* fix(ci): run mobile typechecks without concurrent dependency refresh
* test(ci): check effective Linux E2E package list
* test(ci): preserve the mobile production compiler barrier
---------
Co-authored-by: Orca Integration Recovery <orca-validation@invalid.example>
* test(terminal): restore the live fish fixture prerequisites (#24947)
A restored pane waits for the initial status replay before subscribing to
PTY output. This fixture never settled that replay, so fish printed its
mode-2031 arm before the renderer connected. Its PTY API also omitted the
reset-input listener required by the serializer, aborting attachment.
Settle and dispose the existing startup-snapshot registration and provide
the same reset-listener mock used by the other PTY tests. The real fish
child-stdin assertions and timeouts remain unchanged. No production change.
* fix(shortcuts): defer TUI editing chords in terminal-first mode (#24640)
Restack the original focused change onto current main, preserving every owned source and test blob and the merged CI contract and journal cleanup fixes.
Original-commit: f707cde14a
fix(shortcuts): defer TUI editing chords in terminal-first mode
Original-commit: 0c6348e49e
docs(shortcuts): describe deferred preview terminal chords
Original-commit: be62c1b6c6
Align worktree history shortcut metadata with terminal conflict policy
Restacked-from: be62c1b6c6
Restacked-onto: f7b1f9d8be
* Register supervised Qoder China and Qwen Code (#24616)
* Add Qoder session history and search with real CLI coverage
* Allow the real Qoder marker file to end with a newline
* Keep Qoder tool output out of history previews and search
* Keep Qoder search pages readable by older clients
* Verify persisted Qoder history after a real generated and resumed task
* Negotiate Qoder filters before searching an older execution host
* Combine search client imports for the CI plugin gate
* Keep the relay search oracle aligned with legacy agent filtering
* Register supervised Qoder China and Qwen lifecycle integration
* Cover Qoder China mobile assets and mixed-host resume gates
* Verify Qoder provider tags against the older released wire parser
* Verify China and Qwen keep independent Windows hook scripts
* Verify Qoder registrations against the installed older Windows release
* test(qoder): align search capability contracts and pin old-host fencing
* fix(qoder): rank exact picker identities and command aliases first
* test(qoder): preserve the regional CLI shared icon expectation
Keep the full bundled-asset and no-remote-image checks, with an explicit
shared-logo basename for Qoder China. The map also works with older
catalog type unions.
* fix(qoder): align China catalog entry with fallback order
---------
Co-authored-by: Orca Integration Recovery <orca-validation@invalid.example>
* Use the measured pnpm lookup policy automatically in hosted root CI (#24951)
* Select lookup automatically for the measured hosted root-install profile
* Record hosted automatic-mode cold cache publication proof
* fix: bound remote generation setup and honor OpenCode option terminators
Count execution-host profile resolution inside the existing request deadline, cancel its waiter promptly, and pass only the remaining time to the child. Shared bounded profile probes keep their existing cache lifetime; the SSH transport margin is unchanged.
Read OpenCode output format from active final-argv options before -- so literal prompt arguments cannot select the JSON finalizer.
Fresh exact-source controls reproduce nine failures before; 139 related checks pass after, including primary/fallback delays, deadline boundaries, cancellation, parser metadata, and SSH lanes. Node typecheck and strict changed-file lint pass.
* Continue Antigravity IDE and 2.0 history in new CLI conversations (#24692)
* feat(antigravity): bridge IDE history into new CLI conversations
* fix(antigravity): preserve fresh-launch model and environment for IDE references
* fix(antigravity): forward IDE history opt-in through desktop IPC
* fix(antigravity): rebuild remote IDE reference startup on its host
* fix(antigravity): register IDE continuation action labels
* fix(antigravity): confine IDE references and bound metadata reads
* fix(antigravity): localize IDE continuation badges
* Preserve scanner service cache assertions and refresh Antigravity opening metadata
* Preserve Antigravity opening joins and target folder runtime authority
* fix(opencode): retry timed-out SSH plugin updates (#24666)
Preserve bounded retry behavior and the current-main status-envelope fields.
Original-PR: #24124
Reviewed-source: 103144f9c5
Co-authored-by: Justas Brazauskas <brazauskasjustas@gmail.com>
* fix(opencode): keep Go credentials private and resolve backend keys (#24615)
Preserve the complete credential storage, migration, IPC, Settings and rate-limit refresh change alongside standalone GLM plans, current-main database diagnostics and the reviewed unknown-backend environment correction. Keep native discovery cancellation third and selected environment fourth.
Original-topic-commit: 7903f1cddb
Original-topic-commit: 588117b5cf
Original-topic-commit: dedd4f8c86
Original-topic-commit: 6645dae104
Original-topic-commit: a25b80c02c1af7830b0e6a65e72d965b3ad98276
Restacked-from: a25b80c02c1af7830b0e6a65e72d965b3ad98276
Restacked-onto: b032867021
Co-authored-by: kespineira <kespineira@users.noreply.github.com>
Co-authored-by: kevimux <kevimux@users.noreply.github.com>
Reported-by: pullfrog
Reviewed-full-source: 849fe093073f4c1606bd65d79a0c725d955d0d1f
Native-helper-source: 80dbe23237
Reviewed-full-current-source: 36acb57d44adb3d378c0289c8c15f7da0fda214c
* fix(opencode): enforce deadline through executable startup
Keep synchronous Windows PATH resolution and child startup within the existing request budget.
---------
Co-authored-by: Orca Integration Recovery <orca-validation@invalid.example>
Co-authored-by: Justas Brazauskas <brazauskasjustas@gmail.com>
* fix(accounts): recover interrupted profile removal on startup
Scan private removal backups before quarantined cleanup, reuse the existing state schema to validate the exact UUID, and preserve registered or unmarked profiles. Mark account rm as destructive using the existing typo recovery policy. Retain the full original account component and public author ancestry.
Account-PR: 24636
Original-Account-Source: cf71ae4cb6
Reviewed-Public-Parent: 6abaf736e3
Frozen-Integrated-Main: 53f9ea7839
Private source only; no ref or publication.
* fix(persistence): preserve projectGroupOrder on restart for flat folder-scan groups
Retain saved project ranks when the project remains in its folder-scan group.
Co-authored-by: lurunzi <lurunzi@gmail.com>
Co-authored-by: Neil <neil@stably.ai>
* fix(runtime): reclaim temp files orphaned by interrupted mobile store writes
Reuse the existing stale temporary-file cleanup policy when each mobile store opens.
Co-authored-by: LDH1103 <ldh517525@gmail.com>
Co-authored-by: Neil <neil@stably.ai>
* fix(terminal): show actionable copy for missing folder workspace paths
Show the existing folder-path failure with concrete recovery instructions.
Co-authored-by: Wayn_Liu <wayntingliu@gmail.com>
Co-authored-by: Neil <neil@stably.ai>
* docs: reference local orca.yaml and .worktreeinclude
Document the existing workspace configuration, include/copy rules and sharing behavior.
Co-authored-by: Neil <neil@stably.ai>
* fix(orchestration): allow model selection for OMP workers
Pass an explicitly requested model through the existing OMP worker launch catalog.
Co-authored-by: Neil <neil@stably.ai>
* fix(cli): preserve primitive success results in JSON output
Check for an object before inspecting screenshot fields so primitive success results remain printable.
Related: https://github.com/stablyai/orca/pull/14735
Co-authored-by: Neil <neil@stably.ai>
Co-authored-by: VXNCXNX <VXNCXNX@users.noreply.github.com>
* Show loaded file changes before deleting a workspace
Expose up to ten already loaded changed paths without altering deletion counts, hydration or authorization.
Related: https://github.com/stablyai/orca/pull/22778
Co-authored-by: Neil <neil@stably.ai>
Co-authored-by: Michiel de Gooijer <mdgooijer@gmail.com>
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix(keybindings): record and match Option+digit shortcuts on macOS
Use the physical digit key for explicitly assigned Mac Option+digit shortcuts while retaining modifier checks.
Co-authored-by: marcuslannister <marcus@lannister.cc>
Co-authored-by: Neil <neil@stably.ai>
* fix(editor): don't throw closing a stale active file
Recover stale active-file selection within the owning workspace before choosing a surviving editor.
Related: https://github.com/stablyai/orca/pull/24715
Co-authored-by: m4air <m4air@m4airs-Air.localdomain>
Co-authored-by: Neil <neil@stably.ai>
* Fix Project Settings targeting for multiple local checkouts
Carry the selected checkout identity into the existing project Settings action.
Co-authored-by: fsmeier <1506919+fsmeier@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: Neil <neil@stably.ai>
* feat(documents): open CSV and TSV files from the OS
Extend existing OS document associations and delivery to CSV/TSV, preserving restoration and authorization.
Co-authored-by: Neil <neil@stably.ai>
* feat(cli): link GitHub and GitLab items on worktree create and set
Expose and validate the existing workspace link fields, preserving omitted values and provider identity checks.
Related: https://github.com/stablyai/orca/pull/22609
Co-authored-by: marco song <marco.song@mvlchain.io>
Co-authored-by: SongMarco <20613630+SongMarco@users.noreply.github.com>
Co-authored-by: Luca Critelli <lucacri@gmail.com>
Co-authored-by: Neil <neil@stably.ai>
* feat(sidebar): include folder workspaces in keyboard navigation
Use the rendered sidebar row order and host identity when cycling through folder and Git workspaces.
Related: https://github.com/stablyai/orca/pull/10555
Co-authored-by: Neil <neil@stably.ai>
Co-authored-by: JeongUk Park <jeongph.dev@gmail.com>
* fix(build): turn off MSBuild file tracking for Windows native rebuilds
Default Windows native rebuilds to TrackFileAccess=false while preserving explicit caller preferences.
Co-authored-by: B1nh M1nh <43268322+b1nhm1nh@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: Neil <neil@stably.ai>
* Fix Ctrl+M in Linux and Windows terminals
Keep the Minimize menu item but disable its accelerator registration on Linux and Windows.
Co-authored-by: Zhichang Yu <yuzhichang@gmail.com>
Co-authored-by: Neil <neil@stably.ai>
Co-authored-by: Ahmed Nagy <ahmednagy25t@gmail.com>
Related contribution: https://github.com/stablyai/orca/pull/24143
* fix(startup): read a nushell login PATH from $env.PATH
Use Nushell login command syntax and preserve the actual login PATH value.
Related: https://github.com/stablyai/orca/pull/22677
Co-authored-by: Kh05ifr4nD <meandSSH0219@gmail.com>
Co-authored-by: Neil <neil@stably.ai>
* fix(cursor): run local hooks through sh for non-POSIX login shells
Keep POSIX hook syntax inside one quoted sh command so the login shell can invoke it safely.
Related: https://github.com/stablyai/orca/pull/22663
Co-authored-by: Kh05ifr4nD <meandSSH0219@gmail.com>
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Neil <neil@stably.ai>
* Fix word wrap for both panes in side-by-side diffs
Forward wrapping to both diff panes through the existing editor option path and clean up listeners.
Co-authored-by: Wooseong Kim <innocarpe@gmail.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Neil <neil@stably.ai>
* fix(monaco): highlight Svelte block closers inside markup
Return Svelte block closers to the existing markup tokenizer state.
Co-authored-by: Wooseong Kim <innocarpe@gmail.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Neil <neil@stably.ai>
* fix(repos): keep active clone dialog open on outside clicks
Ignore accidental outside dismissal only while cloning; Escape, Close and Back still cancel.
Related: https://github.com/stablyai/orca/pull/24581, https://github.com/stablyai/orca/pull/23430
Co-authored-by: Neil <neil@stably.ai>
Co-authored-by: Nawapat Buakoet <nawapat.b@covest.finance>
* fix(editor): keep chat visible after closing Markdown tabs
Count remaining unified chat tabs before clearing workspace selection during editor close.
Related: https://github.com/stablyai/orca/pull/24273, https://github.com/stablyai/orca/pull/23760
Co-authored-by: Neil <neil@stably.ai>
Co-authored-by: Wooseong Kim <innocarpe@gmail.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
* Honor typed starting numbers in chat ordered lists
Forward the existing parsed ordered-list start attribute to both chat Markdown renderers.
Related: https://github.com/stablyai/orca/pull/19765
Co-authored-by: Neil <neil@stably.ai>
Co-authored-by: Frederic Barthelemy <git@fbartho.com>
* Normalize base source once while checking changed-code diagnostics (#24557)
Reuse the exact existing moved-code matching algorithm with run-local lazy normalization of immutable base source blocks across diagnostics.
* Support real OpenCode sessions in native Chat (#24647)
* Use bounded OpenCode context for vault session continuation
OpenCode database and synthetic row paths are not text transcripts. Use the
vault preview or captured pane context, preserving actual transcript paths
containing a hash and supporting both OpenCode lanes and Windows paths.
Adapted the intent of #11859 and extended it to actual installed v2 vault rows.
Co-authored-by: mrcha033 <mrcha033@users.noreply.github.com>
* Read real OpenCode sessions in terminal-backed native Chat
Reuse the bounded AI Vault SQLite worker for v1 and v2 session pages and live updates. Keep terminal input as the real execution path and pace OpenCode Stop through its two-Escape interrupt.
Co-authored-by: xodmd45-ctrl <xodmd45-ctrl@users.noreply.github.com>
* fix(opencode): publish approval cards for permission requests
* Send OpenCode native approval through its Enter selector
* Resolve mobile Chat readability for folder workspaces
* Bound OpenCode part batches and preserve v2 image attachments
* Prefer live migrated OpenCode sessions over legacy copies
* Consolidate mobile Chat eligibility test imports
* Consolidate OpenCode SQLite protocol type imports
* Update native chat settings contract for both OpenCode agents
* fix(native-chat): reconcile bounded OpenCode transcript reads
* fix(native-chat): dispatch OpenCode questions safely
* fix(native-chat): keep native discovery and transcript windows current
* feat(accounts): link standalone GLM Coding Plans (#24618)
* feat(accounts): link standalone GLM Coding Plans
Adapt the reviewed GLM accounts contribution to current main, retain Antigravity behavior, guard late credential results, expose storage protection, and redact quota errors.
Co-authored-by: Luchong <lu740528977@gmail.com>
* fix(accounts): retain GLM credential results during quota refresh
* fix(accounts): make GLM credential editing desktop-only
* fix(accounts): mirror the host GLM site in paired clients
* fix(accounts): report unknown GLM host details and split web settings tests
Apply the independently reviewed Accounts correction from697284a without the v2 adapter commits. Preserve the saved-key store and serialized write behavior.
* fix(zcode): ship required GLM account translation entries
* chore: record GLM reconciliation hook validation
* chore: validate installed GLM commit hooks
* test: complete GLM account fixtures and web API inventory
---------
Co-authored-by: Luchong <lu740528977@gmail.com>
* fix(native-chat): route transcript requests through shared SQLite worker
* fix(ci): prevent concurrent pnpm refresh during mobile typechecks (#24776)
* fix(ci): run mobile typechecks without concurrent dependency refresh
* test(ci): check effective Linux E2E package list
* test(ci): preserve the mobile production compiler barrier
---------
Co-authored-by: Orca Integration Recovery <orca-validation@invalid.example>
* test(terminal): restore the live fish fixture prerequisites (#24947)
A restored pane waits for the initial status replay before subscribing to
PTY output. This fixture never settled that replay, so fish printed its
mode-2031 arm before the renderer connected. Its PTY API also omitted the
reset-input listener required by the serializer, aborting attachment.
Settle and dispose the existing startup-snapshot registration and provide
the same reset-listener mock used by the other PTY tests. The real fish
child-stdin assertions and timeouts remain unchanged. No production change.
* fix(shortcuts): defer TUI editing chords in terminal-first mode (#24640)
Restack the original focused change onto current main, preserving every owned source and test blob and the merged CI contract and journal cleanup fixes.
Original-commit: f707cde14a
fix(shortcuts): defer TUI editing chords in terminal-first mode
Original-commit: 0c6348e49e
docs(shortcuts): describe deferred preview terminal chords
Original-commit: be62c1b6c6
Align worktree history shortcut metadata with terminal conflict policy
Restacked-from: be62c1b6c6
Restacked-onto: f7b1f9d8be
* Register supervised Qoder China and Qwen Code (#24616)
* Add Qoder session history and search with real CLI coverage
* Allow the real Qoder marker file to end with a newline
* Keep Qoder tool output out of history previews and search
* Keep Qoder search pages readable by older clients
* Verify persisted Qoder history after a real generated and resumed task
* Negotiate Qoder filters before searching an older execution host
* Combine search client imports for the CI plugin gate
* Keep the relay search oracle aligned with legacy agent filtering
* Register supervised Qoder China and Qwen lifecycle integration
* Cover Qoder China mobile assets and mixed-host resume gates
* Verify Qoder provider tags against the older released wire parser
* Verify China and Qwen keep independent Windows hook scripts
* Verify Qoder registrations against the installed older Windows release
* test(qoder): align search capability contracts and pin old-host fencing
* fix(qoder): rank exact picker identities and command aliases first
* test(qoder): preserve the regional CLI shared icon expectation
Keep the full bundled-asset and no-remote-image checks, with an explicit
shared-logo basename for Qoder China. The map also works with older
catalog type unions.
* fix(qoder): align China catalog entry with fallback order
---------
Co-authored-by: Orca Integration Recovery <orca-validation@invalid.example>
* Use the measured pnpm lookup policy automatically in hosted root CI (#24951)
* Select lookup automatically for the measured hosted root-install profile
* Record hosted automatic-mode cold cache publication proof
* fix(native-chat): keep OpenCode history usable at read limits
Continue past failed database probes while preserving discovery cancellation.
Verify rows displaced by a capped tail before deciding whether to replace history.
Represent oversized v1/v2 rows with the existing omission text and stable cursors.
* Continue Antigravity IDE and 2.0 history in new CLI conversations (#24692)
* feat(antigravity): bridge IDE history into new CLI conversations
* fix(antigravity): preserve fresh-launch model and environment for IDE references
* fix(antigravity): forward IDE history opt-in through desktop IPC
* fix(antigravity): rebuild remote IDE reference startup on its host
* fix(antigravity): register IDE continuation action labels
* fix(antigravity): confine IDE references and bound metadata reads
* fix(antigravity): localize IDE continuation badges
* Preserve scanner service cache assertions and refresh Antigravity opening metadata
* Preserve Antigravity opening joins and target folder runtime authority
* fix(opencode): retry timed-out SSH plugin updates (#24666)
Preserve bounded retry behavior and the current-main status-envelope fields.
Original-PR: #24124
Reviewed-source: 103144f9c5
Co-authored-by: Justas Brazauskas <brazauskasjustas@gmail.com>
* fix(opencode): keep Go credentials private and resolve backend keys (#24615)
Preserve the complete credential storage, migration, IPC, Settings and rate-limit refresh change alongside standalone GLM plans, current-main database diagnostics and the reviewed unknown-backend environment correction. Keep native discovery cancellation third and selected environment fourth.
Original-topic-commit: 7903f1cddb
Original-topic-commit: 588117b5cf
Original-topic-commit: dedd4f8c86
Original-topic-commit: 6645dae104
Original-topic-commit: a25b80c02c1af7830b0e6a65e72d965b3ad98276
Restacked-from: a25b80c02c1af7830b0e6a65e72d965b3ad98276
Restacked-onto: b032867021
Co-authored-by: kespineira <kespineira@users.noreply.github.com>
Co-authored-by: kevimux <kevimux@users.noreply.github.com>
Reported-by: pullfrog
Reviewed-full-source: 849fe093073f4c1606bd65d79a0c725d955d0d1f
Native-helper-source: 80dbe23237
Reviewed-full-current-source: 36acb57d44adb3d378c0289c8c15f7da0fda214c
---------
Co-authored-by: mrcha033 <mrcha033@users.noreply.github.com>
Co-authored-by: xodmd45-ctrl <xodmd45-ctrl@users.noreply.github.com>
Co-authored-by: Luchong <lu740528977@gmail.com>
Co-authored-by: Orca Integration Recovery <orca-validation@invalid.example>
Co-authored-by: Justas Brazauskas <brazauskasjustas@gmail.com>
* fix(accounts): protect registered UUID case variants during removal recovery
Compare validated account UUID identities without changing stored IDs or filesystem paths. Protect registered originals and quarantines, including explicit retry variants, and accept equivalent UUID spelling in a valid removal backup. Retain the complete account component and published contributor ancestry.
Private source only; no index, ref, or public mutation.
* Drain removal fixture jobs before resetting and deleting their records (#24977)
* Reuse measured Electron preparation for current Terminal Perf refs (#24968)
* feat(jcode): add Jcode as a supported TUI agent with managed hooks
Ports PR #10521 onto current main: agent catalog, managed hook service,
agent-status listener, session resume, AI Vault parser, per-pane daemon
isolation, and Source Control AI support.
Co-authored-by: Neil <neil@stably.ai>
* feat(jcode): evidence-backed status pipeline (turn_start, live pre_tool, questions)
Co-authored-by: czzczz <chanzrz_zbf@foxmail.com>
* fix(jcode): register vault fixture, dynamic model discovery, script refresher
Co-authored-by: czzczz <chanzrz_zbf@foxmail.com>
* fix(jcode): keep finished-turn detail so completion notifications fire
Co-authored-by: czzczz <chanzrz_zbf@foxmail.com>
* fix(jcode): show the prompt in agent rows instead of jcode's repainting title
Co-authored-by: czzczz <chanzrz_zbf@foxmail.com>
* fix(jcode): pre-warm the per-pane daemon so a cold runtime dir cannot time out
Co-authored-by: czzczz <chanzrz_zbf@foxmail.com>
* fix(jcode): stop the OSC color skip from crashing every pane connect
Co-authored-by: czzczz <chanzrz_zbf@foxmail.com>
* refactor(jcode): fold three reverse-scan copies into one, reuse the shared hook POST
The jcode journal reader, the Claude transcript reader and the Command Code
transcript reader each carried their own copy of the same reverse chunked line
scan; they now share one tested helper. jcode's managed hook script drops its
hand-rolled curl for buildPosixAgentHookPostCommand, which also gains it the
raw-JSON transport and the --noproxy guard the bespoke copy was missing.
Co-authored-by: czzczz <chanzrz_zbf@foxmail.com>
* fix(jcode): narrow dynamic reads with predicates, pin the Windows hook shape
CI's anti-slop audit rejects Reflect.get: parse dynamic input into a named type
instead. Adds Windows script-shape tests too, since Windows is the platform this
change could not be exercised on directly.
Co-authored-by: czzczz <chanzrz_zbf@foxmail.com>
* test(jcode): measure the gate against the synchronous path, not the clock
The absolute 1s bound was the flake CI shard 3/8 hit: it is tight enough to catch
a synchronous gate on an idle laptop and too tight on a loaded runner. Measuring
the same POST both ways on the same machine makes the claim a ratio, which is what
the test is actually about. Mutation-checked: a synchronous gate reads 6120ms
against a 1517ms observer.
Co-authored-by: czzczz <chanzrz_zbf@foxmail.com>
* test(jcode): drop the wall-clock gate assertion
The bound was machine-speed sensitive and flaked on CI shard 3/8 at 1525ms. The
structural assertions (detached gate branch, foreground observer branch) and the
no-hang stdin drain cover the same contract without a timer.
Co-authored-by: czzczz <chanzrz_zbf@foxmail.com>
* fix(jcode): address CodeRabbit review on #22539
- Windows posted no payload at all: the shared builder reads `payload@-` from
stdin, which the gate has already drained and observer hooks never receive, so
every Windows event was dropped. Write the env var to a temp file and pipe it.
- The daemon pre-warm never fired for daemon-host spawns, which is the default
local path; it now runs there too, and from the final env so the daemon gets the
hook port and token.
- A failed runtime-dir mkdir took down every local terminal, jcode or not.
- removeJcodeManagedHooks matched the raw line, so a user hook whose comment
mentioned the managed script was deleted; matching on Windows never worked.
- A managed entry left by a copied home or a platform switch is now repointed
instead of being reported as user-owned forever.
- The OSC colour skip only checked launchAgent, so a command- or telemetry-named
jcode pane still leaked the reply into its composer.
- `['hooks']` and a commented scalar are recognised, instead of appending a
second [hooks] table that makes jcode reject the whole config.
- A failed tool's error is marked as tool output rather than agent prose.
- The vault keeps a session's stored name, counts its tokens, and skips
background_task and [Scheduled task] turns.
Co-authored-by: czzczz <chanzrz_zbf@foxmail.com>
* fix(jcode): read the journal once per turn, key prompts by byte offset
The jcode prompt reader ran a synchronous bounded file scan plus a JSON
parse on every hook event. jcode blocks on pre_tool, so a turn that ran
four tools charged the user eight scans of latency it did not need — the
prompt cannot change inside a turn.
- Cache the journal read per pane, refreshed on the turn boundary that
can change it. The cache holds the whole evidence record, since
hasExplicitUserPrompt needs the transcript-evidence flag and not just
the text, and it joins the existing pane-scoped lifecycle (close,
rename, reset) rather than living in a module singleton.
- Key a journal prompt by its absolute byte offset instead of its
region-local line index. The backward scan windows the file from EOF,
so appending shifted every boundary and reminted the key for a prompt
that never moved; a repeated turn_end then slipped past the same-hash
dedupe as a second done event with duplicate telemetry.
- Pin the platform in the daemon pre-warm tests. The pre-warm is a no-op
off POSIX, so the dedupe and retry cases would have passed vacuously
on a Windows runner.
Co-authored-by: czzczz <chanzrz_zbf@foxmail.com>
* fix(jcode): quote the managed hook path, drop tools from patch prompts
Three fixes, all on paths this PR could not exercise locally.
The managed hook command was stored as a bare path. jcode tokenizes that
string shell-style before exec'ing it directly (parse_hook_command,
crates/jcode-terminal-launch/src/lib.rs): unquoted whitespace splits, and
every unquoted backslash is consumed as an escape. So on Windows
`C:\Users\me\.orca\agent-hooks\jcode-hook.cmd` reached exec as
`C:Usersme.orcaagent-hooksjcode-hook.cmd` and no hook fired at all, and a
POSIX home with a space split into two arguments. Store the path
single-quoted (verbatim, backslashes included), falling back to double
quotes for a path containing a single quote. Existing bare entries are
already repointed by the stale-key path, and getStatus accepts both forms
so the repair is not reported as a user-owned hook. The quoting helper was
previously dead code that only tests called; the three production sites
now use it. isJcodeManagedCommand also normalizes separators, since a
`/`-only needle never matched a Windows entry.
Commit-message generation feeds a staged patch to `jcode run` as the
prompt — attacker-influenced text — while jcode's default profile exposes
shell, read, write, and MCP. Pass `--tool-profile none`, which resolves to
an empty allowed-tool set in jcode's config (base_allowed_tools), matching
the read-only posture claude (plan) and codex (read-only) already take.
docs/reference/jcode-hook-events.md was never actually in this PR: the
repo ignores docs/** and tracks reference docs by allow-list only, so the
captured-payload evidence four source comments point at was silently
dropped. Allow-list it.
Co-authored-by: czzczz <chanzrz_zbf@foxmail.com>
* test(jcode): pin the tool-profile flag in the generation plan too
The argv assertion lives in two places; --tool-profile none only landed in
one, so the plan test still expected the unrestricted argv.
Co-authored-by: czzczz <chanzrz_zbf@foxmail.com>
* fix(text-generation): refuse an oversized argv prompt on Linux too
The pre-spawn size guard only ran on Windows. Linux caps a single argv
entry at MAX_ARG_STRLEN (32 pages, 128 KiB on a 4-KiB-page host) and
execve fails with E2BIG past it, so an agent that delivers the whole
prompt as one argument — jcode, and the other argv-delivery agents —
failed on a large staged diff with an error the user could not act on.
The cap is per-argument and in bytes, which is why it is not the Windows
line budget: 40k chars trips Windows and is nowhere near the Linux limit,
so folding them together would have refused prompts Linux runs fine.
Co-authored-by: czzczz <chanzrz_zbf@foxmail.com>
* fix(jcode): register in main's remote-installer guard, drop our duplicate
Rebasing onto 853 commits of main surfaced two things the earlier branch
had hidden.
main already owns a guard for the issue-#7253 bug class
(`remote-hook-service-registry-coverage.test.ts`). This branch had added a
second, near-identical one — a parallel implementation of a test that
already existed, which is what AGENTS.md's reuse rule is about. Deleted
ours and registered jcode in main's, which is the one that has kept pace
with every agent added since.
Also fixes a missing separator in the mobile icon map. `pnpm tc` does not
cover `mobile/`, so only the session-route closure suite caught it.
Co-authored-by: czzczz <chanzrz_zbf@foxmail.com>
* fix(jcode): decompose the four files jcode pushed over max-lines
Adding an agent tipped four modules past their line budget. AGENTS.md
forbids a `max-lines` disable or a per-file bump, so each is split on a
real seam rather than silenced:
- agent-catalog.tsx keeps `AgentIcon`, which 70+ files import, and the
rows move out. The rows alone exceed the 300-line budget a `.ts` file
gets, so they follow the primary/secondary split this repo already uses
for commit-message agent specs.
- getAgentResumeArgv -> agent-resume-argv.ts, re-exported so the 18 call
sites keep one import path.
- isDiscoverableSessionFile/pathSegments -> session-file-discovery.ts.
- remoteCodexSources -> remote-session-scanner-codex-sources.ts; Codex is
the one remote agent with two CODEX_HOME roots.
Also:
- Records the readiness-census baseline jcode now needs. main added that
gate while this branch was out; the fixture is the recorded 74-case
matrix, not a hand-written one.
- Restores two entries a rebase resolution silently dropped from
config/tsconfig.cli.json (gitlab/project-ref-parser,
startup/shell-path-probe). Nothing to do with jcode; losing them was a
conflict-resolution mistake on this branch.
Co-authored-by: czzczz <chanzrz_zbf@foxmail.com>
* feat(native-chat): open structured chats on the paired Orca server that owns the workspace (#24205)
* fix(native-chat): a host admits structured sessions by client capability, not its own chat setting
A host's experimentalStructuredNativeChat decided whether any paired client could reach
agentSession.* at all, and whether session.tabs.* showed it structured tabs. That setting is the
host user's own launch preference: whether a new agent opens as a chat or a terminal is decided by
whoever launches it. Using it as admission control meant a client whose own preference was
"structured chat" was refused on a host whose preference was "terminal", and chats opened while
the setting was on were withheld from mobile once it was turned off.
The gate now asks one thing: did the client advertise agent-session.structured.v1 (in-process
callers negotiate nothing and are always admitted). Tab projection and restore follow the same
rule. With the setting no longer gating anything, the separate cleanup gate (close, cancel,
unsubscribe, release), which existed only so those kept working after the setting was switched
off, is identical to the main gate and is folded into it. The settings listener that republished
tabs when the setting changed is removed, since projection no longer depends on it.
The host setting still picks the default for launches that start on the host itself
(agent.launch from mobile, orchestration worker-start).
* fix(native-chat): the desktop declares structured chat support to paired hosts
The desktop renderer advertised agent-session.structured.v1 (and the Claude, turn-item and
background-task capabilities that go with it) to its own main process but not to a paired Orca
server. The server therefore refused every agentSession.* call from the desktop and stripped
structured chat tabs out of the tab list it published to it, so a structured chat running on a
paired server never appeared on the desktop, even though the renderer already mirrors a host's
agent-session tabs and drives each one against the server that owns its workspace.
The same renderer reads structured chats on either host, so the remote Electron list now carries
the same structured-session capabilities as the local one, and the capability test pins that
nothing is advertised only locally.
* feat(native-chat): open structured chats on the paired server that owns the workspace
With the structured-chat default on, an agent launched in a workspace that lives on a paired Orca
server always opened as a terminal (or the terminal-backed chat view). Three things kept it off
the structured path: the launch check refused every host but this machine, a remote workspace was
handed to the host-published terminal path before the structured route was even considered, and
the structured launch pipeline sent create and every follow-up call to this machine's runtime.
A workspace's owning runtime is fixed, so the pipeline now derives it from the workspace instead
of assuming this machine (structured-agent-session-owner.ts, the same derivation the chat pane
already uses to read a session). The launch intent carries that target; the pre-create support
check, create, the publication check and fence read, the launch prompt send, held option picks,
the "focus this chat" marker, the placeholder tab's host, and tab close/purge all use it.
The launch check now accepts a paired server and asks that server's own capabilities (read from
the status the client already cached for it) rather than this machine's. An SSH workspace stays
terminal-backed: no Orca runtime runs there. The host still answers createSupport before
anything is created, so an older server that refuses shows the failure in the chat tab.
A chat the user closed before its create landed is now also retired on the paired server when it
publishes, as the local sync already does. Orchestration workers placed on another runtime are
unchanged: federation creates terminal agents only.
* fix(native-chat): negotiate client-chosen launch mode so released phones and old servers keep terminals
Hosts advertise agent-session.structured.client-launch-mode.v1: they admit
structured sessions by client capability alone. A remote client that does
not advertise it (phones released before agent.launch) asks createSupport
to pick the launch mode, so the host keeps answering that with its own
setting, exactly as before. Cleanup methods keep their own named gate so a
future admission condition cannot make close or cancel refusable.
* refactor(runtime): keep the Electron client capability list in its own module
protocol-version.ts is at its line budget; the list is what the desktop
advertises to paired hosts, not the host's own contract.
* fix(native-chat): the desktop declares it picks each launch mode itself
Paired hosts and the desktop's own main process then answer createSupport
by the workspace rather than by their own chat setting.
* fix(native-chat): pin each structured chat to the host it was launched on
- Route: a paired server opens a chat only when it advertises the
client-chosen launch mode; an older server keeps its terminal. Its
capabilities come from the store's host status, not the compatibility
cache that is empty after boot or reconnect.
- A launch command override is this machine's: the route applies it only
locally, and a host's createSupport refuses on its own override.
- The owning host is resolved once, from the same value the route used,
and carried on the launch intent, its persisted record (legacy records
load as local), the provisional tab and every mirrored chat tab. Close,
purge, retry and reload read it instead of re-deriving it from a
worktree id two hosts can share; an owner that cannot be named refuses.
- Cancellation tombstones record their host: only that host's
authoritative inventory retires one, restored cleanup closes it there,
and a paired host's tombstone expires after 30 days if it never answers.
- A paired server's frame settles launches it published, as the local
inventory already does for this machine.
* fix(native-chat): a paired server that declines a chat opens its terminal instead
createSupport only reads, so both of its non-answers are settled before
anything is created:
- A paired server that answers it cannot run the chat (a WSL repo, a
Claude account mismatch, its own launch command override) closes the
chat tab and opens the terminal the route would have chosen, with a
notice saying why. This machine's own decline stays a failed chat.
- A host that could not be asked closes the chat tab and leaves one
failure toast, instead of a lingering "could not confirm" chat.
* test(native-chat): a provisional chat carries its launch's host and hands pre-create failures on
* chore(native-chat): justify the two type assertions this change's lines touch
* test(native-chat): state why each staged test fixture is cast
* fix(native-chat): chats that already exist keep showing whatever the chat setting says
The structured chat setting decides only what new agents open as. With it
off, this machine's structured chats used to be hidden while the host,
which no longer reads the setting, still reported them to the workspace
activation gate, so a workspace holding only a chat opened empty. The
local chat mirror and its startup restore now run whatever the setting
says, the continue-after-restart offer follows the chats that exist, and
the setting's copy says it applies to new agents.
* fix(native-chat): the browser client keeps its host terminal on paired servers
A browser client whose own preferences turn structured chat on took the
structured route for every paired-server workspace, but its handshake
never says it reads structured sessions, so the server refused the chat
and the user got a failed chat tab where a host terminal used to open.
The route for a paired host now also asks what this client advertises to
it: the desktop's list does, the browser client's does not. Its handshake
list is now a named constant the route reads, so the two cannot drift.
The chat setting's copy now says it runs on paired Orca servers too;
WSL and SSH hosts still use terminal chat.
* fix(native-chat): a retried launch a paired server declines opens its terminal too
A launch restored after a reload settles only through its Retry, so a
declining paired server left a failed chat there while a first launch got
the server's terminal and a notice. The chat's Retry now hands the same
pre-create failures to the same replacement, carrying the prompt the
launch had staged.
* refactor(native-chat): a paired host's cancelled-chat record ends on its 30-day TTL
The paired census re-read a host's whole inventory after every
authoritative frame to retire tombstones, and a tombstone restored after
a reload needed a second such frame, so in practice it retired nothing.
A tombstone guards a random session id and is inert once stale; the chat
is already closed on its host whenever a frame shows it. The census, its
trigger in the mirror layer and its cleanup are removed; the owner-scoped
tombstones, close-on-sight, the TTL and publication marking from frames
stay.
* fix(native-chat): a chat's pane and status read from the host recorded on its tab
The chat pane and its sidebar status still derived the host from the
workspace id, which two hosts can share; a paired chat in a non-active
same-id workspace was read from this machine. Both now read the owner
stamped on the tab, as close, purge and publication already do.
* fix(native-chat): "Resume in chat" follows the terminal resume's host rule
Agent Session History offered "Resume in chat" for a conversation
recorded on this machine into a paired server's workspace, where its
transcript does not exist. A chat now resumes a conversation only on the
host that recorded it, as the terminal resume does, and that host is the
one asked whether it can resume history.
* fix(native-chat): the chat setting says older paired servers keep terminal chat
* test(native-chat): pin that a host advertises the client-chosen launch mode
* fix(native-chat): mirror this machine's chats only where it holds them
Round 1 ran the local chat mirror for everyone so existing chats show
whatever the setting says. That gave every desktop a permanent
session-tabs listener, which turns on the runtime's phone replication
paths, plus two full session-tab censuses at startup, and made the
browser client mirror its remote host a second time.
The runtime now says whether it holds structured chats: its structured
host is built only when saved chats were restored at startup or a client
created one here, and it announces the moment one is built. The mirror,
the startup restore and the continue-after-restart offer run only when
the setting launches chats or the host holds some, and never in the
browser client. A chat a paired client creates here with the setting off
still appears at once. The chat behaviour settings show wherever chats
exist, and the setting's copy says it picks what new agents open as. The
toggle-off teardown this made dead is removed.
* test(native-chat): route a paired-server launch over the capability lists both sides really advertise
* test(native-chat): record install listeners without a cast
* fix(native-chat): a paired server admits a chat before any of it exists here
The desktop opened a paired server's chat tab, launch record, queued
prompt and focus intent before asking the server, so a "no" needed a
replacement that undid and redid all of it, and every piece it missed
was a bug: the workspace deselected, the caller told "failed" while a
terminal ran its prompt, the caller's arguments and other queued prompts
lost, and a create whose reply was lost treated as never sent.
A paired launch now asks the server first and commits nothing until it
answers. Admitted opens the chat as before. Declined runs the caller's
own launch as the server's terminal, with the existing notice (a resume
fails instead, having no terminal equivalent). Unreachable opens nothing
and names the server in one toast. The new-tab launcher reports the
host's surface for paired workspaces, as it did before paired chats,
with the prompt delivery of whichever surface got the prompt. The
replacement and its error classes are gone, and the probe inside a
launch is back to its old meaning: a "no" is a failed chat with Retry,
and no answer leaves "Could not confirm" with Retry and the prompt kept,
here as on this machine.
* fix(native-chat): mirror this machine's chats only once it holds one, not once its host is built
Session history, resume preparation, terminal resume commands and replay-safe phone launches all
build the structured host for users who never had a chat, which turned on the chat mirror and the
structured-only settings rows until the next restart. The signal is now derived from the host's
records (or a records file still owed its import) and pushed when the first chat is restored or
created. A throwing listener no longer fails the install that fired it.
* fix(native-chat): a fork's reveal never seeds a terminal beside the surface the launcher opens
Forking into a paired-server workspace revealed it as if nothing would open there, so the reveal
created a blank host terminal beside the forked chat (and beside a forked agent terminal on main).
The launcher always opens the fork's surface itself, so the reveal now says so for every surface,
as the fix-checks launch already does.
* fix(native-chat): a declined direct launch keeps the caller's CLI args; an unreachable resume toasts once
When a paired server declines a "Fix checks" chat in a new workspace, the terminal that opens
instead now carries the recipe's saved CLI arguments, launch platform and launch source, as the
terminal route did. "Resume in chat" to a server that cannot be reached showed the admission's
"Could not reach" toast and the vault's generic one; the admission marks its failure notified and
the vault adds nothing.
* fix(native-chat): a declined background create opens its terminal without switching workspaces
Since #23974 a worktree create the user moved away from must not pull them onto the new
workspace. When a paired server declined that create's chat, the fallback terminal opened as a new
agent tab, whose host create selects the workspace. The create now opens its own agent terminal the
way main's background branch does: in place from the request's startup plan (so its CLI args carry),
without selecting the workspace. A create the user is still watching keeps the new-tab fallback.
* test(native-chat): name the launch's host in main's new outbox fence test
Main's new staging-failure test calls settleStructuredAgentLaunchPrompt without the target this PR
made required; it is a local launch, as in the sibling tests.
* fix(native-chat): a paired server's new chat shows no model until the server reports the one it started
A chat on a paired server starts with the server's saved model and options, but the picker showed
this desktop's saved selection (or the catalog default) until the server reported a model, and a
pick made in that window was remembered on the server under that guessed model. A paired launch
now carries no desktop seed, and until the server reports its model the picker names no model and
takes no picks. Local chats are unchanged.
* test(native-chat): seed the paired repo without a cast
The repo literal already satisfies Repo, so the changed-lines cast gate has nothing to excuse.
* feat(native-chat): createSupport reports the saved selection a new chat on this host starts with
A chat on a paired server starts with the server's saved model and options, which the desktop could
not read, so its picker showed a guess. createSupport's answer, which the desktop already waits for
before a paired launch, now also carries that seed as a new optional field (older clients ignore it).
Create and createSupport read it through one resolver so they cannot drift.
* fix(native-chat): a paired server's new chat shows the selection the server will start it with
The paired server now names its saved model and options in the admission answer the desktop
already waits for. That seed goes into the launch intent and its persisted record, so the picker
shows the server's model at once, stays pickable like a local chat, and remembers picks on the
server under that model; a reload shows the same. The locked picker remains only for a server too
old to name a seed.
Also moves host admission and launch-outcome tracking into their own modules: the latest main
merge left structured-agent-session-launch.ts over the max-lines limit.
* test(native-chat): expect the launch intent's new seed argument in exact-call assertions
* refactor(protocol): move the Electron remote client capability list into its own module
Merging main left protocol-version.ts one line over the max-lines limit on this branch. The list of
capabilities the desktop advertises to a paired host moves, unchanged, into
electron-remote-runtime-client-capabilities.ts, the module the next PR in the stack already uses
for it; importers point there.
* fix(native-chat): a paired chat with no saved server model is pickable; Retry shows the server's current seed
A server whose user never saved a chat model sends no seed, and the desktop showed a locked,
model-only picker for it, although that is the common case: no server that can admit a paired chat
predates the seed field. Such a chat now behaves like a local chat with no saved model: the CLI
default, pickable. The lock and its snapshot helper are gone.
Retry kept the first admission's seed while the create probe, which already runs on every attempt,
reported the server's current one and dropped it. The probe's seed now replaces a paired launch's
seed and the picker's, so a retried chat shows what its create will run.
* test(cross-version): stub the launch seed resolver createSupport now reads
* test(protocol): pin the desktop capability divergence against what a paired server receives
Every paired transport sends the shared remote base plus the Electron list, so the
divergence test now compares that union with the renderer's local list instead of
the declared Electron list. A capability added only to the shared base can no
longer slip past it. The two base-only capabilities it surfaced are recorded:
skills.install-result.v2 has no local caller; the authoritative-inventory label is
read by the local tabs sync but dropped by main, and is marked unsettled.
The turn-item and both background-task-stop capabilities were already sent through
the shared base, so the Electron list no longer repeats them. The wire set is
unchanged; this PR's real change on the wire is structured.v1, the Claude
structured capability and the client launch-mode capability.
* fix(native-chat): the desktop tells its own host it picks each launch mode, so retrying an existing chat works with the setting off
* docs(native-chat): name the real exit for the released-phone createSupport rule
* fix(native-chat): the route reads the capabilities a paired host actually receives
The renderer decided whether a paired host would admit a chat from the desktop's Electron list, but
every desktop transport sends that list plus the shared remote base. They agreed only because the
route's checks happened to sit in both. The route input is now built with the same
remoteRuntimeClientCapabilities the transports use (the browser client already sends its list as is),
and a test pins each against the real handshake.
* test(cross-version): a released client still gets the host-setting createSupport answer; a launch-mode client gets supported plus the seed
* test(native-chat): let main's …
* Read OpenCode Go usage from the selected account (#24770)
* Add host-owned OpenCode and Devin account profiles
* Manage OpenCode and Devin profiles in account Settings
* Expose registered account roots to host transcript readers
* Clarify managed profile provider flags
* Retain isolated Electron home in browser sidecars
* Restore inherited account environment and preserve cleanup retries
* Check relay environment values before merging
* fix(opencode): keep saved Go credentials in main-owned storage
Co-authored-by: kespineira <kespineira@users.noreply.github.com>
Co-authored-by: kevimux <kevimux@users.noreply.github.com>
* test(opencode): retain legacy key ownership across worker writes
* Consolidate managed account type imports
* test(accounts): provide OpenCode credential API in lifetime fixture
* fix(accounts): use existing localized provider names
Align the new Japanese account copy with the existing catalog repair policy.
* Keep managed account baselines private to the execution host
* Align account enrollment help with accepted providers and flags
* Show the active System account in managed profile lists
* Document the validated Linux managed account scope
* fix(opencode): resolve Go keys by execution backend
* Use host-private account restoration for OpenCode Go usage
* Fix managed account removal, credential audits, and runtime bundling
* Quarantine removed accounts and audit captured credential snapshots
* fix(accounts): canonicalize account removal to rm
Use account rm as the canonical removal command and keep account remove as an alias. Preserve the existing accounts.removeData RPC and the full original 63-path account component.
Original-Account-Source: cf71ae4cb6
Frozen-Account-Base: 08ee7ba9ef
Frozen-Integrated-Main: 53f9ea7839
Private validation source only; no ref or publication.
* fix(accounts): recover interrupted profile removal on startup
Scan private removal backups before quarantined cleanup, reuse the existing state schema to validate the exact UUID, and preserve registered or unmarked profiles. Mark account rm as destructive using the existing typo recovery policy. Retain the full original account component and public author ancestry.
Account-PR: 24636
Original-Account-Source: cf71ae4cb6
Reviewed-Public-Parent: 6abaf736e3
Frozen-Integrated-Main: 53f9ea7839
Private source only; no ref or publication.
* fix(accounts): protect registered UUID case variants during removal recovery
Compare validated account UUID identities without changing stored IDs or filesystem paths. Protect registered originals and quarantines, including explicit retry variants, and accept equivalent UUID spelling in a valid removal backup. Retain the complete account component and published contributor ancestry.
Private source only; no index, ref, or public mutation.
* Allow cold node-pty setup on Windows ARM CI
Keep a ten-minute bound only for node-pty rebuilt on Windows ARM CI hosts; all other node-gyp calls retain five minutes. Cold setup in two completed ARM jobs left compilation less than a minute.
---------
Co-authored-by: kespineira <kespineira@users.noreply.github.com>
Co-authored-by: kevimux <kevimux@users.noreply.github.com>
* fix(source-control): default Codex to GPT-5.6 Terra low (#24495)
* fix(source-control): use Codex's configured model by default
Source Control AI pinned Codex to gpt-5.5, which Codex retires on
2026-10-14. Any path that still resolves to that slug would then break
commit-message and PR-field generation.
Follow the Antigravity precedent (#21606): add a "Config default" entry
for Codex, make it the default, and omit --model when it is selected so
`codex exec` uses the model from the user's Codex config or Codex's own
default. Explicit model choices still pass --model.
Older remote servers would still build `--model default`, so advertise
git.codex-configured-model.v1 and have clients refuse the sentinel for
servers without it, the same way Antigravity is gated. The gate now
covers both agents, and the two capabilities live in their own module
because protocol-version.ts is at the max-lines limit.
Written with AI assistance (Claude Code).
Fixes#24481
* fix(source-control): honor -m, repo overrides and low effort for Codex
Review on #24495 found three gaps in the configured-model change.
The remote compatibility gate only recognized --model, so a recipe
passing Codex's -m short flag was rejected on older servers. The gate
also ignored the repository's per-operation model override that the
server applies. And moving Codex to Config default dropped the low
reasoning effort the pinned model used, which would change cost and
latency for users whose Codex config sets a higher effort.
* fix(source-control): match gate repo and model checks to the server
Repo ids can repeat across hosts, so the configured-model gate now reads
the repo row for the worktree's host instead of the first id match, the
same row the server applies. A recipe passing --model default or
-m default no longer counts as an explicit model, since an older server
still forwards that sentinel to the Codex CLI.
* fix(source-control): read only the worktree host's repo row in the gate
A worktree that names its own host must not fall back to the runtime
host's repo row, since the server applies the row for the worktree's
host. Also cover an environment id that needs URL encoding.
* fix(source-control): default Codex to GPT-5.6 Terra low
---------
Co-authored-by: Jinjing <6427696+AmethystLiang@users.noreply.github.com>
* fix(jira): search plain text when task search input isn't JQL (#22900)
* fix(jira): search plain text when task search input isn't JQL
The Tasks page sent every keystroke to Jira as JQL, so partial input like `s` failed with a 400.
Fixes#22265
* fix(jira): keep the JQL rejection reason behind Details
For plain-text searches the reason ("Field 'login' does not exist") reads as noise.
* fix(jira): announce the text-match notice to screen readers
* fix(jira): skip the wildcard when the last search word has punctuation
Jira doesn't split a wildcard term into words, so `login,*` or `c#*` matched nothing.
* fix(jira): keep the text-match live region mounted between searches
* fix(jira): keep hyphenated words like sign-in as plain text search
* fix(jira): retry a key-shaped search as text when no issue matches
Input like `utf-8`, `sha-256` or `covid-19` matches the issue-key shape, so
the search ran only `key = "UTF-8"` and showed an empty list with nothing to
explain why. An empty exact-key lookup is a wrong guess, not an answer, so
retry it as a text search — the same let-Jira-decide-then-fall-back rule the
JQL path already uses.
Splits the key and text builders apart so the caller can tell which branch it
took; `buildJiraTextSearchJql` keeps its combined behaviour for the
smart-workspace caller.
* fix(jira): let Jira's answer settle key, JQL and text guesses
Bare `in`/`is` sent everyday phrases down the JQL path, a key lookup Jira
answered with 400 never reached the text search, and a missing key like
PROJ-1401 was read as a lost connection. One key-or-text search now serves
the Tasks page and new-workspace, and one parser reads the status prefix.
* fix(jira): require IN to be its own word before a function name
`input (raw)` and `init()` read as `in` + a function call and took the JQL path.
---------
Co-authored-by: Neil <neil@stably.ai>
* Wait for OpenCode worker input before the first dispatch (#24601)
* fix: wait for OpenCode worker composer before first dispatch
Reuse captured composer readiness on local and paired execution hosts and revoke launching-shell paste anchors.
Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com>
* chore(deps): update reviewed dependencies across Orca (#24561)
* chore(deps): update reviewed desktop dependencies and tooling
* chore(deps): update compatible mobile packages and Fastlane
* chore(deps): update cloud transports and enforce release age
* chore(deps): patch documentation dependencies and record review
* chore: remove dependency review reports
* test(linear): smoke-load resolved SDK through CommonJS loader
* fix(deps): keep native rebuilds from reinstalling addon dependencies
* fix(native): invoke installed node-gyp directly for Node rebuilds
* test(cloud): exclude observer probes from row-lock timing budget
* test(mobile): preserve the CSS writer receiver in viewport spy
* test(native): remove obsolete batch-shim fixture exception
* Stream native rebuild output through the process wrapper
* fix(antigravity): discover global skills from the CLI configuration root (#24592)
* fix(antigravity): keep quota probes free and visible without Gemini OAuth (#24593)
Consolidates the reviewed version and visibility work from #24283 with the probe gating and structured error classification from #24296. Reject unsuccessful version probes, preserve diagnostic precedence, and assert that real quota reads start no model turn.
Co-authored-by: Pablo Werlang <19828711+werlang@users.noreply.github.com>
* fix(antigravity): launch POSIX hooks through sh with bounded JSON stdin (#24596)
* fix(antigravity): make POSIX hooks executable with bounded JSON stdin
* test(antigravity): decode SSH hook shell command before assertions
* test: check plugin fixture worktree cleanup (#24779)
* fix(files): ignore nested generated directories on macOS (#24620)
* fix(files): ignore nested generated directories on macOS
* fix(files): filter generated filenames containing newlines
* Check E2E packages where the installer reads them (#24785)
* test: check plugin fixture worktree cleanup
* test: check E2E installer package environment
* Preserve Mermaid exports through mobile bundling (#24661)
* Update README downloads badge
* ci: skip installed glibc tools in SSH host qualification (#24733)
* Fix terminal width cutoff on wide panes (#24687)
* fix(terminal): let wide panes use up to 1024 columns
Adapt the wider viewport limit proposed in #16578 to the current runtime, shared RPC schemas, and preview sizing.
Co-authored-by: innocarpe <innocarpe@users.noreply.github.com>
* test(terminal): wait for probe output after command echo
* test(terminal): align RPC boundary with wider viewport limit
---------
Co-authored-by: innocarpe <innocarpe@users.noreply.github.com>
* fix(runtime): report an open agent question as blocked to tui-idle waits (#24533)
OpenCode's question tool (and Pi/OMP ask tools and custom modals) put the
hook row in a waiting state but paint no dialog wording the blocked-text
layer knows, so the hook lane read the wait as pending and the tui-idle
wait timed out with no blocked reason. For agents whose hooks are
authoritative, a wait the hook reports with no recognised dialog text now
blocks with the existing agent-interactive-prompt reason, unless input
reached the pane after the row (it may have answered the question before
the next hook arrived).
* fix(runtime): read Codex's 'Implement this plan?' menu as blocked (#24536)
After a Plan-mode turn Codex shows a menu that owns the keyboard, but its Stop
hook has already fired, so a tui-idle wait settled ready and sent text into the
menu. A Codex blocked text anchor now names it an interactive prompt while its
key row ends the tail, written against a new 0.160.0 capture that is replayed
by the readiness census.
* feat: live updates for agent state rules (#24387)
Orca downloads a newer agent-state-rules.json from a fixed GitHub release (stable or next channel), validates it like the bundled rules, and applies it without a restart; a local override wins over the download, which wins over the bundled rules. A hand-started workflow from main is the only publisher; merging publishes nothing.
* fix(runtime): settle Codex tui-idle waits on its hook done, leaving working rows to the rules (#24541)
A headless orca serve has no window to write the Codex ready title, so a
Codex tui-idle wait settled only after three quiet seconds. Rule files gain
profile.hooks: "turn-end": a fresh hook done settles the wait, while a
working or permission row leaves the decision to the rules, so a Codex
whose Esc posts no event (before its Interrupt hook) cannot hang the wait.
Codex moves from identity-only to turn-end.
* fix(release): accept the build identity as a later declarator in the minified telemetry check (#24219)
* fix(windows): link the CLI launcher's C runtime statically so orca.exe runs without the VC++ Redistributable (#24484)
* feat(terminal): point an old terminal's Codex shared-server banner at a new terminal (STA-9051) (#24501)
* feat(terminal): point an old terminal's shared-server banner at a new terminal
A terminal opened before the update that added Orca's codex wrapper is
still served by an older terminal daemon, so a typed codex there joins
Codex's shared server. The banner now says why and offers a new terminal
instead of the global Fix, which changes Codex settings and stops a
server other sessions use.
Detection reads the owning daemon's protocol from the router's in-memory
session map, only after a pane is already found on the shared server.
Refs #24217, STA-9051
* refactor(terminal): simplify the old-terminal banner after review
- Open new terminal now works from Activity, which shows panes from
worktrees that are not active: it activates the tab's worktree first.
- Inline the legacy-daemon check in the IPC handler over the existing
getLegacyDaemonAdapters instead of a new routing export.
- One banner frame with the variant chosen inline; the Fix dialog is a
sibling rather than a children slot.
- Rename CodexSharedServerJoin to CodexSharedServerStatus.
* fix(terminal): open the new terminal in the pane's own workspace, and only promise it where it helps
Open new terminal now always goes through the folder-aware workspace activation
(returning early when that fails) and reveals the floating panel for floating
panes, so folder workspaces and panes viewed from Activity open in the right place.
The old-terminal variant now shows only when the shell Codex was typed into gets
Orca's codex function from this build: zsh, bash and PowerShell from protocol 37,
fish from 39, cmd.exe never. The shell is the parent of the Codex process in the
process table the shared-server check already reads.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* feat(terminal): explain an old terminal's shared server in Learn more
Old-tab banner says Orca now gives each Codex its own server, and gains a
Learn more dialog: why it matters, why this terminal still shares, Open new
terminal, and a quieter way into the existing Turn off / Stop server steps.
* fix(terminal): shorten the old-terminal Learn more copy to one line
* fix(terminal): drop the global fix from the old-terminal dialog
A new terminal already runs Codex on its own server there, so turning off sharing everywhere only changes settings outside Orca and can end other sessions.
* fix(terminal): treat fish without config as a shell Orca does not wrap
* fix(terminal): return focus when a Codex shared-server dialog closes
Both banner dialogs are controlled with no Radix trigger, so Esc or X left
focus on document.body. Capture the active surface when the banner opens a
dialog and restore it on close via useModalReturnFocus; Open new terminal
skips the restore so the new terminal keeps focus.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix(terminal): return focus when no new terminal opens, and keep an open banner dialog when Codex ends
---------
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix(filesystem): deleting a workspace no longer fails as 'outside allowed directories' in WSL-runtime C:\ projects (#24242)
* fix(filesystem): list a WSL-runtime repo's worktrees through its distro when authorizing paths
* fix(filesystem): record the Git that listed each repo's roots and keep WSL roots under repair
- Registration now takes the distro the caller listed through (create and catalog scan pass theirs)
instead of re-resolving the runtime, so a stale-routed listing is relisted on the next miss.
- A runtime awaiting repair no longer counts as a routing change, so its last WSL listing stays
authorized instead of being replaced by a host-Git listing.
- The routing check runs after each awaited refresh, so a runtime switch during an in-flight
rebuild is corrected in the same request.
- Reuse the shared distro helper for listing; move the drift check into the relist policy and
refresh the root set once per batch.
* test(worktrees): fail the host-Git scan registration test when nothing registers
* fix: dismiss Codex account prompt and return focus to terminal (#24683)
* chore(worktree): include create timing and spare outcome in the workspace create events (#24483)
* chore(worktree): include create timing and spare outcome in the workspace-created event
The workspace_created and workspace_create_failed events gain optional,
numbers-and-enums-only fields built from what the create already measured:
total and per-phase durations, the prepared-checkout hit/miss and miss
reason, the execution host (local/WSL/SSH), a worktree count bucket, how
many other creates were in flight, whether the repo has a post-checkout
hook (file existence only, probed after the create returns), and for a
failure the phase it died in plus elapsed time. No new git process runs;
consent and opt-out are unchanged.
* fix(worktree): attribute failed_phase by error, label WSL-path repos, skip the hook check with telemetry off
- failed_phase now names the outermost timed step the thrown error (or its cause) left, so a
caught failure or a concurrent sibling step can no longer be misattributed; the old-relay SSH
error keeps its cause so it still reads as git_worktree_add.
- execution_host follows the same rule Git routing uses, so a \\wsl.localhost repo reads wsl.
- The post-checkout hook check does not read the repo when telemetry is disabled.
- Privacy page mentions the miss reason code and the failed step.
* test(worktree): pin the old-relay SSH add error to git_worktree_add through its cause
* fix(worktree): name the create event field sets for their role, and type the old-relay test's caught error
* fix(worktree): send create events from runtime creates and record what the spare checkout did
Runtime creates (CLI, agents, phone app, paired clients, orchestration, server
automations) reuse prepared checkouts like the app's own creates, but recorded
no timing and sent no events. Both entry points now start one shared sender
(workspace-create-telemetry.ts), so every create sends exactly one event with
the same fields, plus create_entry_point (app | runtime).
Spare-checkout fields:
- concurrent_preparations: peak prepared-checkout builds and background
discards running during the create, excluding the one it used; the window
closes before the create's own re-arm starts.
- prepared_checkout_claim / prepared_checkout_discard phases, so on a miss
git_worktree_add minus the prepared_checkout_* phases is the plain checkout.
- prepared_checkout_reset (none | base_moved | retargeted) replaces the
retargeted flag; prepared_checkout_origin (prefetch | rearm) on hits.
- workspace_create_failed carries the spare outcome and its wait.
- repo_index_size_bucket from one stat of .git/index in the existing
post-create probe (telemetry on, local/WSL only, 2 s cap).
* fix(worktree): add spare build and idle time, the re-arm prefetch origin, and a tracked-file count
- prepared_checkout_build_ms / prepared_checkout_idle_ms on hits: from arming
the spare to ready, and how long it sat ready before the claim (0 when the
create waited). readyAt is recorded in the pool's existing ready handler.
- prepared_checkout_origin gains rearm_then_prefetch: an automatic re-arm that
the dialog prefetch then asked for too, so rearm means the re-arm alone.
- repo_file_count_bucket replaces the index byte size: the entry count from
the 12-byte index header, which is the same in every index version; left
out for a split or sparse index.
- The shared sender never lets a failed send change the create's result or
error; it logs instead and still ends the create's concurrency membership.
- Tests pin the runtime SSH create's timing hand-off and the throwing-send
cases on both entry points.
* fix(worktree): leave out the file count under any sparse checkout and time spare builds monotonically
- The repo probe also reads .git/config.worktree, where git sparse-checkout
--sparse-index writes index.sparse, and omits the file count whenever
sparse checkout or a sparse index is on in either file, with Git's boolean
spellings. core.hooksPath there is honoured too.
- prepared_checkout_build_ms / _idle_ms use performance.now(), like every
other duration; the build is timed from its own start (buildStartedAt).
- The origin field comment names all three values.
* fix(worktree): keep the spare's build time on its first build and count worktrees by lock reason
- prepared_checkout_build_ms runs from the first build's start (including any
wait for the base fetch it is built on) to its first ready; a later tip
refresh no longer restarts it, though it still counts as new preparation
work. prepared_checkout_idle_ms runs from the latest ready (build or
refresh) to the claim.
- The worktree count reads each .git/worktrees entry's locked file and leaves
out entries whose lock reason names an Orca preparation, the way the
listing does, instead of subtracting this process's spares. That covers
spares from other processes, crash leftovers and spares being discarded,
and cannot run one low while a spare's admin dir does not exist yet. It has
its own 1.5 s cap inside the probe.
* ci(release): skip the orcad template for tags that predate it (#24872)
A patch cut from a base older than #24155 has no orcad template source, so
the template job could never pass and every desktop build waited on it.
* fix(runtime): make OMP 18.4 workers ready at the composer and keep stale-title clears display-only (#24295)
* fix(runtime): require OMP composer readiness and retain native title evidence
Keep timer-cleared titles in display state so they cannot settle an idle wait.
Require OMP's captured empty composer through the existing screen rules.
Co-authored-by: drakeo338 <paranoyouz@gmail.com>
Co-authored-by: Ahmed Nagy <ahmednagy25t@gmail.com>
Co-authored-by: Saurav M Hiremath <sauravhiremath@gmail.com>
* fix(runtime): veto OMP setup by screen and keep stale-title clears display-only
OMP readiness no longer keys on the composer's thinking-effort hint, which
OMP retires after a few uses, hides once a conversation exists and renders
differently per glyph preset. Instead OMP's setup wizard (alternate screen or
a "Setup step N of M" heading) vetoes every ready lane, and OMP's own `π >`
idle title counts for the omp identity only once quiet on a screen read clear
of the wizard. Other OMP titles take the general path again, so the
post-turn `π -` title, 17.x `π:` titles and first-party blocked waits behave
as before. `π >` is no longer explicit idle for every identity.
The stale-working-title clear stays out of runtime records, but display
readers (worktree ps, phone status and titles, terminal list, orchestration
pointer, orphan adoption and reveal titles) now project records through one
display accessor, so they show the cleared title and status again. A title
the clear retired can no longer prove an agent is present; the foreground
process must, which also covers the retained Cursor spinner, so the Cursor
presence exclusion is reverted.
* test(runtime): refuse OMP's setup splash, which has no step heading
* test(runtime): a genuine title retires the stale-working display clear
* test(daemon): record the OMP captures' inherited shrink cursor divergence
* fix(runtime): settle OMP's idle title by its age and veto only its alternate screen
OMP 18.4 re-asserts bracketed paste every second once a terminal answers
its capability probe, which Orca's terminals do, so an idle OMP pane never
went quiet and worker-start still timed out. Its idle title now counts once
it has stood a quiescence window on a screen read clear of the setup wizard.
The setup veto now reads only the alternate screen: the wizard always runs
there, and a 'Setup step N of M' line on the normal screen is an answer's
own text.
* test(runtime): let the grid ingest OMP repaints before the title arrives
* fix(runtime): keep the stale-title clear's process checks and record lifetime
The stale-working-title clear is now an explicit branch instead of an early
return. It still skips the title/status evidence readiness and delivery
read, but runs what main's clear did to re-derive process state: the
foreground-agent re-probe (an exited shell-launched agent loses its
identity) and the completion-race exit check behind a fresh done hook. The
clear now lives on the PTY record, so an SSH relay drop and reattach keeps
the cleared display, and a recreated tracker is seeded from it. Presence
judges a retired title by display ordering, so a renderer pane title older
than the clear cannot prove an exited agent is present.
* docs(runtime): OMP's idle title precedes its setup wizard by its startup tail
* fix(runtime): judge agent presence by the displayed title while a clear stands
A title the stale-working timer cleared used to veto presence outright, so a
live, busy agent whose title cleared to its name and whose process Orca
cannot recognize read as no agent, and the answer depended on whether a
renderer pane was mounted. Presence and the terminal status query now read
the display projection instead, which is what they read before the clear
stopped rewriting records: a cwd spinner still clears to a neutral title, so
the foreground process decides for an agent that exited behind it.
* fix(runtime): compare a cleared title with a lifecycle cleared the same way
The stale-working clear no longer writes the prompt lifecycle, so the
blocked-dialog check compared a cleared title with a still-working
lifecycle and ignored a trust or update dialog painted after the clear:
terminal status said idle, the guarded send accepted, and a mounted pane's
interactive wait went missing. The lifecycle is now projected through the
clear wherever a cleared title is compared with it, as main recorded both
together. That comparison can only report a wait on the user, never idle.
* fix(runtime): verify prompts behind a stale-title clear against main's baseline
Prompt-submission verification read the native lifecycle while a
stale-working clear stood, so output after a swallowed Enter behind a stuck
spinner counted as delivered, and the first spinner frame after the clear
was not a new turn. Its starting status now goes through the same lifecycle
projection the blocked-dialog check uses, and the first working status
after a clear starts a new turn, as main recorded it. Readiness, delivery
and the mailbox still read only native evidence. The blocked-dialog comment
now says the projected pair reproduces main's verdict.
---------
Co-authored-by: drakeo338 <paranoyouz@gmail.com>
Co-authored-by: Ahmed Nagy <ahmednagy25t@gmail.com>
Co-authored-by: Saurav M Hiremath <sauravhiremath@gmail.com>
* fix(files): replace watcher subscriptions after native failure (#24723)
* fix(files): retry failed shallow directory watch replacement (#24724)
* fix(git): share pending watcher reconciliation startup (#24725)
* Skip dependency installation for known headless build inputs (#24716)
* ci: defer headless dependency installation until graph analysis is needed
* docs: align headless CI rollout with platform and cache policy
* test: isolate headless detector output from the parent CI step
* Reuse pnpm verification records in Alpine CI (#24817)
* ci: reuse pnpm verification records in Alpine builders
* ci: qualify consumers of the verification restore action
* ci: match Linux verification cache archive paths
* ci: overlap ARM SSH setup and independent observation waits (#24714)
* fix(tests): run watcher crash harness against current code (#24705)
* fix(tests): resolve watcher crash harness from repository root
* fix(tests): rebuild watcher crash harness from current sources
* fix(tests): register watcher interruption callback as an owner hook
* ci(release): publish after a skipped orcad template (#24882)
* ci(release): publish after a skipped orcad template
#24872 skips orcad-template for tags that predate it, but a skipped ancestor
skips every job that keeps the implicit success(), so publish-release and the
post-release jobs never ran for v1.4.219.
* test: brace-free filter in the orcad downstream contract
* Reduce scheduled CI cache warming to every six hours (#24881)
* Reduce scheduled CI cache warming to every six hours
* Document cache warmer recovery interval and measured tradeoff
* ci: move ARM Mac qualification to macOS 15 (#24760)
* fix(files): keep watcher streams across SSH reconnect races (#24722)
* fix(files): preserve watcher streams across SSH reconnect races
* fix(types): prune the checked SSH watcher from suppression baseline
* fix(files): retain established SSH watches through connection loss
* fix(files): fence initial SSH watcher callbacks across reconnects
* test(files): verify guarded SSH watch errors and cancellation
* Keep large Markdown previews responsive (#24880)
* Keep large Markdown previews responsive
* Fix large preview review navigation and Find budgets
* Initialize preview scroll caches once and check viewport visibility
* Restore large previews after loaded rows are measured
* Refresh loaded Markdown rows after viewport changes
* Keep Markdown revisions visible and reuse bounded search text
* Skip slower Windows root package-store restores in CI (#24885)
* Skip slower Windows root package-store restores in CI
* Update reviewed mobile dependency-store cache expression
* Advance full shutdown deadlines in mocked Codex connection tests (#24893)
* fix(plugins): restore development watchers after folder replacement (#24726)
* fix(files): retry failed shallow directory watch replacement
* fix(plugins): restore development watchers after folder replacement
* fix(plugins): reconcile root bindings without broad macOS watches
* fix(plugins): avoid source-watch restarts on Windows child edits
* fix(plugins): preserve full filesystem identity precision
* fix(plugins): recognize root replacement on inode-less volumes
* fix(plugins): preserve availability without reliable directory identity
* fix(watchers): preserve directory identities when checking replacements (#24888)
* fix(watchers): preserve full directory identity precision
* fix(watchers): recover replaced inode-less directories
* fix(watchers): decline unavailable creation-time identities
* fix(opencode-go): don't fall back to OPENCODE_API_KEY when the credential database is unreadable (#24605)
* fix(opencode-go): stop before OPENCODE_API_KEY when the credential database is unreadable
An unreadable OpenCode credential database read as 'no key', so the Go key
resolver fell through to OPENCODE_API_KEY, which OpenCode shares with its
Zen provider and can show usage for the wrong key. The database read now
reports unreadable separately; with an env key set the resolver stops, and
the usage fetch uses a configured cookie or shows a readable error.
* fix(opencode-go): treat a denied credential-database listing as unreadable, not missing
* fix(codex): skip the hook trust RPC when Codex's session index is unreadable (#24604)
An unreadable state DB was read as 'not busy', so the trust grant ran a
short app-server RPC that can refresh an abandoned backfill lease. A
tri-state pending check lets the trust grant take its fallback, while other
callers keep their existing boolean behaviour.
* refactor(cursor): move the desktop-login read onto the shared foreign SQLite reader worker (#24603)
* refactor(sqlite): rename the OpenCode SQLite worker entry to foreign-sqlite-reader (STA-9122)
The worker thread that reads OpenCode's database off the main thread is about
to read other apps' databases too, so its entry is renamed to what it is:
src/main/foreign-sqlite-readers/foreign-sqlite-reader-entry.ts, built as
out/main/foreign-sqlite-reader-entry.js.
Why now: #24572 fixed the Cursor focus freeze with a second, dedicated
worker. Rather than grow one worker per foreign app, the next commit moves
Cursor onto this entry and deletes that worker. This commit is the rename
only; #24572's cursor-desktop-profile-worker-entry lines stay until then.
It moves out of ai-vault/ into a new foreign-sqlite-readers/ module because
it will no longer be session-scanner code; the module will own the readers,
their dispatch, protocol and main-process client.
The entry still routes only OpenCode kinds in this commit. The OpenCode
dispatch, protocol and process entry stay in ai-vault/ and stay OpenCode-only,
because the SSH/WSL relay reader bundles them (build-relay.mjs).
Every reference is updated: electron.vite.config.ts input key, knip entry,
the plain-node entry guard and its test, the asarUnpack list (the scanner
service still spawns this entry under ELECTRON_RUN_AS_NODE), and the
electron-builder test that reads the filename. The filename and the
beside-or-one-up (Rollup chunks) lookup now live in
foreign-sqlite-reader-entry-path.ts, which the OpenCode spawn reuses, plus an
Electron-main resolver that uses the packaged app.asar path.
* fix(cursor): move the desktop-login read from its dedicated worker onto the foreign SQLite reader (STA-9122)
#24572 fixed the Cursor focus freeze (#24360) with a dedicated worker
(rate-limits/cursor-desktop-profile-worker*.ts). Orca already runs OpenCode's
database reads on a worker, and more foreign-app SQLite reads are coming, so
keeping one worker per app means one entry, build input, asarUnpack line, knip
entry and guard line each. This keeps one pattern instead: Cursor's
state.vscdb read runs on the shared foreign SQLite reader entry, and the
dedicated worker, its entry and its config lines are deleted.
What moves:
- The read itself is a pure cursorProfile reader in
foreign-sqlite-readers/readers/ (was rate-limits/cursor-desktop-state-db.ts),
run only on the worker. A separate dispatch owns the new kinds and refuses
an unknown kind. The entry routes OpenCode kinds to the untouched OpenCode
dispatch, so the relay's OpenCode reader stays byte-identical.
- ForeignSqliteReaderClient gives each reader its own WorkerThreadRequestQueue
lane (own lazily started, idle-torn-down thread; one shared factory) with
in-flight dedupe per database path. Any failure resolves to the reader's
existing failure value and never falls back to the main thread.
Kept from #24572, so every reader gets them:
- Await worker retirement before respawning. Worker.terminate() cannot
interrupt a native SQLite call (e.g. a WAL-index rebuild), so the old
thread lives on until that call returns; respawning at once stacked a new
thread on the same work for every timed-out read (#24572 measured three
live workers). This belongs in the shared host, which fire-and-forgot
terminate(): LazyWorkerThreadHost now takes awaitRetirement and refuses to
spawn until the terminated worker settles, and the queue fails calls closed
meanwhile. Opt-in, because pure-JS clients (session scanner abort, port
scan) respawn right after an abort. A rejected terminate() also ends
retirement, so it cannot latch the reader off (raised in #24572's review).
- 10 s Cursor timeout, 2 consecutive deaths, a queue cap of 8.
- #24572's worker tests, rewritten against the shared client: responsive
caller plus coalesced probes, unavailable worker without path leaks,
stalled-worker recovery, no respawn before retirement, dispose settles.
Tests: reader, dispatch, client (timeout, 10 s default, crash, malformed,
unavailable without a main-thread read, dedupe, queue cap, own thread per
reader), queue retirement (stalled and rejected terminate), import boundary,
and an event-loop test reading a ~50 MB WAL with no -shm on a real worker.
* test(sqlite): walk the reader import boundary with the shared source-tree scan (STA-9122)
* fix(sqlite): key reader dedupe on a caller-supplied key, not the path alone (STA-9122)
* Skip slower root package-store restores in Linux PR jobs (#24896)
* test: advance mocked Claude child exit deadlines (#24897)
* perf(tab-bar): a change to one tab no longer re-renders every tab (#24261)
With many tabs open, a change to any one tab (a retitle, an agent finishing, a tab switch, a git status write, or a browser tab update on SSH and web clients) re-rendered every tab in the strip, so the strip stuttered. Each tab is now a memoized row that re-renders only when its own values change, with stable handlers, a stable drag id list and stable drag sensor options. Editor tabs get their own git status, and mirrored browser tabs keep their page-id list while the ids don't change.
Part of #24241: opening, closing or reordering a tab still re-renders every tab once.
* Reuse the headless detector compiler without installing full dependencies (#24895)
* Reuse the headless detector compiler without full dependency setup
* Keep optional compiler-cache saves from failing cache warming
* Stop mocked renderer imports from qualifying headless CI (#24902)
* Decouple headless running-work tests from the renderer
* Keep the shared running-work probe contract documented
* fix(opencode): read the binder's session store off the main thread; ship the reader worker in orcad (#24638)
* fix(opencode): read the binder's session store on the foreign SQLite reader worker (STA-9122)
Before: the OpenCode session binder listed new sessions from opencode.db with
node:sqlite on the main thread every 60 s (and on SessionStart kicks), so a
large or contended store could stall the app the same way Cursor's did.
After: the read is a pure openCodeBinderSessions reader in
foreign-sqlite-readers/readers/, run only on the worker. The binder's
correlation, pane snapshot and process sweep stay where they were.
- The binder round awaits listSessions and re-checks its generation right
after, so a stop() during the read discards the round before it touches the
unbound map or the watermark.
- The client's in-flight dedupe key now includes the cursor, so a stale round
from before a restart cannot hand its rows to the restarted round.
- Idle teardown is per reader. The binder lane keeps its thread for 120 s,
longer than its 60 s poll, so the thread is not respawned every round.
- A timeout, crash, malformed reply or unstartable worker resolves to [] (no
sessions), the value the old read already returned on failure.
- An absent store still reads as [] without a log line, and a permission or
corrupt-file failure still logs (kept from #24577, now in the reader: it
stats the path and throws anything but ENOENT/ENOTDIR to the client's log).
- The binder lane inherits #24572's limits from the shared lane: no respawn
until a timed-out worker has exited, 2 consecutive deaths, a queue cap of
8. Its timeout stays 60 s, matching its poll.
- dispatch switches on the destructured kind, so a new kind without a case
still fails to compile.
orcad: the hook server runs there too, so orcad now ships
foreign-sqlite-reader-entry.js beside orcad.js (ORCAD_ARTIFACTS, built as an
orcad child). build-orcad runs a smoke check that starts the built worker
under the build's Node and under the pinned runtime, and does a real binder
read on a fixture DB, a Cursor read of a missing file and an OpenCode history
list. The OpenCode history scanner uses the same entry and was bundled into
orcad without it, so on orcad it always failed closed; it can now run.
Tests: reader (cursor, same-ms ids, OpenCode 2 rows, missing then created,
corrupt, inaccessible directory), retirement gate for the binder lane, dispatch
routing, client lane (rows, failure -> [], dedupe per cursor, own thread, idle
teardown default and override), binder loop with an async listSessions
(failure -> [], stop during the read), orcad path resolution through orcad's
host adapters, artifact list, and the smoke check against good, missing and
non-reading entries.
* test(opencode): cover the binder read deadline with fake timers and name the failure test accurately (STA-9122)
* refactor(ai-vault): delete the unused session-scanner worker thread (#24607)
* refactor(ai-vault): delete the unused session-scanner worker thread
Production always scans through the forked session-scanner service process;
the worker thread was reachable only under NODE_ENV=test or the
undocumented ORCA_AI_VAULT_SERVICE_PROCESS=0 switch, and nothing fell back
to it on service failure. Remove the thread (spawn, client, protocol, entry,
tests), its build entry, knip and plain-node-guard listings, and the
backend switch, so session-scanner-background always routes to the service.
- Move the scan options type to the service protocol as
AiVaultServiceScanOptions.
- Tests now mock session-scanner-service-spawn, the seam production calls.
- Repoint the hot-path listing reliability gate from the worker-client test
to the service-client test, which covers the same bounded-queue,
cancellation, and fault-restart properties for the real executor.
STA-9122
* test(ai-vault): cover the service's Claude-vs-OMP subagent lister choice
Runs the real service entry and subagent reader, replacing only the two
per-agent listers, so a swapped lister choice fails.
STA-9122
* Skip slower root package-store restores in macOS PR jobs (#24908)
* Skip slower root package-store restores in macOS PR jobs
* Update the companion cache-policy contract
* Overlap independent Linux headless runtime builds (#24910)
* test(persistence): wait for real worker readiness before timeout (#24793)
* Add Qoder session history and search (#24614)
* Add Qoder session history and search with real CLI coverage
* Allow the real Qoder marker file to end with a newline
* Keep Qoder tool output out of history previews and search
* Keep Qoder search pages readable by older clients
* Verify persisted Qoder history after a real generated and resumed task
* Negotiate Qoder filters before searching an older execution host
* Combine search client imports for the CI plugin gate
* Keep the relay search oracle aligned with legacy agent filtering
* test(qoder): align search capability contracts and pin old-host fencing
* fix(cursor): show the primary model pool in compact usage (#24830)
* fix(cursor): show the primary usage pool without hiding exhaustion
Use Cursor's reported plan percentage when its base allowance disagrees.
Select Cursor Models for compact display while preserving maximum-pool
warning, overflow, sorting and collapse behavior.
Adopts the plan mapping and primary headline intent from PR23531.
Co-authored-by: DakaAlvarez <149860458+Dacadev97@users.noreply.github.com>
* fix(cursor): describe compact usage summaries
Correct the existing six translations and fallback to describe one summary per provider after the compact Cursor primary-pool adoption. Preserve quota mapping, selectors, alerts, sorting and detailed presentation.
Validated source patch: d2a233e5f90a2cf8ccfb02dfafe99e0e03add48d with normal installed commit hooks, localization catalogs and changed-code quality. Standalone copy uses a private index and preserves the reviewed candidate ancestry.
Co-authored-by: DakaAlvarez <149860458+Dacadev97@users.noreply.github.com>
---------
Co-authored-by: DakaAlvarez <149860458+Dacadev97@users.noreply.github.com>
* fix(native-chat): one ordered journal writer (#24127)
* fix(claude): settle a queued send the CLI withdrew from its own cancelled frame
Claude reports each uuid-stamped command's lifecycle (queued, started,
completed, cancelled). A send it withdraws from its queue gets `cancelled`
before the interrupt or cancel_async_message answer, so a lost or failed
answer no longer leaves that send pending: it settles as withdrawn, with the
same reason and words as the receipt path.
A command the CLI already started also ends `cancelled` when its turn is
interrupted or fails, so `cancelled` after `started` is not a withdrawal;
an echoed send has left the waiter lists and is never reached.
Tests replay real 2.1.280 captures, scrubbed.
* fix(claude): release a doubted send when the CLI reports its session idle
A Claude send whose write ended in doubt is recorded `unknown`, and a live
`unknown` reads as work still owed, so the chat showed Working until the
child exited. Claude sends `session_state_changed idle` only once its whole
queue has drained, so it can no longer be holding that send. The runtime now
routes that report to the host's existing release, the same one Codex's
thread-stopped report uses; it retires `unknown` only, never `pending`.
* fix(claude): keep a command's started mark when a redelivery re-emits queued; fixtures name msg_lifecycle_v1
* fix(claude): settle every terminal lifecycle state of a send the CLI never echoed
A send the CLI started, then cancelled before any echo, stayed pending: it may
already be in the conversation, so it is released as doubt (unknown, recovered),
never withdrawn and never re-sent. A late echo still accepts it.
The 2.1.280 schema has two more terminal states. `discarded` (the CLI ended its
session with the send still queued) settles as not delivered; `refused`
(declined before it queued) settles as not accepted by the provider. After
`started`, either one is doubt, as `cancelled` is.
The late-settlement path gains an `unknown` outcome, which the host records as
released doubt.
* fix(claude): release a send the CLI took but left unanswered when it goes idle
`session_state_changed idle` comes only once the CLI's queue has drained, so a
send it took that is still unanswered there got no echo and never will: a turn
that throws can leave `started` with no terminal state. Idle releases it as
doubt.
What proves the CLI took a send is its lifecycle frame. On a CLI that reports no
lifecycle, it is the send's place on stdin: one whose write finished before an
interrupt went out was read before the interrupt was, so the first idle after
that interrupt releases it too. A send armed ahead of the interrupt but written
after it is left alone, since the CLI may still run it.
* fix(native-chat): keep the idle sweep off a Claude child that holds a send
A Claude retrying a rate-limited request has taken the send but echoes nothing,
so no turn row exists yet and the sweep rested the child after the idle window,
turning the send into doubt. The adapter now reports whether the CLI holds a
send (lifecycle `queued` or `started`, not yet echoed or ended), derived from
the live waiters, and owed work counts it.
Nothing is stored: every held send leaves the live set on its echo, its
terminal lifecycle state, the CLI's idle, or the child's exit, so the hold ends
with the send.
* fix(claude): count only a started send at idle and as a held send
2.1.280's end-of-turn cleanup can report idle before it re-reads its queue, so
a send read in that window goes queued, idle, started. Releasing every taken
send at idle doubted that live send and dropped Working. Only a `started` send
is released at idle or keeps the child from the idle sweep; a `queued` one ends
by starting and echoing, by a terminal lifecycle frame, or with the child.
The stdin-order path for CLIs without lifecycle frames is removed: a doubted
send retired there disables content matching on CLIs that mint their own echo
ids, and no Orca failure called for it. Those CLIs keep the earlier behaviour.
Comments that said only a failed write or child exit ends a waiter, or that
idle comes only once the queue has drained, now say what ends one.
* docs(claude): say only what the CLI's lifecycle frames and idle actually prove
* fix(claude): hold the idle sweep while Claude has a send queued, not only started
The sweep rested a child whose CLI had queued a follow-up behind a turn, dropping
the send it had already taken. The hold now spans the CLI reporting it took the
send until its echo, a terminal lifecycle state, or the child's exit. The idle
release still covers only started sends: 2.1.280 can report idle before it
re-reads its queue.
* refactor(native-chat): give provider-proven late dispatch settlement its own module
* test(claude): pin a steer a Stop interrupts after it started as doubt, not withdrawn
* fix(native-chat): one ordered journal writer
Every journal write, streamed or direct, lands in the chat's one write queue
in the order it is issued, and has landed in the fold when its call returns
(except while an owed import is paid, when it lands in queue order). The event
sink stops being a queue ahead of it; journal-write coalescing, which moved a
replaced write to the tail, is removed; closing a sink no longer loses writes
already handed over. The ten flushStreamedEvents patches go. A streamed text
item takes its place at its first delta, so a direct write inside the
coalescing window never lands above text already streamed. A Stop interrupts
whatever its bookkeeping writes do.
* fix(native-chat): a failed Stop holds the lane until its queue pause lands
A Stop whose note write or stop call failed skipped the wait for its
withdrawal and pause, so the lane freed first; with writes queued behind
owed work, the drain could hand the waiting card to the agent after Stop.
* fix(native-chat): a journal write body is typed synchronous
The queue's ordering rule needs every write body to finish before it returns;
serialize still took a promise-returning body, so an await inside one would
let a later write land first. The body type now refuses a promise.
* fix(native-chat): a stream's first window snapshot always lands
The first-delta write counted as the growth checkpoint, so the window's
snapshot was skipped until 32 more characters arrived: a stream showed only
its first token, and a Codex reasoning row could sit as an empty aside. The
coalescer now marks the row-creating emit and the first snapshot after it,
and both providers' growth throttles write those.
* test(native-chat): streamed text rewritten in place above a Stop note
Covers a stream whose later deltas wait in the window across a Stop, for
Codex and Claude, and a Codex item completed inside the window.
* chore(native-chat): drop comments that still describe coalesced journal writes
* fix(native-chat): type the draft-table write body synchronous too
* fix(native-chat): an empty delta owes no streamed-text emit
The opening snapshot is forced past the growth rule, so an empty second
Codex delta rewrote the row with identical text. The coalescer now marks a
stream dirty only when a delta adds text.
* fix(native-chat): a mutation's open pays an owed import first
With the flushes gone, a Stop naming no turn, a goal set or a /clear could
read the fold while provider rows still waited behind a restore's owed
import: the Stop answered cancelled:false and interrupted nothing. The open
every mutation shares now waits for the import, as a reader's does; a failed
import is reported and never refuses the mutation.
* chore(native-chat): whenImported says mutations await it too
* test(native-chat): a Stop interrupts before its withdrawal or pause settles
Pins the order for a write that is held and then fails, for a Stop naming
its turn and one naming none.
* test(native-chat): a Stop ends a starting child before its withdrawal or pause settles
* test(native-chat): Stop order tests wait for the interrupt, not a 50 ms timer
* test(native-chat): settlement-order test reads rows through the journal row parser
Replaces a Reflect.get field walk, which the low-evidence audit rejects, with
parseJournalRow and the named row types.
* fix(native-chat): an empty delta still owes its emit, just not a forced one
The previous fix stopped an empty delta from marking the stream dirty, which
broke the pinned contract that an empty stream is snapshotted and flushed.
The coalescer now marks a stream dirty on every delta and tracks separately
whether its text changed since the last emit; only a changed snapshot is
the opening one that skips the growth throttle.
* revert(native-chat): drop the first-text row write from the delta coalescer
The immediate first-delta emit (and the opening flag and counters it needed)
had no Orca-observed failure behind it and added a journal commit per
streamed item. The coalescer, the Claude checkpoints and the tests that
pinned the first-text row go back to main's behaviour; the one ordered
writer, the sink hand-off and the Stop rules stay.
* fix(worktrees): list a folder once when git reports it twice (#24357)
When git lists the same folder twice (a leftover worktree registration that points at the main checkout), Orca's runtime listing turned each line into its own worktree with the same id, so `orca worktree current`, `active` and `branch:` failed with selector_ambiguous, and paired clients saw a duplicate row. The runtime scan now keeps git's first row per folder, the rule the desktop sidebar already uses. Separately, for a bare or separate-git-dir repo added through a linked worktree, the scan no longer relabels the main row with that worktree's folder (it relabels only when the folder's git dir is the common git dir), so the worktree keeps its own row and branch in the CLI and the sidebar. No extra git command runs.
Part of #23631: the "Profile state writer command timed out" toast in that issue has a separate cause.
* Bound AI Vault cache loading and keep atomic saves responsive (#24789)
* Bound AI Vault cache loading and cooperative atomic saves
Preserve schema 3 caches across compatible releases while limiting bytes, JSON structure, and newest unique rows. Keep in-process entries authoritative and retain a valid prior snapshot when the newest row cannot fit.
Credits @AmethystLiang for the original PR10708 cache bounds and cooperative persistence intent.
* Use checked cache JSON properties in cooperative serialization
Preserves lazy own-property access and all serializer bounds, yields and errors.
* Resolve explicitly configured command aliases for workers (#24648)
* feat(orchestration): resolve explicitly configured command aliases
* docs(orchestration): explain configured command aliases
* fix(orchestration): validate configured aliases with target shell grammar
* fix(orchestration): use actual shell and refuse assignment-only aliases
* test: preserve typed calls in configured worker target checks
Replace Reflect.apply with the existing typed prototype call pattern so the unchanged regression cases pass the anti-slop lint gate.
* Cancel the journal import test sampler before database teardown (#24767)
* fix(antigravity): bound Windows hook stdin on the owning runtime (#24622)
* fix(antigravity): bound Windows hook stdin before posting status
* fix(antigravity): publish Windows hook companion before core
* test(antigravity): name Windows hook payload cases explicitly
* Add verified native Antigravity Accounts on the owning runtime (#24691)
* Add verified native Antigravity accounts on the owning runtime
* Keep Antigravity usage tied to its observed native account
* Refuse oversized encrypted Antigravity snapshots before writing
* fix(antigravity): localize account heading and search terms
* fix(worktrees): a worktree delete git fails partway stays listed and can be retried (#23952)
* fix(worktrees): delete removed checkouts in git, not in Orca's file pool
Local worktree removal renamed the checkout into a sibling trash root and
deleted it in the background with a recursive fs.rm in the main process.
That queued one request per entry on libuv's shared 4-thread file pool, so
for minutes every other async fs call in the main process (the agent-session
store behind chat sends, file explorer reads) waited behind the delete.
`git worktree remove` now deletes the checkout inline in git's own process
again, so the card stays in its Deleting state for the length of the delete
while Orca's file pool stays free. No timeout applies to the call, so a
large delete is never killed halfway.
If git reports success but the path still exists (Git for Windows leaves
junctions and their parent directories in place), the leftover is deleted
with the existing removeHostTree; WSL checkouts stay with the distro.
Nothing creates trash any more: the scheduling queue, rename/restore
helpers and the trash_rename span are gone. The startup sweep stays to
drain entries older releases left behind, and now removes each emptied
trash root so the obligation ends.
* fix(worktrees): let Git delete Windows checkouts with long paths enabled
Removal now always runs Git's own recursive delete, and worktree creation
checks out with core.longpaths on Windows, so a deep checkout Orca created
could fail to delete with "Filename too long" (#6433). The Windows recovery
then finishes the delete but keeps the branch. Pass the same command-scoped
core.longpaths option to `git worktree remove` so Git can delete what it
created.
Also point the CI shard timing entry at the renamed real-git removal suite.
* fix(worktrees): keep an inherited GIT_ASK_YESNO out of the worktree delete
Git for Windows asks $GIT_ASK_YESNO whether to retry when a file stays
locked during a recursive delete. Orca's git env inherits the user's
environment, so an inherited value would run an arbitrary prompt program
in the middle of a removal. Drop it for the removal call only.
* perf(worktrees): run worktree deletes under their own limit, outside git admission
`git worktree remove` now deletes the whole checkout in Git's own process,
which takes 20-35 s on a large tree. It took a general git admission slot at
status tier for that whole time, and that cap is as small as two slots on a
machine with six or fewer cores, so two deletes blocked every status read.
Deletes now skip general admission and queue under their own limit of two
per host instead: two concurrent deletes already saturate one disk, and more
only slow each other down. Leftover cleanup runs inside the same slot.
* fix(worktrees): delete removed checkouts in the background and mark them removing
Since the checkout is deleted by `git worktree remove` in Git's own process,
a large delete takes 20-35 s. Answering the request only after that made web
and mobile (30 s), paired desktop (60/180 s) and the CLI (60 s) report a
failure for a delete that was still going, and mobile silently re-showed the
row.
The request now does everything that can refuse (lock, cleanliness, archive
hook, watcher/terminal gate, terminal stop, shared-link unlink), records the
removal in an in-memory table on the host and answers `removing: true`. The
delete, branch cleanup and metadata purge run after it in the same order as
before, and the watcher/terminal gate stays held until they finish.
- Listings mark rows in the table `removing` for clients that advertise
`worktree.background-removal.v1` (the desktop renderer, paired desktop and
web), and leave them out for everyone else (older clients, mobile, the
CLI), which already dropped the row when the request answered.
- The outcome (removed, with any preserved branch, or the error) rides the
existing worktrees-changed event as an optional field, sent after the row
has left the table.
- A repeat delete while Git runs joins it. A create at the same path or with
the same branch is refused with "Cleanup is pending; try again shortly";
create's name search skips the path, so generated names move on.
- Nothing is persisted: after a quit or crash Git still lists the checkout
and it can be deleted again. WSL checkouts still delete inline.
- `orca worktree rm` says the checkout is still being deleted.
* fix(worktrees): keep the existing Deleting card until the host's Git finishes
The host now answers a local worktree delete on acceptance and deletes in the
background. The renderer keeps the existing delete state set until the host
publishes how it ended:
- The delete that asked waits for the outcome on the worktrees-changed event
(local IPC or the paired runtime's client event), then runs the same
teardown, preserved-branch toast and card error an inline delete did. If
that event is lost to a dropped connection, a listing that shows the row
gone after it was marked removing finishes the wait, and one that shows it
back without the marker fails it.
- Any other renderer (a reload, a paired desktop, web) sets the same delete
state from the host's `removing` marker and clears it when the marker goes.
A failure the host publishes lands on that card's existing error.
- Web advertises `worktree.background-removal.v1` so the host sends it the
marker; paired desktop does through the Electron capability list.
No new component, style or state: the card reads the delete state it always
did. A host that predates this answers when done without `removing`, and the
renderer takes that as finished, as before.
* test(worktrees): type the removal harness and projection for the node typecheck
* fix(worktrees): don't fail a delete retry with an earlier attempt's buffered failure
A background removal's outcome that reached this renderer with no waiter (another client's
delete, a host-marked card, or one already settled from listings) was buffered for 60 s and
consumed by the next delete of the same workspace, so retrying a failed delete failed at once
with the old error while the host was deleting. Drop the buffered outcome before sending the
request; only an outcome that arrives after it can belong to it.
* fix(worktrees): let only a gap in host events settle a background delete from listings
Git unlists the checkout before the host deletes the branch, cleans the push target and purges
metadata, and the worktree-directory watcher refetches within 250 ms. The renderer read the
missing row as a finished delete, so the waiter resolved without the preserved branch (no
toast) and a failure in those last steps showed as success; the real outcome was then dropped.
The listing fallback exists only for a lost outcome event, so it now applies only after this
host's event stream had a gap: a new subscription or a replay after reconnect.
* perf(worktrees): let a bulk delete start each same-repo checkout delete once the host accepts the last
A bulk delete ran one worktree at a time per repo (#2259, for packed-refs and ref-lock races in
branch cleanup). With Git now deleting each checkout for 20-35 s before the request settles, N
worktrees in one repo took N times that. The renderer now queues same-repo deletes only until
the host accepts each one; a parent still waits for its nested children to finish. The host
serializes the branch cleanup step per repo itself, which also covers removals started by
different clients.
* test(worktrees): pin the host platform in the mocked removal suites so they pass on Windows
Removal now passes -c core.longpaths=true on Windows, so the exact-argv
assertions and command-keyed mocks never matched there (17 failures on a
Windows host). Pin darwin as the add-worktree suites already do, and drive
the one Windows-specific case through the same spy.
* test(worktrees): type the blocked git remove result instead of a broad object
The anti-slop static-analysis gate rejects `object` parameters.
* test(worktrees): clear the changed-code quality gate in the removal suites
Merge the duplicate node:fs import, build the mock child without a cast, read
worktrees:list rows through one typed helper, and give the remaining casts a SAFETY line.
* fix(worktrees): record each background delete durably and finish it after a quit or crash
A quit mid-delete left git to finish the checkout on its own while the branch
delete and metadata purge never ran; a crash left a normal-looking row. Each
accepted local removal now writes a record beside the profile state before git
starts, clears it on success or failure, and the host runs the same delete
again for any record left at startup, re-deriving what remains from git and
disk. An orderly quit stops the checkout delete without waiting for it.
* test(worktrees): type the interrupted-removal assertions for the node typecheck
* fix(worktrees): finish an interrupted delete that already removed the checkout's .git file
Quit stops git worktree remove mid-delete, and Git deletes the checkout's .git
file wherever it falls in directory order. Git then refuses the checkout
("validation failed ... .git does not exist") on every retry, so the startup
finish failed and the row could never be deleted from Orca. A registered
checkout this record owns that has lost its .git file now finishes like an
unregistered one: leftover files, prune, then the branch.
* fix(worktrees): let Git finish an interrupted delete, and never take a different checkout
A quit or crash that stops `git worktree remove` after it deleted the checkout's
.git file left a registered checkout Git refuses to remove. The previous fix
deleted that leftover inside Orca's process, which is the bulk delete this
change exists to avoid (and on Windows the leftover can be most of the
checkout). The startup finish now rewrites the missing .git file from Git's
own admin entry for that path and lets `git worktree remove --force` delete
it. `git worktree repair` is not used: it also re-points every other
registered path, including a checkout another repository now owns there.
Orca deletes the leftover itself only when no admin entry claims the path.
The startup finish forces, so it now leaves the path alone when the checkout
there is not the one recorded: a registered worktree on a different branch or
head, or a `.git` at a path Git already unregistered. The record is dropped and
the card shows why.
The record write before Git starts is now bounded (2 s, logged when exceeded)
so a stalled disk cannot hold the delete, and the outcome is published before
the record's clear reaches disk.
* test(worktrees): compare worktree paths by value and tear down with Windows lock retries
Git prints forward slashes in `git worktree list` on Windows, so the real-Git
removal suites never found a joined path there: positive checks failed and
negative ones passed without proving anything. They now compare Git's parsed
rows by value. Teardown uses the shared retrying removeTree, since Windows can
hold the deleted checkout busy for a moment after Git exits. Adds a
relative-path worktree case for the .git restore (skipped before Git 2.48).
* fix(worktrees): reply to a worktree delete when it has finished, not on a broadcast event
A current client's delete request now waits for the host's background delete and gets its real
result (removed, a preserved branch, or the error) as the reply, the way it did before the delete
moved off the request. A request that arrives while the delete runs joins it and gets the same
result. Every other view keeps reading the host's `removing` marker: the row leaving means the
delete finished, and the row listed again without the marker shows "The delete did not finish.
Try again." on a card that view had marked Deleting. A request whose reply is lost (a timeout or a
dropped connection) settles the same way from a fresh listing instead of reporting a failure.
Clients without the background-removal capability (mobile, the CLI, older desktops) are still
answered on acceptance and have rows under removal left out of their listings.
This removes the outcome on worktreesChanged and everything it needed: the renderer's outcome
waiters, early-outcome buffer and TTL, per-host event-gap generations, the request pre-registration,
and the accept callback bulk delete used. Bulk delete runs same-repo deletes in parallel only on
this machine, whose host serializes branch cleanup per repo; SSH and paired hosts stay serialized.
* test(worktrees): type the pending-removal host id in the background-removal suite
* fix(worktrees): answer a delete request even when a concurrent removal of the same worktree replaced its record
The desktop app's removal and the runtime removal (CLI, paired clients) coalesce separately, so
both can be accepted for one worktree. The second replaced the first's record, and the first
delete then finished without resolving the request waiting on it, leaving the desktop card on
Deleting indefinitely. Each delete now settles the request it was started for.
* fix(worktrees): run same-repo removal archive hooks and teardown one at a time on the host
Local bulk delete now sends same-repo removals in parallel, so their archive hooks, terminal
teardown and preflight ran at once; a hook that writes refs can race the repo's ref locks
(#2259). The host now serializes each local removal up to acceptance per repo, for every
client; Git's checkout delete still runs in parallel under the delete limit.
* fix(runtime): keep waiting worktree deletes out of a host's foreground call slots
worktree.rm now replies only after Git deletes the checkout (up to minutes), so on paired
desktop and web each waiting delete held one of the host's 8 foreground call slots, and a
bulk delete queued listing refreshes and every other foreground call behind it. Deletes now
run in their own lane with the same bound; the 2-slot background lane stays for status polls.
* fix(worktrees): join a same-worktree delete accepted while a removal waited its repo turn
The desktop app and the runtime (CLI, paired clients, web) check for a running delete before
they queue for the repo's acceptance turn. A delete of the same worktree from the other path,
accepted while this one queued, was missed: this request re-ran the archive hook, stopped the
terminals again and started a second `git worktree remove` on the directory Git was deleting.
The queued acceptance now re-checks and joins the running delete.
* fix(worktrees): fence a resumed delete's checkout from startup, and drop rows a listing read before the delete finished
A delete a quit or crash interrupted took its terminal and file-watcher gate only when the resume
job ran, after the first window was shown; session restore could open a shell or watcher inside the
half-deleted checkout first, and on Windows that handle can fail the resumed git delete. Loading the
records now fences each recorded path, and the resumed job takes the fence over in the same tick it
takes its own gate.
A listing that read git's registration before a delete finished, and replied after the removal
record cleared, returned the row unmarked, so other views briefly showed "The delete did not
finish". Listings now capture the pending removals before reading git and leave out a row whose
delete finished successfully since; a row whose delete failed stays listed as before.
* test(worktrees): keep git's auto-maintenance out of the real-git removal suite
CI's Git 2.55 failed the file-pool test in teardown with ENOTEMPTY on the scratch repo's
objects/pack after the test body passed: the 3,000-file commit's detached auto-maintenance was
still writing a pack. The scratch repo now disables auto-maintenance and auto-gc.
* fix(worktrees): one archive-hook approval covers a same-repo bulk delete again
Local same-repo deletes now start together, so each queued its trust prompt with a state snapshot
taken before the first prompt was answered; approving the first still showed the same prompt once
per remaining worktree. The queued check now reads the store when its turn comes.
* fix(worktrees): a delete Git fails partway stays listed with its error; Delete retries it
`git worktree remove --force` drops the checkout's registration even when it
cannot delete a file (root-owned files, `chflags uchg`, a read-only Windows
directory). Orca lists workspaces from Git, so the row vanished after the error,
leaving the checkout, the branch and Orca's metadata with no way to retry.
- A background delete that fails with the checkout still on disk, unregistered,
and still the removed checkout's own leftover keeps its durable removal record
with the error (`failure`) instead of clearing it. Every other failure clears
it as before.
- Local listings (desktop list/list-all/detected, runtime list/ps/detected)
add a row for each such record, carrying `removalError`, and for a pending
removal whose checkout Git no longer lists (shown as removing).
- Delete on that row (desktop IPC and runtime worktree.rm) runs the recorded
removal again: terminal teardown, then the leftover, prune, branch and
metadata, under the per-host delete limit.
- The record ends on a successful retry, when the checkout is gone (listing or
startup)…
* fix(source-control): generate PR details before Create PR on a ready branch (#24215)
* fix(source-control): generate PR details before Create PR on a ready branch
A pushed branch sent the placeholder title and empty body, skipping the
configured agent. A run Create PR submits unreviewed keeps the user's base
and never unchecks Draft; the Generate button is unchanged.
Fixes#22824
* docs(source-control): correct the auto-submitted Draft rule comment
* fix(source-control): never let generated details uncheck the user's Draft box
A Draft choice made before generation started was unprotected: the form only
keeps fields whose revision changed *while* a run was in flight, so a user who
checked Draft and then pressed Generate had the box silently unchecked by an
agent answering draft:false, and the next Create PR opened a real review ready
for review. Scope the rule to provenance rather than the in-flight window, and
apply it to reviewed runs and the prepare-branch route too, so the agent can
still flag unfinished work but never reverts a choice the user made.
* test(source-control): cover the Checks panel create-after-run and superseded-run guards
Two guards had no failing test. The Checks panel's Create PR must still send
the finished run's details while its last render shows the run as generating,
or the click silently creates nothing. Each panel's generation must return no
result when a later run replaced its record (Stop, then Generate finishing
first), or the stopped Create PR click would open a PR with the later run's
details.
* fix(source-control): don't reveal a background-created PR over another worktree
Create PR on a ready branch now waits for the agent, and the create still
runs if the sidebar panel closes meanwhile. When it finished, it always
opened the sidebar on Checks (and, with "open after create", the review),
even if the user had switched to another worktree, so Checks showed the
wrong worktree and the in-app link route switched the user back.
Reveal only when the created review's worktree is still selected, or the
panel that made it is still mounted, like the prepare-branch route.
* fix(source-control): keep Create PR in flight while the agent writes the details
Create PR on a ready branch generated the details and then created nothing,
with no message. When generation ended, Source Control's eligibility check
restarted and cleared the eligibility, and the create, which reads the panel's
latest state, returned early on the missing eligibility.
The click now stays in flight from the start of generation until the create
settles, as the prepare-branch route does, so the eligibility check stays
paused and the create sees the eligibility it started with. Repeated clicks
are counted so one returning early can't release another's hold.
* refactor(source-control): move Checks created-review linking into its own hook
No behavior change. The Checks panel's create hook was at its 400-line limit;
linking and refreshing a created review now lives in
use-checks-panel-created-review.tsx, the way Source Control keeps it in
use-hosted-review-created.ts.
* fix(source-control): finish a started Create PR run for the branch that was clicked
After the agent wrote the details, Create PR read the panel's latest render
instead of the state at the click. That render could have lost its
eligibility (no PR, no message), still show the run as generating, or show
another branch. Navigating away also gave opposite results: with the sidebar
open on another worktree no PR was made; after closing the sidebar first, it
was.
The create now always runs through the click's own closure, so a started run
creates the PR for the branch that was clicked wherever the user went, like
the prepare-branch route. The reveal still follows the selected worktree, so
nothing opens over another one. The "still shows generating" bypass in both
panels is gone: the click's render never shows the run as generating.
In the Checks panel, a create that returns after the panel moved to another
worktree still links the review to the clicked worktree and clears its
push-first flag; only the in-flight state, errors, opening the review and
the GitLab checks refresh are skipped, since they belong to whatever the
panel now shows. The same holds for a push-first create whose push finishes
after the panel moved.
* refactor(source-control): let the Create PR click own the in-flight flag
The click already kept Source Control's create in flight through generation,
so the composer stays up instead of closing while eligibility refreshes. It
did that with a per-worktree click counter next to the create's own in-flight
flag, so a repeated click that returned early couldn't end the first click's
hold.
Now the click owns the existing in-flight flag from the click until the
create settles, and a click while it is set is refused before it touches
anything, so the counter is gone. This matches what the UI already did:
Create PR is disabled while the flag is set, including after Stop until the
stopped request returns. Since the create now runs through the click's own
state, the hold only keeps the UI steady; it no longer decides whether a PR
is created.
* docs(source-control): describe the created-review foreground check by what callers pass
The Checks panel now passes whether it still shows the created review, not
just whether it is mounted, since a mounted panel can have moved to another
worktree while the create ran.
* fix(source-control): release Create PR as soon as Stop lands
After Stop, Source Control's Create PR stayed disabled ("Creating...") until
the stopped generation request came back. Locally that is under a second, but
when the cancel can't reach a slow or disconnected remote host the button
stayed disabled for up to the 75 s generation timeout.
A generation run's outcome now settles as soon as its record stops running,
not when the request returns. Stop marks the record canceled at once, so the
click that started the run ends, its hold is released, and the next click
submits the form as shown. The stopped request still finishes in the
background; its late result is dropped because the record is no longer
running for that request. Both panels' generation handlers use the same
helper.
---------
Co-authored-by: Neil <neil@stably.ai>
Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com>
* test(windows): record native PTY stress lifecycle milestones (#25042)
<!-- orca-pr-loc -->
<!-- Programmatic LoC summary. Do not edit by hand; rewritten on every commit. -->
| | Files | Added | Deleted | Net |
| :--- | ---: | ---: | ---: | ---: |
| Test | 3 | $\color{#1a7f37}{\Huge{\mathbf{+}}}$562 | 0 | $\color{#1a7f37}{\Huge{\mathbf{+}}}$562 |
| Prod | 3 | $\color{#1a7f37}{\Huge{\mathbf{+}}}$220 | $\color{#cf222e}{\Huge{\mathbf{−}}}$8 | $\color{#1a7f37}{\Huge{\mathbf{+}}}$212 |
<!-- /orca-pr-loc -->
## ELI5
When a Windows terminal stress test stalls, its old log cannot show where progress stopped. The test now records bounded, sanitized lifecycle observations while keeping its existing assertions and timing.
## What Changed
Record output-pipe, worker, native exit and final callback milestones, pending state at existing deadlines, and hashes of the native addon and supporting files. Redact addresses and credentials even when terminal controls split them, preserving the controls’ positions. Keep the warmup survivor and newest 31 terminals; later terminals still receive observers, with omissions counted explicitly.
## Why
[The failing Windows job](https://github.com/stablyai/orca/actions/runs/37132029374/job/111229398978) had one silent terminal and at least one undrained callback. [An earlier passing job](https://github.com/stablyai/orca/actions/runs/37123932023/job/111205697896) used the same source and cache inputs, without a loaded-addon hash. Observing the existing objects supplies missing evidence while preserving the gate. Bounded recent records retain the terminals most likely involved in a late failure.
## Linked Issue
Diagnosis of PR #25031’s Windows package failure. This does not claim that the original native cause is fixed or close a product issue.
## Visual Proof
N/A — test diagnostics add no UI or interaction change.
## Testing
- [ ] I manually tested these changes locally
- [x] Automated tests added and updated for reproduced failures.
On the exact previous public helper, eight strengthened controls failed and six passed. They reproduce control-interrupted credential/address leaks, omitted late terminals after eleven rounds, and late state after the 256-milestone cap. Independent review then reproduced six more leaks on the intermediate helper: usernames and hostnames masked only part of an email. The final helper runs the existing same-length redactor a second time, removing the remaining private domain while preserving controls and OSC framing. Those six before failures and two credential-overlap controls are retained. Corrected source `5037c18a898571539ade5336ad3519cd49dfb20f` passes 171 controls in four files, including all 22 observer controls, the full anti-slop audit, syntax/AST checks, focused format/lint and six quality gates.
The previous public source `f1ed3e97` passed [actual Windows CI](https://github.com/stablyai/orca/actions/runs/37136703455/job/111243011997): 25 PTYs, eight rounds, 526 passing tests and 26 skips, plus build, package and smoke checks. Its native stress/driver files remain exact here; the observer has changed, so fresh normal CI must qualify this complete correction. The passing run’s native-addon hash differs from the historical failure; it does not explain that failure.
## Review
All four original diagnostic files and public ancestry are retained; all 32,245 paths outside that scope match main `786a040b`. The existing redactor runs twice on a view with presentation controls removed, then controls are restored at their original positions; OSC framing separates title payloads from adjacent text. Logs retain 32 terminal records and 256 milestones with explicit omissions, plus bounded final snapshots. The native callback preserves its receiver, arguments, result and thrown errors; error observers preserve unhandled-error behavior.
Native assertions, deadlines, input, concurrency and cleanup remain exact. Companion hashes identify file bytes rather than in-memory DLL equivalence. The original native cause remains unverifiable.
## Agent skill upstream boundary
- [x] Not applicable; no upstream skill source is copied.
## Notes
No production API, RPC, native patch, dependency or workflow change. Blank output or a missing callback is not evidence of process death. Local helper controls run on macOS; the existing real ConPTY gate exercises Windows.
## Checklist
- [x] Small, focused diagnostic scope.
- [x] Explained the before/after, mechanism and choice.
- [x] Visual proof N/A with reason.
- [x] Self-reviewed privacy, bounded logging and callback behavior.
- [x] Cross-platform and remote impact considered.
- [ ] Fresh normal Windows CI must qualify the complete corrected source.
* fix(cli): orca file open opens PDFs and other binaries like the File Explorer (#24445)
files.open gated the desktop open on the mobile preview list, so PDFs returned opened:false with ok:true. The host now opens every file after an existence/directory check; the CLI fails loudly if an older host still declines.
Fixes#24328. Builds on #24331.
Co-authored-by: Wooseong Kim <innocarpe@gmail.com>
* Update README downloads badge
* refactor(orchestration): one function for six agent-to-agent sends (#24901)
* refactor(orchestration): send every agent-to-agent message through one sendAgentTurn
The structured mail-pointer lane and the structured worker preamble each carried a copy of
"send, wait out a pending start, read the verdict", and four dispatch-preamble sites typed into
a terminal directly. They now share sendAgentTurn, built from host.send, the host's settlement
waiter and sendTerminalAgentPrompt. Every caller passes delivery 'now', so nothing sends
differently; a source-scan ratchet keeps new direct sends out.
* test(orchestration): the refusal fixture uses a real wire refusal code
* fix(orchestration): derive the send fingerprint inside sendAgentTurn and pair target with turn
A `queue` send was refused by a real host: callers supplied a fingerprint over the body alone
while the host digests body and delivery. sendAgentTurn now builds the envelope with the
composer's own builder (extracted to structured-agent-session-send-mutation), so the fingerprint
is always over exactly the fields sent; `now` sends keep the identical digest. The pointer lane
no longer carries a fingerprint it cannot get right.
sendAgentTurn takes one argument, a union on kind that carries its own target and turn, resolved
by an exhaustive switch; the terminal turn names its purpose (only the dispatch preamble today)
instead of every terminal send inheriting the task lead line. A queued outcome keeps the host's
draft receipt, and both structured callers read outcomes exhaustively with unchanged behaviour.
The boundary test now also fences direct structured host sends, catches optional, bracket and
bound member uses, has a planted-offender self-test, and pins the unmoved terminal mail pointer
and agent-teams tmux senders.
* fix(orchestration): keep federation.ts under max-lines and satisfy prefer-template in the send ratchet
* fix(orchestration): wait on the answered submission's id when a replayed queue turn was already handed off
* test(orchestration): pin that a replayed queue turn waits for its hand-off to settle
* test(orchestration): fence call-result and cast host sends, raw terminal writes and the launch-prompt helpers
* feat(native-chat): a Claude subagent waiting on a permission prompt reads as waiting (#22634)
* feat(native-chat): a Claude subagent waiting on a permission prompt reads as waiting
A subagent's permission request reaches the parent session's callback naming the
subagent that asked (agent_id) and the tool call it gates (tool_use_id). The
pending request is recorded with the asking agent. On every drain the child-work
producer re-derives which children a pending request blocks and hands that set
to the Claude child decoder, the one owner of each child's live edges: a blocked
child reads waiting on every live edge it reports, and a child that starts or
stops waiting is a live edge of its own. Answering, denying or cancelling the
request returns the child to its prior live state; nothing is stored beyond the
pending requests.
A live task_updated carrying an error now reaches the record as the child's last
message, without an ending or a new state.
The replay test drives a scrubbed capture of the real CLI (foreground allow,
deny, interrupt, background allow, and the main agent's own request) through the
real adapter into the host's child records.
* test(native-chat): a subagent's request names it before its tool call is read
* docs(agent-status): a subagent asking for approval waits in every lane; the parent row keeps the session's own attention
* test(native-chat): hand canUseTool the asking agent without widening the helper's cast
* test(native-chat): an interrupted Claude subagent settles cancelled, not failed
A captured interrupt shows the spawn call's error result ("The user doesn't want
to proceed…") arriving before the subagent's own `task_updated {status: killed}`.
The spawn result ends nothing (the child ends only on its own terminal frame), so
the child stays live until its `killed` status settles it cancelled. A genuine
failure, captured with the subagent on a model that does not exist, sends its
`failed` status before the error result and still ends failed. Both captures now
replay through the real adapter into the host's records.
* fix(native-chat): a Claude subagent's prompt makes the parent row wait, not block
A subagent's pending prompt made the whole session `attention`, which reads as
the main agent's own `blocked` and outranks the fold's waiting arm, so the
parent row read blocked where a CLI Claude parent reads waiting. The main
agent's state now reads only its own pending prompts.
- A Claude prompt row carries the linkage of the agent that raised it: the one
the permission request names, or the owner of the tool call it gates. The
same join decides which child reads waiting, so the two cannot disagree.
- The status summary projects the session's own status from root prompts only;
every other reader (delivery gates, teardown, restart) still asks whether
anyone is waiting on a human.
- An answer keeps the prompt row's linkage by the journal's own rule: a
revision that names no producer keeps the row's existing one.
- The child-tool queries gain the prompt's producer, so a prompt row and a
child record answer "which agent" from the same join.
* test(native-chat): say which ids the permission capture scrubs and which are its own
* fix(native-chat): the status clock dates attention by the session's own asks only
The session's status is now `attention` only for its own pending prompt, so the
clock's fallback to a subagent's ask could no longer be reached, and it read the
journal by a different rule than the status it dates. Both now read root prompts.
The journal also stamps a Codex subagent's prompt with its thread (#22532), so a
Codex child's approval is that child's wait in the Codex lane too. Two tests
written for the earlier rule are updated: a subagent's ask leaves a running
session `working` on its turn's clock, and a Codex child's answered approval
leaves the settled parent's Activity row done with nothing unread.
* fix(native-chat): a completion still says the user is asked when a subagent asks
The turn-completion feed marked a completion `awaitingUser` from the status
summary's `attention`. The status now means the session's own agent is waiting,
so a subagent's pending approval stopped reaching the completion. The projection
now also says whether anyone is waiting on the user, as the delivery gates,
teardown and restart ask it, and the completion reads that.
The waiting-subagent replay answers its prompt with the adapter's current
response shape.
* revert(native-chat): a live Claude task's error stays out of the child's last message
No capture shows a live task_updated carrying an error, and it is unrelated to
a subagent waiting on a permission request; it leaves this PR.
* test(native-chat): settle the Claude session's startup before replaying a subagent's request
A startup frame drained child work during the first await, so answering a
request freed the child even with the answer's own republish removed.
* fix(native-chat): a Claude subagent's prompt row names it as its other rows do
The prompt row stamped only the asking agent's id, so a nested subagent's
request lost the agent that spawned it, its spawn call and its run. It now takes
the linkage the asker's own rows take: the gated tool call's, when that names the
same agent, else the one resolved through the agent's spawn call. The provider's
agent id stays the asker's id.
* fix(native-chat): a subagent's request makes the parent row wait without a child record
The parent row learned that a subagent needed the user only from that
subagent's child record, so a request no record carried (a Codex child the host
never registered, a Claude task past the live cap) left the row working or done
while the approval card sat in the chat.
"Someone in this session must answer" is now one derived session fact. The
projection names two facts instead of a mode flag: the main agent's own status
(attention only for its own request) and structuredAgentSessionAwaitsUser (any
pending prompt). The status summary publishes the second as an optional
awaitsUser, and the shared fold reads it: the main agent's own ask is blocked,
otherwise awaitsUser or a waiting child record makes the row wait. Every caller
picks the fact it means: the completion edge's awaitingUser and the delivery
gate read awaitsUser; the quit snapshot folds the same two inputs the sidebar
does.
* fix(native-chat): a client that predates awaitsUser still reads a subagent's request as attention
A status summary's status is now the main agent's own, so a client built before
the split would read a subagent's request as working (or idle) and fold it with
code that has no awaitsUser input. Clients advertise
agent-session.status-awaits-user.v1; at agentSession.subscribeStatus the host
sends any client that does not the pre-split summary: attention whenever
awaitsUser is set, without the main agent's own tool line, verdict and clock.
The feed and every in-process reader keep the canonical summary. Transitional,
like the turn-item downgrade.
* test(native-chat): a Codex subagent's approval makes its settled parent's Activity row wait
The test pinned the parent row done while a Codex child asked, through a harness
that fed no child records, so it proved nothing about the ask. It now drives the
ask twice through the real host status store: with no child record (the
session's awaitsUser alone) and with the child's own record waiting from
thread/status/changed. Both read waiting with needsAttention while the ask is
open, then done with nothing unread.
* docs(agent-status): a subagent's request reaches the parent row through awaitsUser in every structured lane
The store reference said a Codex child's request still read as the main agent's
blocked and that only the Codex hook lane fed a waiting child. Both structured
lanes stamp the asking child and feed child records, and awaitsUser carries the
request when no record does. The liveness comment goes back to main's: a child's
blocked is a failed task on an older host's legacy rows.
* fix(native-chat): the restart dialog still headlines a subagent's pending approval
The quit snapshot now records the main agent's own state, so a subagent asking
while the main agent worked recorded `working` and the dialog said "Was
mid-reply" where it used to say "Waiting for your approval". The headline now
comes from the snapshot's pending prompt, whoever raised it, with the existing
copy; `state` stays the main agent's own.
* test(orchestration): a subagent's pending approval holds structured mail delivery
Scoping the delivery gate to the main agent's own request left every gate test
green; a subagent's request now has its own case.
* fix(native-chat): a subagent's request is dated by when it was raised, on every client
Since the summary's clock became the main agent's own, nothing dated a wait
that only a subagent's request held: a pre-split client was sent attention with
no clock, where the old host dated it by the subagent's prompt, and a new
client's waiting row fell back to the time it first saw it, so after a reload a
request the user had already read could read unread again.
The session fact is now when someone started being asked: awaitsUserSince, the
oldest pending prompt whoever raised it, and its presence is what awaitsUser
meant. A row waiting on someone else's request takes that as its clock; the
downgrade for a client without the capability dates its attention by it, which
is what the old host published. A cross-version test pinned to the last
pre-split release runs the same journals through that release's projection and
through this one plus the downgrade, and compares the whole summary. The Codex
end-to-end test also reads the host's own status row, and keeps a read ask read
through a later row and a reload.
* test(native-chat): the pre-split parity check compares only the fields the split owns
An additive summary field is safe for old clients, so comparing whole summaries
against the pinned release would redden on one. The wire comment now says how
the downgrade dates attention: the main agent's own oldest ask, else
awaitsUserSince.
* test(runtime): an aged host-held working summary states that nobody is asked
The test built its working summary by overriding the status of a published
approval summary, which still carried awaitsUserSince, so the row correctly
read waiting. It now drops the request as its scenario says.
* fix(native-chat): the chat's subagent block says waiting when the strip does
While a Claude subagent's request was open, the sidebar and the composer strip
read waiting but the subagent block in the chat history a few pixels above
still read "Kicked off 1 subagent working": it shows the journal's roster
state, and the journal records no wait.
The structured chat now hands its transcript the subagents the strip shows
waiting, read from the host's child records through the strip's own row model
and matched by the provider id the roster names each one by. A running entry
the host says is waiting reads waiting in the group row, its entry and its
section head, with the strip's word and the question colour; it reads the
journal's state again as soon as the host stops reporting the wait.
* fix(native-chat): a collapsed subagent group shows a wait beside a failed sibling
A failed sibling took the group row's one alert slot, so a group with a waiting,
a working and a failed child read "1 working +1 failed" and hid the wait; it
now reads "1 working +1 waiting +1 failed". The waiting set keeps its identity
while a child frame changes no wait, so the transcript's subagent rows do not
re-render on every frame, and the test of a wait ending now updates one mounted
row instead of remounting it.
* refactor(claude): one needs-input state on the parent; the asking subagent alone reads waiting
Drop the split of the main agent's own status from a session-wide "someone must
answer" fact: awaitsUserSince, the agent-session.status-awaits-user.v1
capability and its old-client downgrade, and every reader change that only
consumed them (fold, equality, ingest, delivery gate, turn-completion feed,
quit snapshot, resume headline, status clock, status bridge, attention
dispatch) go back to main. The parent row again reads one needs-input state
for a pending request whoever asked, dated as before.
Kept: a request's owner recorded once on its prompt row with full producer
linkage; the asking subagent's own record reads waiting, re-derived on every
update; the chat history's subagent block reads that same state; an answered
subagent request stays in its subagent's group.
A subagent now waits only on a request the user can still answer (its card
open, no answer underway), and the adapter frees it before the host records an
answer or dismissal. So a waiting child record always sits beside the pending
card, and main's fold never reads the parent as waiting on it: no window after
an answer, and no ~3 s wait after a card dismissed by Stop.
* fix(claude): a subagent waits only beside its committed card
A subagent's wait was pushed to the host as soon as its request arrived,
while the request's card row reached the journal at least a microtask later.
So every subagent request published the parent row as waiting before
blocked (the main agent's own fold reads a waiting child that way), and
Activity got an extra unread "waiting" event that main never shows.
The card is now the one record of an open request. The translator records
the asker on the card once (its row's linkage) and counts the card open only
after the sink confirms its rows landed, then publishes the wait; anything
that closes the card (an answer underway, a dismissal handed to the host,
Claude's own withdrawal, the session's end) frees the subagent first. So
every publish that shows a subagent waiting also shows its pending card, and
the parent reads one needs-input state, exactly as on main.
This retires the registry's view of pending requests (unclaimed(), the
asking-child join) and the translator's holdsOpen. The prompt row's linkage
takes one rule: the agent the provider names, else the gated call's owner.
The parent-row proof now runs through the real deferred sink, durable
journal and status feed, publishing as production does, and checks at every
publish that waiting subagents have pending cards and that the parent row
matches a host fed no waits.
* fix(native-chat): a closed sink's dropped writes never read as landed
The sink's written() resolved ok when the sink was closed with writes still
queued, so a subagent's prompt card could count as open with no row in the
journal. written() now reports a close that dropped writes admitted so far
as not landed; drained() and lifecycleBarrier() keep reading a closed sink as
settled.
Tests: a card never opens when its sink closes first; a card Claude withdraws
while the sink holds the cancelled row back closes at once; two subagents
asking at once, and the main agent asking beside a subagent, keep the parent
row as before with each waiting subagent beside its own card; a process that
dies mid-request leaves no subagent waiting.
* fix(claude): a withdrawn subagent request frees its child before its card closes
Main's sink now hands each write to the journal as it is submitted, and an idle journal commits it
and runs the publication at once. Claude's own withdrawal of a subagent's request therefore closed
the card and published the parent row before the child's wait was freed, so one publish showed the
subagent waiting beside no pending card (fg-interrupt replay). The child's wait now also requires the
request to still be open in the registry, and a withdrawal republishes child work before the
journal takes the close.
* refactor(claude): trim subagent request waiting to the common pattern and its essential tests
The chat history no longer marks a subagent block as waiting: the approval card itself carries the
request, and the asking subagent's row in the sidebar and composer strip reads waiting, as before.
NativeChatWaitingSubagentsProvider, native-chat-waiting-subagents.ts and their renderer changes go.
A subagent waits while its request is still open and unanswered in the prompt registry and its card
has landed in the journal. The registry check also covers a withdrawal under backpressure, so the
card list no longer filters pending cancellations itself.
Tests: one integration file replays the captured CLI frames through the real adapter, sink, journal
and status feed (renamed claude-subagent-permission-request.test.ts), with the asking subagent's
state timeline, attribution, nested linkage and a card write that waits for the journal. The
producer-harness waiting test, the redundant prompt-card cases, the harness reducer swap and three
unused captures (deny, interrupt, main agent, failed subagent) are removed.
* test(claude): pin the parent row's dating when a subagent asked first, and narrow the oracle's claim
* Turn desktop notifications on or off per machine (#24518)
* feat(notifications): turn desktop notifications on or off per machine
Settings > Notifications lists each machine (this computer, SSH targets,
paired Orca servers) with a switch. Muted machines are stored as an
opt-out list so a newly added machine still notifies. Both notification
senders now name the machine a workspace runs on, and main skips the
desktop banner for a muted one; phone push is unchanged.
* fix(notifications): preserve phone alerts and honor machine mutes
* fix(notifications): bind machine mutes to configured sources
* fix(notifications): preserve chat completion subscriptions on owner collisions
* fix(notifications): reduce machine settings clutter with a collapsed section
* fix(notifications): align machine disclosure with settings rows
* fix(notifications): clarify machine switches affect only this computer
* Replace agentArgsOverride with unified removeAgentArgs approach (#25091)
Consolidate override detection and removal into removeAgentArgs. Previously
catalogs used agentArgsOverride to detect conflicts and removeAgentArgs to
strip them; now removeAgentArgs handles both by returning stripped tokens.
This eliminates redundancy and makes the intent clearer.
Enhance removeAgentArgOption with optional value filtering to support
selective removal for complex cases like Codex config overrides.
* Reuse the ancestor path while building mobile agent rows (#24539)
Preserve traversal order and cycle guards using one call-local path Set rather than a copy at every depth.
* Release waiting terminal output when a mobile subscription fails to start (#24547)
Dispose the failed subscription record’s existing terminal backlog before rethrowing its original start error.
* Avoid cloning terminal agent owners twice in relay listings (#24713)
Capture the existing host-age expression at its original time, then use one call-local fresh owner clone for the length check and unchanged published owner field.
* Reuse prepared statements for internal worker checks (#24573)
* Reuse prepared statements across remaining Dispatch reads
Use the existing schema-pinned complete Dispatch column list in five remaining read modules, enabling the existing bounded statement cache without caching results.
* Reuse prepared statements for internal worker checks
Use the existing complete Dispatch projection only for named-field internal consumers; preserve original wildcard reads for every returned failure snapshot.
* Prepare the Activity search query once per filtered list (#24701)
Lazily reuse a call-local search predicate inside the existing filter, using unchanged byte gates and thread text cache.
* Avoid rescanning shared chunks in the plain Node build guard (#24717)
Reuse a successful unchanged chunk-code verdict within one synchronous writeBundle invocation, while preserving traversal and current-code reads.
* Select the latest stable release tag in one pass (#24786)
Replace filter/sort/last selection with one traversal using the unchanged stable-tag regex and numeric comparator; update on equality to preserve the original last spelling. Both actual callers own private plain arrays from Git stdout.
* Avoid Resource Manager renders when terminal removal leaves its inventory unchanged (#24806)
Reuse the private React state object only when the existing single/bulk removal helper returns its identical inventory; keep every lifecycle revision, tombstone, known-ID write and helper invocation in its original order.
* Cancel pending cursor updates when a terminal closes (#24566)
Reuse the existing pane frame tracker to cancel owned deferred focus-class updates during existing cleanup, with disposed guards against late or reentrant delivery.
* Cancel terminal preview fit frames when the preview closes (#24712)
Reuse the existing frame tracker inside the box-fit owner, suppress scheduling after disposal and cancel pending frames at the start of the existing effect cleanup.
* Skip new mobile toast work after feedback owner cleanup (#24759)
Reuse the existing mobile mounted-ref lifecycle pattern at toast presentation entry, preserving admitted clipboard outcomes and every live animation/sequence/timer operation.
* Release terminal side effects after they have been delivered (#24548)
Clear consumed queue slots after successful apply or overflow carry; preserve backing identity across reentrant callbacks and report actual retained slots.
* Release relay handshake timers when a host connection leaves (#24554)
Reuse the existing first-frame finish pattern to remove stage-owned timer/message/close callbacks on receipt, timeout or close; check OPEN after successful async assignment verification.
* Avoid restarting error timers on closed mobile relay links (#24567)
Check the existing irreversible closed flag before scheduling the existing missing-close fallback timer.
* Avoid restarting error timers after mobile relay pairing closes (#24568)
Check the existing pairing owner closed flag before allocating its missing-close fallback alarm.
* Delete unreturned clipboard cache files after a failed write (#24599)
Reuse existing provider-copy best-effort deletion for a newly created clipboard cache file whose write fails before its URI reaches the caller.
* Skip new legacy file inventories after mobile search cleanup (#24792)
Reuse the existing mobile mounted-ref pattern only at legacy fallback entry after completed passive cleanup; preserve admitted work and all live search/authority/cache paths. Correct only the strict fully-unmounted inventory scenario and its sole golden.
* Discard completed AI Vault cancellation IDs in the relay worker (#24820)
Use the relay child's existing pending Set to ignore cancellation IDs after completion, matching its desktop sibling; preserve admitted active/queued cancellation and the bundled private sender's complete replies, errors and ownership.
* Release completed updater setup timers and callbacks (#24920)
Cancel completed deferred updater fallbacks in finally, clear only their exact pending callback, and drop the captured timer before the original fallback guard runs; preserve destroyed admission, successor ownership and updater/quit callbacks.
* Check daemon idle state without building discarded terminal inventories (#24749)
* Check daemon idle state without building discarded terminal inventories
Replace the private idle predicate full public inventory with a host-local full pass that still reads every Session liveness getter in original order.
* Remove overwritten daemon test mock assignment
* fix(updater): keep macOS Orca open when background instances block updates (#24952)
* fix(updater): guard macOS installs against running app instances
* fix(updater): match native app blockers and preserve quit lifecycle
* fix(updater): keep ordinary macOS quit on Squirrel's install-on-exit path
Converting every quit with a staged update into quitAndInstall made Cmd+Q
relaunch Orca, refused the quit when background instances existed, and
hijacked app.relaunch()+app.quit() restart flows (profile switch, admin
restart) into an update install racing the relaunched old app. Only
Update & Restart runs the running-instance preflight now; the
quit-without-install allowance is no longer reachable and is removed.
* fix(updater): preserve quit intent through macOS staging
* test(native-chat): explicitly model legacy published tab ownership
---------
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
Co-authored-by: m4air <m4air@m4airs-Air.localdomain>
* Cancel review animations and timers when the Markdown preview closes (#24644)
Track review frames with the existing frame owner, remember all active pulse timers, and retire only the detached preview generation.
* docs: update Android APK links to 0.0.52 (#25107)
* Preserve saved account credentials after enrollment errors (#25059)
* fix: preserve registered account credentials after enrollment errors
* refactor(orchestration): one function for six agent-to-agent sends (#24901)
* refactor(orchestration): send every agent-to-agent message through one sendAgentTurn
The structured mail-pointer lane and the structured worker preamble each carried a copy of
"send, wait out a pending start, read the verdict", and four dispatch-preamble sites typed into
a terminal directly. They now share sendAgentTurn, built from host.send, the host's settlement
waiter and sendTerminalAgentPrompt. Every caller passes delivery 'now', so nothing sends
differently; a source-scan ratchet keeps new direct sends out.
* test(orchestration): the refusal fixture uses a real wire refusal code
* fix(orchestration): derive the send fingerprint inside sendAgentTurn and pair target with turn
A `queue` send was refused by a real host: callers supplied a fingerprint over the body alone
while the host digests body and delivery. sendAgentTurn now builds the envelope with the
composer's own builder (extracted to structured-agent-session-send-mutation), so the fingerprint
is always over exactly the fields sent; `now` sends keep the identical digest. The pointer lane
no longer carries a fingerprint it cannot get right.
sendAgentTurn takes one argument, a union on kind that carries its own target and turn, resolved
by an exhaustive switch; the terminal turn names its purpose (only the dispatch preamble today)
instead of every terminal send inheriting the task lead line. A queued outcome keeps the host's
draft receipt, and both structured callers read outcomes exhaustively with unchanged behaviour.
The boundary test now also fences direct structured host sends, catches optional, bracket and
bound member uses, has a planted-offender self-test, and pins the unmoved terminal mail pointer
and agent-teams tmux senders.
* fix(orchestration): keep federation.ts under max-lines and satisfy prefer-template in the send ratchet
* fix(orchestration): wait on the answered submission's id when a replayed queue turn was already handed off
* test(orchestration): pin that a replayed queue turn waits for its hand-off to settle
* test(orchestration): fence call-result and cast host sends, raw terminal writes and the launch-prompt helpers
* feat(native-chat): a Claude subagent waiting on a permission prompt reads as waiting (#22634)
* feat(native-chat): a Claude subagent waiting on a permission prompt reads as waiting
A subagent's permission request reaches the parent session's callback naming the
subagent that asked (agent_id) and the tool call it gates (tool_use_id). The
pending request is recorded with the asking agent. On every drain the child-work
producer re-derives which children a pending request blocks and hands that set
to the Claude child decoder, the one owner of each child's live edges: a blocked
child reads waiting on every live edge it reports, and a child that starts or
stops waiting is a live edge of its own. Answering, denying or cancelling the
request returns the child to its prior live state; nothing is stored beyond the
pending requests.
A live task_updated carrying an error now reaches the record as the child's last
message, without an ending or a new state.
The replay test drives a scrubbed capture of the real CLI (foreground allow,
deny, interrupt, background allow, and the main agent's own request) through the
real adapter into the host's child records.
* test(native-chat): a subagent's request names it before its tool call is read
* docs(agent-status): a subagent asking for approval waits in every lane; the parent row keeps the session's own attention
* test(native-chat): hand canUseTool the asking agent without widening the helper's cast
* test(native-chat): an interrupted Claude subagent settles cancelled, not failed
A captured interrupt shows the spawn call's error result ("The user doesn't want
to proceed…") arriving before the subagent's own `task_updated {status: killed}`.
The spawn result ends nothing (the child ends only on its own terminal frame), so
the child stays live until its `killed` status settles it cancelled. A genuine
failure, captured with the subagent on a model that does not exist, sends its
`failed` status before the error result and still ends failed. Both captures now
replay through the real adapter into the host's records.
* fix(native-chat): a Claude subagent's prompt makes the parent row wait, not block
A subagent's pending prompt made the whole session `attention`, which reads as
the main agent's own `blocked` and outranks the fold's waiting arm, so the
parent row read blocked where a CLI Claude parent reads waiting. The main
agent's state now reads only its own pending prompts.
- A Claude prompt row carries the linkage of the agent that raised it: the one
the permission request names, or the owner of the tool call it gates. The
same join decides which child reads waiting, so the two cannot disagree.
- The status summary projects the session's own status from root prompts only;
every other reader (delivery gates, teardown, restart) still asks whether
anyone is waiting on a human.
- An answer keeps the prompt row's linkage by the journal's own rule: a
revision that names no producer keeps the row's existing one.
- The child-tool queries gain the prompt's producer, so a prompt row and a
child record answer "which agent" from the same join.
* test(native-chat): say which ids the permission capture scrubs and which are its own
* fix(native-chat): the status clock dates attention by the session's own asks only
The session's status is now `attention` only for its own pending prompt, so the
clock's fallback to a subagent's ask could no longer be reached, and it read the
journal by a different rule than the status it dates. Both now read root prompts.
The journal also stamps a Codex subagent's prompt with its thread (#22532), so a
Codex child's approval is that child's wait in the Codex lane too. Two tests
written for the earlier rule are updated: a subagent's ask leaves a running
session `working` on its turn's clock, and a Codex child's answered approval
leaves the settled parent's Activity row done with nothing unread.
* fix(native-chat): a completion still says the user is asked when a subagent asks
The turn-completion feed marked a completion `awaitingUser` from the status
summary's `attention`. The status now means the session's own agent is waiting,
so a subagent's pending approval stopped reaching the completion. The projection
now also says whether anyone is waiting on the user, as the delivery gates,
teardown and restart ask it, and the completion reads that.
The waiting-subagent replay answers its prompt with the adapter's current
response shape.
* revert(native-chat): a live Claude task's error stays out of the child's last message
No capture shows a live task_updated carrying an error, and it is unrelated to
a subagent waiting on a permission request; it leaves this PR.
* test(native-chat): settle the Claude session's startup before replaying a subagent's request
A startup frame drained child work during the first await, so answering a
request freed the child even with the answer's own republish removed.
* fix(native-chat): a Claude subagent's prompt row names it as its other rows do
The prompt row stamped only the asking agent's id, so a nested subagent's
request lost the agent that spawned it, its spawn call and its run. It now takes
the linkage the asker's own rows take: the gated tool call's, when that names the
same agent, else the one resolved through the agent's spawn call. The provider's
agent id stays the asker's id.
* fix(native-chat): a subagent's request makes the parent row wait without a child record
The parent row learned that a subagent needed the user only from that
subagent's child record, so a request no record carried (a Codex child the host
never registered, a Claude task past the live cap) left the row working or done
while the approval card sat in the chat.
"Someone in this session must answer" is now one derived session fact. The
projection names two facts instead of a mode flag: the main agent's own status
(attention only for its own request) and structuredAgentSessionAwaitsUser (any
pending prompt). The status summary publishes the second as an optional
awaitsUser, and the shared fold reads it: the main agent's own ask is blocked,
otherwise awaitsUser or a waiting child record makes the row wait. Every caller
picks the fact it means: the completion edge's awaitingUser and the delivery
gate read awaitsUser; the quit snapshot folds the same two inputs the sidebar
does.
* fix(native-chat): a client that predates awaitsUser still reads a subagent's request as attention
A status summary's status is now the main agent's own, so a client built before
the split would read a subagent's request as working (or idle) and fold it with
code that has no awaitsUser input. Clients advertise
agent-session.status-awaits-user.v1; at agentSession.subscribeStatus the host
sends any client that does not the pre-split summary: attention whenever
awaitsUser is set, without the main agent's own tool line, verdict and clock.
The feed and every in-process reader keep the canonical summary. Transitional,
like the turn-item downgrade.
* test(native-chat): a Codex subagent's approval makes its settled parent's Activity row wait
The test pinned the parent row done while a Codex child asked, through a harness
that fed no child records, so it proved nothing about the ask. It now drives the
ask twice through the real host status store: with no child record (the
session's awaitsUser alone) and with the child's own record waiting from
thread/status/changed. Both read waiting with needsAttention while the ask is
open, then done with nothing unread.
* docs(agent-status): a subagent's request reaches the parent row through awaitsUser in every structured lane
The store reference said a Codex child's request still read as the main agent's
blocked and that only the Codex hook lane fed a waiting child. Both structured
lanes stamp the asking child and feed child records, and awaitsUser carries the
request when no record does. The liveness comment goes back to main's: a child's
blocked is a failed task on an older host's legacy rows.
* fix(native-chat): the restart dialog still headlines a subagent's pending approval
The quit snapshot now records the main agent's own state, so a subagent asking
while the main agent worked recorded `working` and the dialog said "Was
mid-reply" where it used to say "Waiting for your approval". The headline now
comes from the snapshot's pending prompt, whoever raised it, with the existing
copy; `state` stays the main agent's own.
* test(orchestration): a subagent's pending approval holds structured mail delivery
Scoping the delivery gate to the main agent's own request left every gate test
green; a subagent's request now has its own case.
* fix(native-chat): a subagent's request is dated by when it was raised, on every client
Since the summary's clock became the main agent's own, nothing dated a wait
that only a subagent's request held: a pre-split client was sent attention with
no clock, where the old host dated it by the subagent's prompt, and a new
client's waiting row fell back to the time it first saw it, so after a reload a
request the user had already read could read unread again.
The session fact is now when someone started being asked: awaitsUserSince, the
oldest pending prompt whoever raised it, and its presence is what awaitsUser
meant. A row waiting on someone else's request takes that as its clock; the
downgrade for a client without the capability dates its attention by it, which
is what the old host published. A cross-version test pinned to the last
pre-split release runs the same journals through that release's projection and
through this one plus the downgrade, and compares the whole summary. The Codex
end-to-end test also reads the host's own status row, and keeps a read ask read
through a later row and a reload.
* test(native-chat): the pre-split parity check compares only the fields the split owns
An additive summary field is safe for old clients, so comparing whole summaries
against the pinned release would redden on one. The wire comment now says how
the downgrade dates attention: the main agent's own oldest ask, else
awaitsUserSince.
* test(runtime): an aged host-held working summary states that nobody is asked
The test built its working summary by overriding the status of a published
approval summary, which still carried awaitsUserSince, so the row correctly
read waiting. It now drops the request as its scenario says.
* fix(native-chat): the chat's subagent block says waiting when the strip does
While a Claude subagent's request was open, the sidebar and the composer strip
read waiting but the subagent block in the chat history a few pixels above
still read "Kicked off 1 subagent working": it shows the journal's roster
state, and the journal records no wait.
The structured chat now hands its transcript the subagents the strip shows
waiting, read from the host's child records through the strip's own row model
and matched by the provider id the roster names each one by. A running entry
the host says is waiting reads waiting in the group row, its entry and its
section head, with the strip's word and the question colour; it reads the
journal's state again as soon as the host stops reporting the wait.
* fix(native-chat): a collapsed subagent group shows a wait beside a failed sibling
A failed sibling took the group row's one alert slot, so a group with a waiting,
a working and a failed child read "1 working +1 failed" and hid the wait; it
now reads "1 working +1 waiting +1 failed". The waiting set keeps its identity
while a child frame changes no wait, so the transcript's subagent rows do not
re-render on every frame, and the test of a wait ending now updates one mounted
row instead of remounting it.
* refactor(claude): one needs-input state on the parent; the asking subagent alone reads waiting
Drop the split of the main agent's own status from a session-wide "someone must
answer" fact: awaitsUserSince, the agent-session.status-awaits-user.v1
capability and its old-client downgrade, and every reader change that only
consumed them (fold, equality, ingest, delivery gate, turn-completion feed,
quit snapshot, resume headline, status clock, status bridge, attention
dispatch) go back to main. The parent row again reads one needs-input state
for a pending request whoever asked, dated as before.
Kept: a request's owner recorded once on its prompt row with full producer
linkage; the asking subagent's own record reads waiting, re-derived on every
update; the chat history's subagent block reads that same state; an answered
subagent request stays in its subagent's group.
A subagent now waits only on a request the user can still answer (its card
open, no answer underway), and the adapter frees it before the host records an
answer or dismissal. So a waiting child record always sits beside the pending
card, and main's fold never reads the parent as waiting on it: no window after
an answer, and no ~3 s wait after a card dismissed by Stop.
* fix(claude): a subagent waits only beside its committed card
A subagent's wait was pushed to the host as soon as its request arrived,
while the request's card row reached the journal at least a microtask later.
So every subagent request published the parent row as waiting before
blocked (the main agent's own fold reads a waiting child that way), and
Activity got an extra unread "waiting" event that main never shows.
The card is now the one record of an open request. The translator records
the asker on the card once (its row's linkage) and counts the card open only
after the sink confirms its rows landed, then publishes the wait; anything
that closes the card (an answer underway, a dismissal handed to the host,
Claude's own withdrawal, the session's end) frees the subagent first. So
every publish that shows a subagent waiting also shows its pending card, and
the parent reads one needs-input state, exactly as on main.
This retires the registry's view of pending requests (unclaimed(), the
asking-child join) and the translator's holdsOpen. The prompt row's linkage
takes one rule: the agent the provider names, else the gated call's owner.
The parent-row proof now runs through the real deferred sink, durable
journal and status feed, publishing as production does, and checks at every
publish that waiting subagents have pending cards and that the parent row
matches a host fed no waits.
* fix(native-chat): a closed sink's dropped writes never read as landed
The sink's written() resolved ok when the sink was closed with writes still
queued, so a subagent's prompt card could count as open with no row in the
journal. written() now reports a close that dropped writes admitted so far
as not landed; drained() and lifecycleBarrier() keep reading a closed sink as
settled.
Tests: a card never opens when its sink closes first; a card Claude withdraws
while the sink holds the cancelled row back closes at once; two subagents
asking at once, and the main agent asking beside a subagent, keep the parent
row as before with each waiting subagent beside its own card; a process that
dies mid-request leaves no subagent waiting.
* fix(claude): a withdrawn subagent request frees its child before its card closes
Main's sink now hands each write to the journal as it is submitted, and an idle journal commits it
and runs the publication at once. Claude's own withdrawal of a subagent's request therefore closed
the card and published the parent row before the child's wait was freed, so one publish showed the
subagent waiting beside no pending card (fg-interrupt replay). The child's wait now also requires the
request to still be open in the registry, and a withdrawal republishes child work before the
journal takes the close.
* refactor(claude): trim subagent request waiting to the common pattern and its essential tests
The chat history no longer marks a subagent block as waiting: the approval card itself carries the
request, and the asking subagent's row in the sidebar and composer strip reads waiting, as before.
NativeChatWaitingSubagentsProvider, native-chat-waiting-subagents.ts and their renderer changes go.
A subagent waits while its request is still open and unanswered in the prompt registry and its card
has landed in the journal. The registry check also covers a withdrawal under backpressure, so the
card list no longer filters pending cancellations itself.
Tests: one integration file replays the captured CLI frames through the real adapter, sink, journal
and status feed (renamed claude-subagent-permission-request.test.ts), with the asking subagent's
state timeline, attribution, nested linkage and a card write that waits for the journal. The
producer-harness waiting test, the redundant prompt-card cases, the harness reducer swap and three
unused captures (deny, interrupt, main agent, failed subagent) are removed.
* test(claude): pin the parent row's dating when a subagent asked first, and narrow the oracle's claim
* Turn desktop notifications on or off per machine (#24518)
* feat(notifications): turn desktop notifications on or off per machine
Settings > Notifications lists each machine (this computer, SSH targets,
paired Orca servers) with a switch. Muted machines are stored as an
opt-out list so a newly added machine still notifies. Both notification
senders now name the machine a workspace runs on, and main skips the
desktop banner for a muted one; phone push is unchanged.
* fix(notifications): preserve phone alerts and honor machine mutes
* fix(notifications): bind machine mutes to configured sources
* fix(notifications): preserve chat completion subscriptions on owner collisions
* fix(notifications): reduce machine settings clutter with a collapsed section
* fix(notifications): align machine disclosure with settings rows
* fix(notifications): clarify machine switches affect only this computer
* fix: distinguish unavailable account metadata from absence
* fix(accounts): resolve registered profile UUIDs consistently
* Replace agentArgsOverride with unified removeAgentArgs approach (#25091)
Consolidate override detection and removal into removeAgentArgs. Previously
catalogs used agentArgsOverride to detect conflicts and removeAgentArgs to
strip them; now removeAgentArgs handles both by returning stripped tokens.
This eliminates redundancy and makes the intent clearer.
Enhance removeAgentArgOption with optional value filtering to support
selective removal for complex cases like Codex config overrides.
* Reuse the ancestor path while building mobile agent rows (#24539)
Preserve traversal order and cycle guards using one call-local path Set rather than a copy at every depth.
* Release waiting terminal output when a mobile subscription fails to start (#24547)
Dispose the failed subscription record’s existing terminal backlog before rethrowing its original start error.
* Avoid cloning terminal agent owners twice in relay listings (#24713)
Capture the existing host-age expression at its original time, then use one call-local fresh owner clone for the length check and unchanged published owner field.
* Reuse prepared statements for internal worker checks (#24573)
* Reuse prepared statements across remaining Dispatch reads
Use the existing schema-pinned complete Dispatch column list in five remaining read modules, enabling the existing bounded statement cache without caching results.
* Reuse prepared statements for internal worker checks
Use the existing complete Dispatch projection only for named-field internal consumers; preserve original wildcard reads for every returned failure snapshot.
* Prepare the Activity search query once per filtered list (#24701)
Lazily reuse a call-local search predicate inside the existing filter, using unchanged byte gates and thread text cache.
* Avoid rescanning shared chunks in the plain Node build guard (#24717)
Reuse a successful unchanged chunk-code verdict within one synchronous writeBundle invocation, while preserving traversal and current-code reads.
* Select the latest stable release tag in one pass (#24786)
Replace filter/sort/last selection with one traversal using the unchanged stable-tag regex and numeric comparator; update on equality to preserve the original last spelling. Both actual callers own private plain arrays from Git stdout.
* Avoid Resource Manager renders when terminal removal leaves its inventory unchanged (#24806)
Reuse the private React state object only when the existing single/bulk removal helper returns its identical inventory; keep every lifecycle revision, tombstone, known-ID write and helper invocation in its original order.
* Cancel pending cursor updates when a terminal closes (#24566)
Reuse the existing pane frame tracker to cancel owned deferred focus-class updates during existing cleanup, with disposed guards against late or reentrant delivery.
* Cancel terminal preview fit frames when the preview closes (#24712)
Reuse the existing frame tracker inside the box-fit owner, suppress scheduling after disposal and cancel pending frames at the start of the existing effect cleanup.
* Skip new mobile toast work after feedback owner cleanup (#24759)
Reuse the existing mobile mounted-ref lifecycle pattern at toast presentation entry, preserving admitted clipboard outcomes and every live animation/sequence/timer operation.
* Release terminal side effects after they have been delivered (#24548)
Clear consumed queue slots after successful apply or overflow carry; preserve backing identity across reentrant callbacks and report actual retained slots.
* Release relay handshake timers when a host connection leaves (#24554)
Reuse the existing first-frame finish pattern to remove stage-owned timer/message/close callbacks on receipt, timeout or close; check OPEN after successful async assignment verification.
* Avoid restarting error timers on closed mobile relay links (#24567)
Check the existing irreversible closed flag before scheduling the existing missing-close fallback timer.
* Avoid restarting error timers after mobile relay pairing closes (#24568)
Check the existing pairing owner closed flag before allocating its missing-close fallback alarm.
* Delete unreturned clipboard cache files after a failed write (#24599)
Reuse existing provider-copy best-effort deletion for a newly created clipboard cache file whose write fails before its URI reaches the caller.
* Skip new legacy file inventories after mobile search cleanup (#24792)
Reuse the existing mobile mounted-ref pattern only at legacy fallback entry after completed passive cleanup; preserve admitted work and all live search/authority/cache paths. Correct only the strict fully-unmounted inventory scenario and its sole golden.
* Discard completed AI Vault cancellation IDs in the relay worker (#24820)
Use the relay child's existing pending Set to ignore cancellation IDs after completion, matching its desktop sibling; preserve admitted active/queued cancellation and the bundled private sender's complete replies, errors and ownership.
* Release completed updater setup timers and callbacks (#24920)
Cancel completed deferred updater fallbacks in finally, clear only their exact pending callback, and drop the captured timer before the original fallback guard runs; preserve destroyed admission, successor ownership and updater/quit callbacks.
* Check daemon idle state without building discarded terminal inventories (#24749)
* Check daemon idle state without building discarded terminal inventories
Replace the private idle predicate full public inventory with a host-local full pass that still reads every Session liveness getter in original order.
* Remove overwritten daemon test mock assignment
* Preserve the registered account ID when selecting UUID variants
* fix(updater): keep macOS Orca open when background instances block updates (#24952)
* fix(updater): guard macOS installs against running app instances
* fix(updater): match native app blockers and preserve quit lifecycle
* fix(updater): keep ordinary macOS quit on Squirrel's install-on-exit path
Converting every quit with a staged update into quitAndInstall made Cmd+Q
relaunch Orca, refused the quit when background instances existed, and
hijacked app.relaunch()+app.quit() restart flows (profile switch, admin
restart) into an update install racing the relaunched old app. Only
Update & Restart runs the running-instance preflight now; the
quit-without-install allowance is no longer reachable and is removed.
* fix(updater): preserve quit intent through macOS staging
* test(native-chat): explicitly model legacy published tab ownership
---------
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
Co-authored-by: m4air <m4air@m4airs-Air.localdomain>
---------
Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com>
Co-authored-by: Jinjing <6427696+AmethystLiang@users.noreply.github.com>
Co-authored-by: OrcaWin <alpha-eng@stably.ai>
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
Co-authored-by: m4air <m4air@m4airs-Air.localdomain>
* Fix admission of later Claude question rows and real CLI verification (#25119)
* Attempt each Claude question journal row after admission refusal
* test: read Claude auth JSON after configuration warnings
* test: retain the explicitly opted-in Claude profile without disabling write guards
* test: use the probed Claude profile in real handshake sessions
* Keep the real Claude signed-out control free of environment credentials
---------
Co-authored-by: Orca QA <orca-qa@users.noreply.github.com>
* fix: remove managed profiles through canonical UUID paths (#25126)
* Recognize Build terminals and preserve Windows confirmation key routing
Recognize dsb terminal identity and preserve foreground Windows confirmation key routing.
Co-authored-by: Wooseong Kim <innocarpe@gmail.com>
* fix(opencode): finish status installation with invalid TUI settings (#25031)
* fix(opencode): keep server plugin installation independent of invalid TUI config
* test(opencode): format invalid TUI installation control
* Add host-owned OpenCode and Devin managed account profiles (#24636)
* Add host-owned OpenCode and Devin account profiles
* Manage OpenCode and Devin profiles in account Settings
* Expose registered account roots to host transcript readers
* Clarify managed profile provider flags
* Retain isolated Electron home in browser sidecars
* Restore inherited account environment and preserve cleanup retries
* Check relay environment values before merging
* Consolidate managed account type imports
* fix(accounts): use existing localized provider names
Align the new Japanese account copy with the existing catalog repair policy.
* Keep managed account baselines private to the execution host
* Align account enrollment help with accepted providers and flags
* Show the active System account in managed profile lists
* Document the validated Linux managed account scope
* Fix managed account removal, credential audits, and runtime bundling
* Quarantine removed accounts and audit captured credential snapshots
* Continue Antigravity IDE and 2.0 history in new CLI conversations (#24692)
* feat(antigravity): bridge IDE history into new CLI conversations
* fix(antigravity): preserve fresh-launch model and environment for IDE references
* fix(antigravity): forward IDE history opt-in through desktop IPC
* fix(antigravity): rebuild remote IDE reference startup on its host
* fix(antigravity): register IDE continuation action labels
* fix(antigravity): confine IDE references and bound metadata reads
* fix(antigravity): localize IDE continuation badges
* Preserve scanner service cache assertions and refresh Antigravity opening metadata
* Preserve Antigravity opening joins and target folder runtime authority
* fix(opencode): retry timed-out SSH plugin updates (#24666)
Preserve bounded retry behavior and the current-main status-envelope fields.
Original-PR: #24124
Reviewed-source: 103144f9c5
Co-authored-by: Justas Brazauskas <brazauskasjustas@gmail.com>
* fix(opencode): keep Go credentials private and resolve backend keys (#24615)
Preserve the complete credential storage, migration, IPC, Settings and rate-limit refresh change alongside standalone GLM plans, current-main database diagnostics and the reviewed unknown-backend environment correction. Keep native discovery cancellation third and selected environment fourth.
Original-topic-commit: 7903f1cddb
Original-topic-commit: 588117b5cf
Original-topic-commit: dedd4f8c86
Original-topic-commit: 6645dae104
Original-topic-commit: a25b80c02c1af7830b0e6a65e72d965b3ad98276
Restacked-from: a25b80c02c1af7830b0e6a65e72d965b3ad98276
Restacked-onto: b032867021
Co-authored-by: kespineira <kespineira@users.noreply.github.com>
Co-authored-by: kevimux <kevimux@users.noreply.github.com>
Reported-by: pullfrog
Reviewed-full-source: 849fe093073f4c1606bd65d79a0c725d955d0d1f
Native-helper-source: 80dbe23237
Reviewed-full-current-source: 36acb57d44adb3d378c0289c8c15f7da0fda214c
* Skip store notifications when refreshed work items are unchanged (#24530)
An identical forced refresh previously returned an empty Zustand patch, creating a new root state and notifying every subscriber. It now returns the current state after renewing the existing freshness timestamp. Real row or metadata changes still publish once.
* Read project activity once while sorting the sidebar (#24549)
Reuse the existing pure recent rank once per actual entry tuple within each synchronous sort; discard the lookup after sorting.
* Skip impossible link and tag matches in docs search results (#24646)
* Skip impossible HTML matches when formatting docs search results
After the existing link-removal phase, skip the unchanged HTML regex only when the current string has no closing delimiter.
* Skip impossible link and tag matches in docs search results
Skip the five unchanged link regexes when their protected-code input lacks ]; skip the unchanged HTML regex when its post-link input lacks >.
* Decode complete transcript lines without copying their bytes (#24546)
Decode each single owned Buffer synchronously; preserve concatenation for multipart records and remove a redundant tail array copy.
* Clear unused speech worker timers after errors and exit (#24924)
Call the existing idle timer cleanup when private current-worker state is cleared; keep current-worker guards, transcript/error order, deliberate warmth and stop deadline unchanged.
* Reuse documentation search excerpts during result navigation (#24574)
Memoize the existing pure excerpt renderer by complete raw text for the current result array, retaining original rendering as a miss fallback.
* Append subagent handoffs without recopying their message list (#24672)
Append each message reference to its private call-local scope list; retain the final merge and existing ordering.
* Cancel plugin retry timers when their fetch is replaced (#24700)
Reuse the existing timer clear block immediately after the fetch generation advances; preserve live retries and all state publications.
* Avoid rebuilding visible file paths for single-row selection (#24743)
Skip the visible path array only for keyboard replacement selection; the existing replacement branch never reads that order.
* Reuse prepared reads while searching saved agent conversations (#24535)
Use schema-complete session columns to enable the existing bounded statement cache without caching result rows.
* Reuse discovered mobile chunks during static traversal (#24774)
Iterate the existing live visited Set in FIFO discovery order instead of maintaining a second shifted queue; both actual callers own untouched esbuild JSON metadata.
* Skip unused image-size calculations in mobile web browser requests (#24941)
Use the existing mobile density budget only for mobile view; pass the existing constant to the existing assembler for web/default mode, where that argument is discarded. No cache, policy, request or native path changes.
* Skip diff analysis after a result has been discarded (#24711)
Move the unchanged pure render-limit calculation after both existing generation and section-token rejection fences.
* Skip late browser grab toasts after their surface closes (#24727)
Reuse the existing mounted-owner ref at the toast presentation entry while preserving all admitted extraction/screenshot and clipboard completion.
* Classify native chat waiting messages once (#24771)
Use the existing native filter traversal to populate a fresh waiting array, keeping the original predicate, policy, projection and output ordering while classifying each actual private slot once.
* Skip discarded Kanban pruning for an empty selection (#24782)
Guard only the existing open pruning branch when both its private selected Set and nullable anchor are empty; retain all original pre-guard projections and nonempty/anchor-only/public-helper behavior.
* Index discovered test files once during shard selection validation (#24532)
Verified selection plans previously validated each selected filename with a scan of all discovered files. One per-call Set now handles membership checks. Exact source/discovery checks, nonempty selection, fallback to all tests, shard balancing and manifests remain intact.
* Clear the watchdog benchmark heartbeat when CPU sampling fails (#24802)
Move the existing initial CPU observation and histogram enable inside the existing try/finally so their failures clear the sampler's owned heartbeat, preserving successful operation order and original errors.
* Reuse the parsed notification when leasing a push delivery (#24645)
* Reuse the parsed notification when leasing a push delivery
Pass the notification already parsed for the dismissal check into the existing private delivery builder.
* Reuse the parsed notification when leasing a push delivery
Pass the notification already parsed for the dismissal check into the existing private delivery builder.
* fix(accounts): canonicalize account removal to rm
Use account rm as the canonical removal command and keep account remove as an alias. Preserve the existing accounts.removeData RPC and the full original 63-path account component.
Original-Account-Source: cf71ae4cb6
Frozen-Account-Base: 08ee7ba9ef
Frozen-Integrated-Main: 53f9ea7839
Private validation source only; no ref or publication.
* Update README downloads badge
* Let retired-cache GC observations settle across the existing six-turn budget (#24967)
* Collect test-selection evidence when full unit tests fail (#24955)
* Collect advisory unit-selection evidence from failed full runs
* Trigger checks after retargeting the evidence fix to main
* Check each project repository once while filtering mobile cards (#24540)
Reuse exact raw source/slug matching decisions within one project filter call, preserving the matcher, membership, negative matches, output order and identity.
* Skip renderer callbacks after their request has ended (#24631)
Reuse the existing pending-request identity check before starting its deferred renderer callback.
* Decode single-piece saved-session tails without copying them (#24632)
Decode the existing owned Buffer directly when the EOF tail has one piece; preserve the existing concatenation for multiple pieces.
* Avoid irrelevant scroll-action searches in Mac snapshots (#24731)
Check the current pure action name before searching for vertical scroll actions in the same immutable array.
* Stop copying every retained browser page before attachment (#24553)
Find the first page/generation match directly in the existing insertion-ordered Map instead of copying all page references into an array.
* Reuse event byte sizes when relay watchers notify several clients (#24558)
Store the existing grouped numeric byte-size array on the already call-local watcher batch sizing object, for reuse by later chunking clients.
* Stop building unused import candidates in the test planner (#24611)
Replace the existing ordered extension map/find with a loop that forms each candidate only when its existing lookup is reached.
* docs(terminal): explain local macOS/Linux shell startup files
Document the actual login/interactive shell startup-file order and existing shell setup behavior.
Co-authored-by: brynnclaw <261708852+brynnclaw@users.noreply.github.com>
Co-authored-by: Neil <neil@stably.ai>
* fix(editor): recognize Ruby task and configuration files
Add Ruby task/configuration filenames to the existing generated language associations.
Co-authored-by: ggbdpq <ggbdpq@gmail.com>
Co-authored-by: Neil <neil@stably.ai>
* Speed up large Markdown Find and render oversized tables (#24948)
* Speed up large Markdown Find and render oversized tables
* Poll table preview geometry outside hidden renderers
* Preserve Markdown navigation through refresh and tab restoration
* Confirm Markdown restoration when refreshed content is ready
* Trace table refresh positions and update Unicode search reference
* Recognize queued measurement scrolls before restoring Markdown anchors
* Rebuild Markdown Find ranges after renderer components change
* fix(source-control): generate clean OpenCode messages locally and over SSH (#24613)
* fix(source-control): generate clean OpenCode answers on local and SSH hosts
Restack the original focused change onto current main, preserving every owned source and test blob and the merged CI contract and journal cleanup fixes.
Original-commit: 64bb15e3f2
fix(source-control): generate clean OpenCode answers on local and SSH hosts
Use configured models and JSON answer/error events, preserve run-first arguments, and handle the precise v2 variant rejection. Hydrate SSH execution-host PATH through the existing bounded login environment resolver before direct spawning.
Credits: andy-murr (PR #5197 SSH environment intent) and coelho-doti (PR #13065 argument-order intent).
Original-commit: 1b60ec5d11
fix(source-control): retry inline OpenCode model and variant options
Original-commit: 98fdecc7a3
Preserve OpenCode named errors without a data message
Restacked-from: 98fdecc7a3
Restacked-onto: f7b1f9d8be
* fix(ci): prevent concurrent pnpm refresh during mobile typechecks (#24776)
* fix(ci): run mobile typechecks without concurrent dependency refresh
* test(ci): check effective Linux E2E package list
* test(ci): preserve the mobile production compiler barrier
---------
Co-authored-by: Orca Integration Recovery <orca-validation@invalid.example>
* test(terminal): restore the live fish fixture prerequisites (#24947)
A restored pane waits for the initial status replay before subscribing to
PTY output. This fixture never settled that replay, so fish printed its
mode-2031 arm before the renderer connected. Its PTY API also omitted the
reset-input listener required by the serializer, aborting attachment.
Settle and dispose the existing startup-snapshot registration and provide
the same reset-listener mock used by the other PTY tests. The real fish
child-stdin assertions and timeouts remain unchanged. No production change.
* fix(shortcuts): defer TUI editing chords in terminal-first mode (#24640)
Restack the original focused change onto current main, preserving every owned source and test blob and the merged CI contract and journal cleanup fixes.
Original-commit: f707cde14a
fix(shortcuts): defer TUI editing chords in terminal-first mode
Original-commit: 0c6348e49e
docs(shortcuts): describe deferred preview terminal chords
Original-commit: be62c1b6c6
Align worktree history shortcut metadata with terminal conflict policy
Restacked-from: be62c1b6c6
Restacked-onto: f7b1f9d8be
* Register supervised Qoder China and Qwen Code (#24616)
* Add Qoder session history and search with real CLI coverage
* Allow the real Qoder marker file to end with a newline
* Keep Qoder tool output out of history previews and search
* Keep Qoder search pages readable by older clients
* Verify persisted Qoder history after a real generated and resumed task
* Negotiate Qoder filters before searching an older execution host
* Combine search client imports for the CI plugin gate
* Keep the relay search oracle aligned with legacy agent filtering
* Register supervised Qoder China and Qwen lifecycle integration
* Cover Qoder China mobile assets and mixed-host resume gates
* Verify Qoder provider tags against the older released wire parser
* Verify China and Qwen keep independent Windows hook scripts
* Verify Qoder registrations against the installed older Windows release
* test(qoder): align search capability contracts and pin old-host fencing
* fix(qoder): rank exact picker identities and command aliases first
* test(qoder): preserve the regional CLI shared icon expectation
Keep the full bundled-asset and no-remote-image checks, with an explicit
shared-logo basename for Qoder China. The map also works with older
catalog type unions.
* fix(qoder): align China catalog entry with fallback order
---------
Co-authored-by: Orca Integration Recovery <orca-validation@invalid.example>
* Use the measured pnpm lookup policy automatically in hosted root CI (#24951)
* Select lookup automatically for the measured hosted root-install profile
* Record hosted automatic-mode cold cache publication proof
* fix: bound remote generation setup and honor OpenCode option terminators
Count execution-host profile resolution inside the existing request deadline, cancel its waiter promptly, and pass only the remaining time to the child. Shared bounded profile probes keep their existing cache lifetime; the SSH transport margin is unchanged.
Read OpenCode output format from active final-argv options before -- so literal prompt arguments cannot select the JSON finalizer.
Fresh exact-source controls reproduce nine failures before; 139 related checks pass after, including primary/fallback delays, deadline boundaries, cancellation, parser metadata, and SSH lanes. Node typecheck and strict changed-file lint pass.
* Continue Antigravity IDE and 2.0 history in new CLI conversations (#24692)
* feat(antigravity): bridge IDE history into new CLI conversations
* fix(antigravity): preserve fresh-launch model and environment for IDE references
* fix(antigravity): forward IDE history opt-in through desktop IPC
* fix(antigravity): rebuild remote IDE reference startup on its host
* fix(antigravity): register IDE continuation action labels
* fix(antigravity): confine IDE references and bound metadata reads
* fix(antigravity): localize IDE continuation badges
* Preserve scanner service cache assertions and refresh Antigravity opening metadata
* Preserve Antigravity opening joins and target folder runtime authority
* fix(opencode): retry timed-out SSH plugin updates (#24666)
Preserve bounded retry behavior and the current-main status-envelope fields.
Original-PR: #24124
Reviewed-source: 103144f9c5
Co-authored-by: Justas Brazauskas <brazauskasjustas@gmail.com>
* fix(opencode): keep Go credentials private and resolve backend keys (#24615)
Preserve the complete credential storage, migration, IPC, Settings and rate-limit refresh change alongside standalone GLM plans, current-main database diagnostics and the reviewed unknown-backend environment correction. Keep native discovery cancellation third and selected environment fourth.
Original-topic-commit: 7903f1cddb
Original-topic-commit: 588117b5cf
Original-topic-commit: dedd4f8c86
Original-topic-commit: 6645dae104
Original-topic-commit: a25b80c02c1af7830b0e6a65e72d965b3ad98276
Restacked-from: a25b80c02c1af7830b0e6a65e72d965b3ad98276
Restacked-onto: b032867021
Co-authored-by: kespineira <kespineira@users.noreply.github.com>
Co-authored-by: kevimux <kevimux@users.noreply.github.com>
Reported-by: pullfrog
Reviewed-full-source: 849fe093073f4c1606bd65d79a0c725d955d0d1f
Native-helper-source: 80dbe23237
Reviewed-full-current-source: 36acb57d44adb3d378c0289c8c15f7da0fda214c
* fix(opencode): enforce deadline through executable startup
Keep synchronous Windows PATH resolution and child startup within the existing request budget.
---------
Co-authored-by: Orca Integration Recovery <orca-validation@invalid.example>
Co-authored-by: Justas Brazauskas <brazauskasjustas@gmail.com>
* fix(accounts): recover interrupted profile removal on startup
Scan private removal backups before quarantined cleanup, reuse the existing state schema to validate the exact UUID, and preserve registered or unmarked profiles. Mark account rm as destructive using the existing typo recovery policy. Retain the full original account component and public author ancestry.
Account-PR: 24636
Original-Account-Source: cf71ae4cb6
Reviewed-Public-Parent: 6abaf736e3
Frozen-Integrated-Main: 53f9ea7839
Private source only; no ref or publication.
* fix(persistence): preserve projectGroupOrder on restart for flat folder-scan groups
Retain saved project ranks when the project remains in its folder-scan group.
Co-authored-by: lurunzi <lurunzi@gmail.com>
Co-authored-by: Neil <neil@stably.ai>
* fix(runtime): reclaim temp files orphaned by interrupted mobile store writes
Reuse the existing stale temporary-file cleanup policy when each mobile store opens.
Co-authored-by: LDH1103 <ldh517525@gmail.com>
Co-authored-by: Neil <neil@stably.ai>
* fix(terminal): show actionable copy for missing folder workspace paths
Show the existing folder-path failure with concrete recovery instructions.
Co-authored-by: Wayn_Liu <wayntingliu@gmail.com>
Co-authored-by: Neil <neil@stably.ai>
* docs: reference local orca.yaml and .worktreeinclude
Document the existing workspace configuration, include/copy rules and sharing behavior.
Co-authored-by: Neil <neil@stably.ai>
* fix(orchestration): allow model selection for OMP workers
Pass an explicitly requested model through the existing OMP worker launch catalog.
Co-authored-by: Neil <neil@stably.ai>
* fix(cli): preserve primitive success results in JSON output
Check for an object before inspecting screenshot fields so primitive success results remain printable.
Related: https://github.com/stablyai/orca/pull/14735
Co-authored-by: Neil <neil@stably.ai>
Co-authored-by: VXNCXNX <VXNCXNX@users.noreply.github.com>
* Show loaded file changes before deleting a workspace
Expose up to ten already loaded changed paths without altering deletion counts, hydration or authorization.
Related: https://github.com/stablyai/orca/pull/22778
Co-authored-by: Neil <neil@stably.ai>
Co-authored-by: Michiel de Gooijer <mdgooijer@gmail.com>
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix(keybindings): record and match Option+digit shortcuts on macOS
Use the physical digit key for explicitly assigned Mac Option+digit shortcuts while retaining modifier checks.
Co-authored-by: marcuslannister <marcus@lannister.cc>
Co-authored-by: Neil <neil@stably.ai>
* fix(editor): don't throw closing a stale active file
Recover stale active-file selection within the owning workspace before choosing a surviving editor.
Related: https://github.com/stablyai/orca/pull/24715
Co-authored-by: m4air <m4air@m4airs-Air.localdomain>
Co-authored-by: Neil <neil@stably.ai>
* Fix Project Settings targeting for multiple local checkouts
Carry the selected checkout identity into the existing project Settings action.
Co-authored-by: fsmeier <1506919+fsmeier@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: Neil <neil@stably.ai>
* feat(documents): open CSV and TSV files from the OS
Extend existing OS document associations and delivery to CSV/TSV, preserving restoration and authorization.
Co-authored-by: Neil <neil@stably.ai>
* feat(cli): link GitHub and GitLab items on worktree create and set
Expose and validate the existing workspace link fields, preserving omitted values and provider identity checks.
Related: https://github.com/stablyai/orca/pull/22609
Co-authored-by: marco song <marco.song@mvlchain.io>
Co-authored-by: SongMarco <20613630+SongMarco@users.noreply.github.com>
Co-authored-by: Luca Critelli <lucacri@gmail.com>
Co-authored-by: Neil <neil@stably.ai>
* feat(sidebar): include folder workspaces in keyboard navigation
Use the rendered sidebar row order and host identity when cycling through folder and Git workspaces.
Related: https://github.com/stablyai/orca/pull/10555
Co-authored-by: Neil <neil@stably.ai>
Co-authored-by: JeongUk Park <jeongph.dev@gmail.com>
* fix(build): turn off MSBuild file tracking for Windows native rebuilds
Default Windows native rebuilds to TrackFileAccess=false while preserving explicit caller preferences.
Co-authored-by: B1nh M1nh <43268322+b1nhm1nh@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: Neil <neil@stably.ai>
* Fix Ctrl+M in Linux and Windows terminals
Keep the Minimize menu item but disable its accelerator registration on Linux and Windows.
Co-authored-by: Zhichang Yu <yuzhichang@gmail.com>
Co-authored-by: Neil <neil@stably.ai>
Co-authored-by: Ahmed Nagy <ahmednagy25t@gmail.com>
Related contribution: https://github.com/stablyai/orca/pull/24143
* fix(startup): read a nushell login PATH from $env.PATH
Use Nushell login command syntax and preserve the actual login PATH value.
Related: https://github.com/stablyai/orca/pull/22677
Co-authored-by: Kh05ifr4nD <meandSSH0219@gmail.com>
Co-authored-by: Neil <neil@stably.ai>
* fix(cursor): run local hooks through sh for non-POSIX login shells
Keep POSIX hook syntax inside one quoted sh command so the login shell can invoke it safely.
Related: https://github.com/stablyai/orca/pull/22663
Co-authored-by: Kh05ifr4nD <meandSSH0219@gmail.com>
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Neil <neil@stably.ai>
* Fix word wrap for both panes in side-by-side diffs
Forward wrapping to both diff panes through the existing editor option path and clean up listeners.
Co-authored-by: Wooseong Kim <innocarpe@gmail.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Neil <neil@stably.ai>
* fix(monaco): highlight Svelte block closers inside markup
Return Svelte block closers to the existing markup tokenizer state.
Co-authored-by: Wooseong Kim <innocarpe@gmail.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Neil <neil@stably.ai>
* fix(repos): keep active clone dialog open on outside clicks
Ignore accidental outside dismissal only while cloning; Escape, Close and Back still cancel.
Related: https://github.com/stablyai/orca/pull/24581, https://github.com/stablyai/orca/pull/23430
Co-authored-by: Neil <neil@stably.ai>
Co-authored-by: Nawapat Buakoet <nawapat.b@covest.finance>
* fix(editor): keep chat visible after closing Markdown tabs
Count remaining unified chat tabs before clearing workspace selection during editor close.
Related: https://github.com/stablyai/orca/pull/24273, https://github.com/stablyai/orca/pull/23760
Co-authored-by: Neil <neil@stably.ai>
Co-authored-by: Wooseong Kim <innocarpe@gmail.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
* Honor typed starting numbers in chat ordered lists
Forward the existing parsed ordered-list start attribute to both chat Markdown renderers.
Related: https://github.com/stablyai/orca/pull/19765
Co-authored-by: Neil <neil@stably.ai>
Co-authored-by: Frederic Barthelemy <git@fbartho.com>
* Normalize base source once while checking changed-code diagnostics (#24557)
Reuse the exact existing moved-code matching algorithm with run-local lazy normalization of immutable base source blocks across diagnostics.
* Support real OpenCode sessions in native Chat (#24647)
* Use bounded OpenCode context for vault session continuation
OpenCode database and synthetic row paths are not text transcripts. Use the
vault preview or captured pane context, preserving actual transcript paths
containing a hash and supporting both OpenCode lanes and Windows paths.
Adapted the intent of #11859 and extended it to actual installed v2 vault rows.
Co-authored-by: mrcha033 <mrcha033@users.noreply.github.com>
* Read real OpenCode sessions in terminal-backed native Chat
Reuse the bounded AI Vault SQLite worker for v1 and v2 session pages and live updates. Keep terminal input as the real execution path and pace OpenCode Stop through its two-Escape interrupt.
Co-authored-by: xodmd45-ctrl <xodmd45-ctrl@users.noreply.github.com>
* fix(opencode): publish approval cards for permission requests
* Send OpenCode native approval through its Enter selector
* Resolve mobile Chat readability for folder workspaces
* Bound OpenCode part batches and preserve v2 image attachments
* Prefer live migrated OpenCode sessions over legacy copies
* Consolidate mobile Chat eligibility test imports
* Consolidate OpenCode SQLite protocol type imports
* Update native chat settings contract for both OpenCode agents
* fix(native-chat): reconcile bounded OpenCode transcript reads
* fix(native-chat): dispatch OpenCode questions safely
* fix(native-chat): keep native discovery and transcript windows current
* feat(accounts): link standalone GLM Coding Plans (#24618)
* feat(accounts): link standalone GLM Coding Plans
Adapt the reviewed GLM accounts contribution to current main, retain Antigravity behavior, guard late credential results, expose storage protection, and redact quota errors.
Co-authored-by: Luchong <lu740528977@gmail.com>
* fix(accounts): retain GLM credential results during quota refresh
* fix(accounts): make GLM credential editing desktop-only
* fix(accounts): mirror the host GLM site in paired clients
* fix(accounts): report unknown GLM host details and split web settings tests
Apply the independently reviewed Accounts correction from697284a without the v2 adapter commits. Preserve the saved-key store and serialized write behavior.
* fix(zcode): ship required GLM account translation entries
* chore: record GLM reconciliation hook validation
* chore: validate installed GLM commit hooks
* test: complete GLM account fixtures and web API inventory
---------
Co-authored-by: Luchong <lu740528977@gmail.com>
* fix(native-chat): route transcript requests through shared SQLite worker
* fix(ci): prevent concurrent pnpm refresh during mobile typechecks (#24776)
* fix(ci): run mobile typechecks without concurrent dependency refresh
* test(ci): check effective Linux E2E package list
* test(ci): preserve the mobile production compiler barrier
---------
Co-authored-by: Orca Integration Recovery <orca-validation@invalid.example>
* test(terminal): restore the live fish fixture prerequisites (#24947)
A restored pane waits for the initial status replay before subscribing to
PTY output. This fixture never settled that replay, so fish printed its
mode-2031 arm before the renderer connected. Its PTY API also omitted the
reset-input listener required by the serializer, aborting attachment.
Settle and dispose the existing startup-snapshot registration and provide
the same reset-listener mock used by the other PTY tests. The real fish
child-stdin assertions and timeouts remain unchanged. No production change.
* fix(shortcuts): defer TUI editing chords in terminal-first mode (#24640)
Restack the original focused change onto current main, preserving every owned source and test blob and the merged CI contract and journal cleanup fixes.
Original-commit: f707cde14a
fix(shortcuts): defer TUI editing chords in terminal-first mode
Original-commit: 0c6348e49e
docs(shortcuts): describe deferred preview terminal chords
Original-commit: be62c1b6c6
Align worktree history shortcut metadata with terminal conflict policy
Restacked-from: be62c1b6c6
Restacked-onto: f7b1f9d8be
* Register supervised Qoder China and Qwen Code (#24616)
* Add Qoder session history and search with real CLI coverage
* Allow the real Qoder marker file to end with a newline
* Keep Qoder tool output out of history previews and search
* Keep Qoder search pages readable by older clients
* Verify persisted Qoder history after a real generated and resumed task
* Negotiate Qoder filters before searching an older execution host
* Combine search client imports for the CI plugin gate
* Keep the relay search oracle aligned with legacy agent filtering
* Register supervised Qoder China and Qwen lifecycle integration
* Cover Qoder China mobile assets and mixed-host resume gates
* Verify Qoder provider tags against the older released wire parser
* Verify China and Qwen keep independent Windows hook scripts
* Verify Qoder registrations against the installed older Windows release
* test(qoder): align search capability contracts and pin old-host fencing
* fix(qoder): rank exact picker identities and command aliases first
* test(qoder): preserve the regional CLI shared icon expectation
Keep the full bundled-asset and no-remote-image checks, with an explicit
shared-logo basename for Qoder China. The map also works with older
catalog type unions.
* fix(qoder): align China catalog entry with fallback order
---------
Co-authored-by: Orca Integration Recovery <orca-validation@invalid.example>
* Use the measured pnpm lookup policy automatically in hosted root CI (#24951)
* Select lookup automatically for the measured hosted root-install profile
* Record hosted automatic-mode cold cache publication proof
* fix(native-chat): keep OpenCode history usable at read limits
Continue past failed database probes while preserving discovery cancellation.
Verify rows displaced by a capped tail before deciding whether to replace history.
Represent oversized v1/v2 rows with the existing omission text and stable cursors.
* Continue Antigravity IDE and 2.0 history in new CLI conversations (#24692)
* feat(antigravity): bridge IDE history into new CLI conversations
* fix(antigravity): preserve fresh-launch model and environment for IDE references
* fix(antigravity): forward IDE history opt-in through desktop IPC
* fix(antigravity): rebuild remote IDE reference startup on its host
* fix(antigravity): register IDE continuation action labels
* fix(antigravity): confine IDE references and bound metadata reads
* fix(antigravity): localize IDE continuation badges
* Preserve scanner service cache assertions and refresh Antigravity opening metadata
* Preserve Antigravity opening joins and target folder runtime authority
* fix(opencode): retry timed-out SSH plugin updates (#24666)
Preserve bounded retry behavior and the current-main status-envelope fields.
Original-PR: #24124
Reviewed-source: 103144f9c5
Co-authored-by: Justas Brazauskas <brazauskasjustas@gmail.com>
* fix(opencode): keep Go credentials private and resolve backend keys (#24615)
Preserve the complete credential storage, migration, IPC, Settings and rate-limit refresh change alongside standalone GLM plans, current-main database diagnostics and the reviewed unknown-backend environment correction. Keep native discovery cancellation third and selected environment fourth.
Original-topic-commit: 7903f1cddb
Original-topic-commit: 588117b5cf
Original-topic-commit: dedd4f8c86
Original-topic-commit: 6645dae104
Original-topic-commit: a25b80c02c1af7830b0e6a65e72d965b3ad98276
Restacked-from: a25b80c02c1af7830b0e6a65e72d965b3ad98276
Restacked-onto: b032867021
Co-authored-by: kespineira <kespineira@users.noreply.github.com>
Co-authored-by: kevimux <kevimux@users.noreply.github.com>
Reported-by: pullfrog
Reviewed-full-source: 849fe093073f4c1606bd65d79a0c725d955d0d1f
Native-helper-source: 80dbe23237
Reviewed-full-current-source: 36acb57d44adb3d378c0289c8c15f7da0fda214c
---------
Co-authored-by: mrcha033 <mrcha033@users.noreply.github.com>
Co-authored-by: xodmd45-ctrl <xodmd45-ctrl@users.noreply.github.com>
Co-authored-by: Luchong <lu740528977@gmail.com>
Co-authored-by: Orca Integration Recovery <orca-validation@invalid.example>
Co-authored-by: Justas Brazauskas <brazauskasjustas@gmail.com>
* fix(accounts): protect registered UUID case variants during removal recovery
Compare validated account UUID identities without changing stored IDs or filesystem paths. Protect registered originals and quarantines, including explicit retry variants, and accept equivalent UUID spelling in a valid removal backup. Retain the complete account component and published contributor ancestry.
Private source only; no index, ref, or public mutation.
* Drain removal fixture jobs before resetting and deleting their records (#24977)
* Reuse measured Electron preparation for current Terminal Perf refs (#24968)
* feat(jcode): add Jcode as a supported TUI agent with managed hooks
Ports PR #10521 onto current main: agent catalog, managed hook service,
agent-status listener, session resume, AI Vault parser, per-pane daemon
isolation, and Source Control AI support.
Co-authored-by: Neil <neil@stably.ai>
* feat(jcode): evidence-backed status pipeline (turn_start, live pre_tool, questions)
Co-authored-by: czzczz <chanzrz_zbf@foxmail.com>
* fix(jcode): register vault fixture, dynamic model discovery, script refresher
Co-authored-by: czzczz <chanzrz_zbf@foxmail.com>
* fix(jcode): keep finished-turn detail so completion notifications fire
Co-authored-by: czzczz <chanzrz_zbf@foxmail.com>
* fix(jcode): show the prompt in agent rows instead of jcode's repainting title
Co-authored-by: czzczz <chanzrz_zbf@foxmail.com>
* fix(jcode): pre-warm the per-pane daemon so a cold runtime dir cannot time out
Co-authored-by: czzczz <chanzrz_zbf@foxmail.com>
* fix(jcode): stop the OSC color skip from crashing every pane connect
Co-authored-by: czzczz <chanzrz_zbf@foxmail.com>
* refactor(jcode): fold three reverse-scan copies into one, reuse the shared hook POST
The jcode journal reader, the Claude transcript reader and the Command Code
transcript reader each carried their own copy of the same reverse chunked line
scan; they now share one tested helper. jcode's managed hook script drops its
hand-rolled curl for buildPosixAgentHookPostCommand, which also gains it the
raw-JSON transport and the --noproxy guard the bespoke copy was missing.
Co-authored-by: czzczz <chanzrz_zbf@foxmail.com>
* fix(jcode): narrow dynamic reads with predicates, pin the Windows hook shape
CI's anti-slop audit rejects Reflect.get: parse dynamic input into a named type
instead. Adds Windows script-shape tests too, since Windows is the platform this
change could not be exercised on directly.
Co-authored-by: czzczz <chanzrz_zbf@foxmail.com>
* test(jcode): measure the gate against the synchronous path, not the clock
The absolute 1s bound was the flake CI shard 3/8 hit: it is tight enough to catch
a synchronous gate on an idle laptop and too tight on a loaded runner. Measuring
the same POST both ways on the same machine makes the claim a ratio, which is what
the test is actually about. Mutation-checked: a synchronous gate reads 6120ms
against a 1517ms observer.
Co-authored-by: czzczz <chanzrz_zbf@foxmail.com>
* test(jcode): drop the wall-clock gate assertion
The bound was machine-speed sensitive and flaked on CI shard 3/8 at 1525ms. The
structural assertions (detached gate branch, foreground observer branch) and the
no-hang stdin drain cover the same contract without a timer.
Co-authored-by: czzczz <chanzrz_zbf@foxmail.com>
* fix(jcode): address CodeRabbit review on #22539
- Windows posted no payload at all: the shared builder reads `payload@-` from
stdin, which the gate has already drained and observer hooks never receive, so
every Windows event was dropped. Write the env var to a temp file and pipe it.
- The daemon pre-warm never fired for daemon-host spawns, which is the default
local path; it now runs there too, and from the final env so the daemon gets the
hook port and token.
- A failed runtime-dir mkdir took down every local terminal, jcode or not.
- removeJcodeManagedHooks matched the raw line, so a user hook whose comment
mentioned the managed script was deleted; matching on Windows never worked.
- A managed entry left by a copied home or a platform switch is now repointed
instead of being reported as user-owned forever.
- The OSC colour skip only checked launchAgent, so a command- or telemetry-named
jcode pane still leaked the reply into its composer.
- `['hooks']` and a commented scalar are recognised, instead of appending a
second [hooks] table that makes jcode reject the whole config.
- A failed tool's error is marked as tool output rather than agent prose.
- The vault keeps a session's stored name, counts its tokens, and skips
background_task and [Scheduled task] turns.
Co-authored-by: czzczz <chanzrz_zbf@foxmail.com>
* fix(jcode): read the journal once per turn, key prompts by byte offset
The jcode prompt reader ran a synchronous bounded file scan plus a JSON
parse on every hook event. jcode blocks on pre_tool, so a turn that ran
four tools charged the user eight scans of latency it did not need — the
prompt cannot change inside a turn.
- Cache the journal read per pane, refreshed on the turn boundary that
can change it. The cache holds the whole evidence record, since
hasExplicitUserPrompt needs the transcript-evidence flag and not just
the text, and it joins the existing pane-scoped lifecycle (close,
rename, reset) rather than living in a module singleton.
- Key a journal prompt by its absolute byte offset instead of its
region-local line index. The backward scan windows the file from EOF,
so appending shifted every boundary and reminted the key for a prompt
that never moved; a repeated turn_end then slipped past the same-hash
dedupe as a second done event with duplicate telemetry.
- Pin the platform in the daemon pre-warm tests. The pre-warm is a no-op
off POSIX, so the dedupe and retry cases would have passed vacuously
on a Windows runner.
Co-authored-by: czzczz <chanzrz_zbf@foxmail.com>
* fix(jcode): quote the managed hook path, drop tools from patch prompts
Three fixes, all on paths this PR could not exercise locally.
The managed hook command was stored as a bare path. jcode tokenizes that
string shell-style before exec'ing it directly (parse_hook_command,
crates/jcode-terminal-launch/src/lib.rs): unquoted whitespace splits, and
every unquoted backslash is consumed as an escape. So on Windows
`C:\Users\me\.orca\agent-hooks\jcode-hook.cmd` reached exec as
`C:Usersme.orcaagent-hooksjcode-hook.cmd` and no hook fired at all, and a
POSIX home with a space split into two arguments. Store the path
single-quoted (verbatim, backslashes included), falling back to double
quotes for a path containing a single quote. Existing bare entries are
already repointed by the stale-key path, and getStatus accepts both forms
so the repair is not reported as a user-owned hook. The quoting helper was
previously dead code that only tests called; the three production sites
now use it. isJcodeManagedCommand also normalizes separators, since a
`/`-only needle never matched a Windows entry.
Commit-message generation feeds a staged patch to `jcode run` as the
prompt — attacker-influenced text — while jcode's default profile exposes
shell, read, write, and MCP. Pass `--tool-profile none`, which resolves to
an empty allowed-tool set in jcode's config (base_allowed_tools), matching
the read-only posture claude (plan) and codex (read-only) already take.
docs/reference/jcode-hook-events.md was never actually in this PR: the
repo ignores docs/** and tracks reference docs by allow-list only, so the
captured-payload evidence four source comments point at was silently
dropped. Allow-list it.
Co-authored-by: czzczz <chanzrz_zbf@foxmail.com>
* test(jcode): pin the tool-profile flag in the generation plan too
The argv assertion lives in two places; --tool-profile none only landed in
one, so the plan test still expected the unrestricted argv.
Co-authored-by: czzczz <chanzrz_zbf@foxmail.com>
* fix(text-generation): refuse an oversized argv prompt on Linux too
The pre-spawn size guard only ran on Windows. Linux caps a single argv
entry at MAX_ARG_STRLEN (32 pages, 128 KiB on a 4-KiB-page host) and
execve fails with E2BIG past it, so an agent that delivers the whole
prompt as one argument — jcode, and the other argv-delivery agents —
failed on a large staged diff with an error the user could not act on.
The cap is per-argument and in bytes, which is why it is not the Windows
line budget: 40k chars trips Windows and is nowhere near the Linux limit,
so folding them together would have refused prompts Linux runs fine.
Co-authored-by: czzczz <chanzrz_zbf@foxmail.com>
* fix(jcode): register in main's remote-installer guard, drop our duplicate
Rebasing onto 853 commits of main surfaced two things the earlier branch
had hidden.
main already owns a guard for the issue-#7253 bug class
(`remote-hook-service-registry-coverage.test.ts`). This branch had added a
second, near-identical one — a parallel implementation of a test that
already existed, which is what AGENTS.md's reuse rule is about. Deleted
ours and registered jcode in main's, which is the one that has kept pace
with every agent added since.
Also fixes a missing separator in the mobile icon map. `pnpm tc` does not
cover `mobile/`, so only the session-route closure suite caught it.
Co-authored-by: czzczz <chanzrz_zbf@foxmail.com>
* fix(jcode): decompose the four files jcode pushed over max-lines
Adding an agent tipped four modules past their line budget. AGENTS.md
forbids a `max-lines` disable or a per-file bump, so each is split on a
real seam rather than silenced:
- agent-catalog.tsx keeps `AgentIcon`, which 70+ files import, and the
rows move out. The rows alone exceed the 300-line budget a `.ts` file
gets, so they follow the primary/secondary split this repo already uses
for commit-message agent specs.
- getAgentResumeArgv -> agent-resume-argv.ts, re-exported so the 18 call
sites keep one import path.
- isDiscoverableSessionFile/pathSegments -> session-file-discovery.ts.
- remoteCodexSources -> remote-session-scanner-codex-sources.ts; Codex is
the one remote agent with two CODEX_HOME roots.
Also:
- Records the readiness-census baseline jcode now needs. main added that
gate while this branch was out; the fixture is the recorded 74-case
matrix, not a hand-written one.
- Restores two entries a rebase resolution silently dropped from
config/tsconfig.cli.json (gitlab/project-ref-parser,
startup/shell-path-probe). Nothing to do with jcode; losing them was a
conflict-resolution mistake on this branch.
Co-authored-by: czzczz <chanzrz_zbf@foxmail.com>
* feat(native-chat): open structured chats on the paired Orca server that owns the workspace (#24205)
* fix(native-chat): a host admits structured sessions by client capability, not its own chat setting
A host's experimentalStructuredNativeChat decided whether any paired client could reach
agentSession.* at all, and whether session.tabs.* showed it structured tabs. That setting is the
host user's own launch preference: whether a new agent opens as a chat or a terminal is decided by
whoever launches it. Using it as admission control meant a client whose own preference was
"structured chat" was refused on a host whose preference was "terminal", and chats opened while
the setting was on were withheld from mobile once it was turned off.
The gate now asks one thing: did the client advertise agent-session.structured.v1 (in-process
callers negotiate nothing and are always admitted). Tab projection and restore follow the same
rule. With the setting no longer gating anything, the separate cleanup gate (close, cancel,
unsubscribe, release), which existed only so those kept working after the setting was switched
off, is identical to the main gate and is folded into it. The settings listener that republished
tabs when the setting changed is removed, since projection no longer depends on it.
The host setting still picks the default for launches that start on the host itself
(agent.launch from mobile, orchestration worker-start).
* fix(native-chat): the desktop declares structured chat support to paired hosts
The desktop renderer advertised agent-session.structured.v1 (and the Claude, turn-item and
background-task capabilities that go with it) to its own main process but not to a paired Orca
server. The server therefore refused every agentSession.* call from the desktop and stripped
structured chat tabs out of the tab list it published to it, so a structured chat running on a
paired server never appeared on the desktop, even though the renderer already mirrors a host's
agent-session tabs and drives each one against the server that owns its workspace.
The same renderer reads structured chats on either host, so the remote Electron list now carries
the same structured-session capabilities as the local one, and the capability test pins that
nothing is advertised only locally.
* feat(native-chat): open structured chats on the paired server that owns the workspace
With the structured-chat default on, an agent launched in a workspace that lives on a paired Orca
server always opened as a terminal (or the terminal-backed chat view). Three things kept it off
the structured path: the launch check refused every host but this machine, a remote workspace was
handed to the host-published terminal path before the structured route was even considered, and
the structured launch pipeline sent create and every follow-up call to this machine's runtime.
A workspace's owning runtime is fixed, so the pipeline now derives it from the workspace instead
of assuming this machine (structured-agent-session-owner.ts, the same derivation the chat pane
already uses to read a session). The launch intent carries that target; the pre-create support
check, create, the publication check and fence read, the launch prompt send, held option picks,
the "focus this chat" marker, the placeholder tab's host, and tab close/purge all use it.
The launch check now accepts a paired server and asks that server's own capabilities (read from
the status the client already cached for it) rather than this machine's. An SSH workspace stays
terminal-backed: no Orca runtime runs there. The host still answers createSupport before
anything is created, so an older server that refuses shows the failure in the chat tab.
A chat the user closed before its create landed is now also retired on the paired server when it
publishes, as the local sync already does. Orchestration workers placed on another runtime are
unchanged: federation creates terminal agents only.
* fix(native-chat): negotiate client-chosen launch mode so released phones and old servers keep terminals
Hosts advertise agent-session.structured.client-launch-mode.v1: they admit
structured sessions by client capability alone. A remote client that does
not advertise it (phones released before agent.launch) asks createSupport
to pick the launch mode, so the host keeps answering that with its own
setting, exactly as before. Cleanup methods keep their own named gate so a
future admission condition cannot make close or cancel refusable.
* refactor(runtime): keep the Electron client capability list in its own module
protocol-version.ts is at its line budget; the list is what the desktop
advertises to paired hosts, not the host's own contract.
* fix(native-chat): the desktop declares it picks each launch mode itself
Paired hosts and the desktop's own main process then answer createSupport
by the workspace rather than by their own chat setting.
* fix(native-chat): pin each structured chat to the host it was launched on
- Route: a paired server opens a chat only when it advertises the
client-chosen launch mode; an older server keeps its terminal. Its
capabilities come from the store's host status, not the compatibility
cache that is empty after boot or reconnect.
- A launch command override is this machine's: the route applies it only
locally, and a host's createSupport refuses on its own override.
- The owning host is resolved once, from the same value the route used,
and carried on the launch intent, its persisted record (legacy records
load as local), the provisional tab and every mirrored chat tab. Close,
purge, retry and reload read it instead of re-deriving it from a
worktree id two hosts can share; an owner that cannot be named refuses.
- Cancellation tombstones record their host: only that host's
authoritative inventory retires one, restored cleanup closes it there,
and a paired host's tombstone expires after 30 days if it never answers.
- A paired server's frame settles launches it published, as the local
inventory already does for this machine.
* fix(native-chat): a paired server that declines a chat opens its terminal instead
createSupport only reads, so both of its non-answers are settled before
anything is created:
- A paired server that answers it cannot run the chat (a WSL repo, a
Claude account mismatch, its own launch command override) closes the
chat tab and opens the terminal the route would have chosen, with a
notice saying why. This machine's own decline stays a failed chat.
- A host that could not be asked closes the chat tab and leaves one
failure toast, instead of a lingering "could not confirm" chat.
* test(native-chat): a provisional chat carries its launch's host and hands pre-create failures on
* chore(native-chat): justify the two type assertions this change's lines touch
* test(native-chat): state why each staged test fixture is cast
* fix(native-chat): chats that already exist keep showing whatever the chat setting says
The structured chat setting decides only what new agents open as. With it
off, this machine's structured chats used to be hidden while the host,
which no longer reads the setting, still reported them to the workspace
activation gate, so a workspace holding only a chat opened empty. The
local chat mirror and its startup restore now run whatever the setting
says, the continue-after-restart offer follows the chats that exist, and
the setting's copy says it applies to new agents.
* fix(native-chat): the browser client keeps its host terminal on paired servers
A browser client whose own preferences turn structured chat on took the
structured route for every paired-server workspace, but its handshake
never says it reads structured sessions, so the server refused the chat
and the user got a failed chat tab where a host terminal used to open.
The route for a paired host now also asks what this client advertises to
it: the desktop's list does, the browser client's does not. Its handshake
list is now a named constant the route reads, so the two cannot drift.
The chat setting's copy now says it runs on paired Orca servers too;
WSL and SSH hosts still use terminal chat.
* fix(native-chat): a retried launch a paired server declines opens its terminal too
A launch restored after a reload settles only through its Retry, so a
declining paired server left a failed chat there while a first launch got
the server's terminal and a notice. The chat's Retry now hands the same
pre-create failures to the same replacement, carrying the prompt the
launch had staged.
* refactor(native-chat): a paired host's cancelled-chat record ends on its 30-day TTL
The paired census re-read a host's whole inventory after every
authoritative frame to retire tombstones, and a tombstone restored after
a reload needed a second such frame, so in practice it retired nothing.
A tombstone guards a random session id and is inert once stale; the chat
is already closed on its host whenever a frame shows it. The census, its
trigger in the mirror layer and its cleanup are removed; the owner-scoped
tombstones, close-on-sight, the TTL and publication marking from frames
stay.
* fix(native-chat): a chat's pane and status read from the host recorded on its tab
The chat pane and its sidebar status still derived the host from the
workspace id, which two hosts can share; a paired chat in a non-active
same-id workspace was read from this machine. Both now read the owner
stamped on the tab, as close, purge and publication already do.
* fix(native-chat): "Resume in chat" follows the terminal resume's host rule
Agent Session History offered "Resume in chat" for a conversation
recorded on this machine into a paired server's workspace, where its
transcript does not exist. A chat now resumes a conversation only on the
host that recorded it, as the terminal resume does, and that host is the
one asked whether it can resume history.
* fix(native-chat): the chat setting says older paired servers keep terminal chat
* test(native-chat): pin that a host advertises the client-chosen launch mode
* fix(native-chat): mirror this machine's chats only where it holds them
Round 1 ran the local chat mirror for everyone so existing chats show
whatever the setting says. That gave every desktop a permanent
session-tabs listener, which turns on the runtime's phone replication
paths, plus two full session-tab censuses at startup, and made the
browser client mirror its remote host a second time.
The runtime now says whether it holds structured chats: its structured
host is built only when saved chats were restored at startup or a client
created one here, and it announces the moment one is built. The mirror,
the startup restore and the continue-after-restart offer run only when
the setting launches chats or the host holds some, and never in the
browser client. A chat a paired client creates here with the setting off
still appears at once. The chat behaviour settings show wherever chats
exist, and the setting's copy says it picks what new agents open as. The
toggle-off teardown this made dead is removed.
* test(native-chat): route a paired-server launch over the capability lists both sides really advertise
* test(native-chat): record install listeners without a cast
* fix(native-chat): a paired server admits a chat before any of it exists here
The desktop opened a paired server's chat tab, launch record, queued
prompt and focus intent before asking the server, so a "no" needed a
replacement that undid and redid all of it, and every piece it missed
was a bug: the workspace deselected, the caller told "failed" while a
terminal ran its prompt, the caller's arguments and other queued prompts
lost, and a create whose reply was lost treated as never sent.
A paired launch now asks the server first and commits nothing until it
answers. Admitted opens the chat as before. Declined runs the caller's
own launch as the server's terminal, with the existing notice (a resume
fails instead, having no terminal equivalent). Unreachable opens nothing
and names the server in one toast. The new-tab launcher reports the
host's surface for paired workspaces, as it did before paired chats,
with the prompt delivery of whichever surface got the prompt. The
replacement and its error classes are gone, and the probe inside a
launch is back to its old meaning: a "no" is a failed chat with Retry,
and no answer leaves "Could not confirm" with Retry and the prompt kept,
here as on this machine.
* fix(native-chat): mirror this machine's chats only once it holds one, not once its host is built
Session history, resume preparation, terminal resume commands and replay-safe phone launches all
build the structured host for users who never had a chat, which turned on the chat mirror and the
structured-only settings rows until the next restart. The signal is now derived from the host's
records (or a records file still owed its import) and pushed when the first chat is restored or
created. A throwing listener no longer fails the install that fired it.
* fix(native-chat): a fork's reveal never seeds a terminal beside the surface the launcher opens
Forking into a paired-server workspace revealed it as if nothing would open there, so the reveal
created a blank host terminal beside the forked chat (and beside a forked agent terminal on main).
The launcher always opens the fork's surface itself, so the reveal now says so for every surface,
as the fix-checks launch already does.
* fix(native-chat): a declined direct launch keeps the caller's CLI args; an unreachable resume toasts once
When a paired server declines a "Fix checks" chat in a new workspace, the terminal that opens
instead now carries the recipe's saved CLI arguments, launch platform and launch source, as the
terminal route did. "Resume in chat" to a server that cannot be reached showed the admission's
"Could not reach" toast and the vault's generic one; the admission marks its failure notified and
the vault adds nothing.
* fix(native-chat): a declined background create opens its terminal without switching workspaces
Since #23974 a worktree create the user moved away from must not pull them onto the new
workspace. When a paired server declined that create's chat, the fallback terminal opened as a new
agent tab, whose host create selects the workspace. The create now opens its own agent terminal the
way main's background branch does: in place from the request's startup plan (so its CLI args carry),
without selecting the workspace. A create the user is still watching keeps the new-tab fallback.
* test(native-chat): name the launch's host in main's new outbox fence test
Main's new staging-failure test calls settleStructuredAgentLaunchPrompt without the target this PR
made required; it is a local launch, as in the sibling tests.
* fix(native-chat): a paired server's new chat shows no model until the server reports the one it started
A chat on a paired server starts with the server's saved model and options, but the picker showed
this desktop's saved selection (or the catalog default) until the server reported a model, and a
pick made in that window was remembered on the server under that guessed model. A paired launch
now carries no desktop seed, and until the server reports its model the picker names no model and
takes no picks. Local chats are unchanged.
* test(native-chat): seed the paired repo without a cast
The repo literal already satisfies Repo, so the changed-lines cast gate has nothing to excuse.
* feat(native-chat): createSupport reports the saved selection a new chat on this host starts with
A chat on a paired server starts with the server's saved model and options, which the desktop could
not read, so its picker showed a guess. createSupport's answer, which the desktop already waits for
before a paired launch, now also carries that seed as a new optional field (older clients ignore it).
Create and createSupport read it through one resolver so they cannot drift.
* fix(native-chat): a paired server's new chat shows the selection the server will start it with
The paired server now names its saved model and options in the admission answer the desktop
already waits for. That seed goes into the launch intent and its persisted record, so the picker
shows the server's model at once, stays pickable like a local chat, and remembers picks on the
server under that model; a reload shows the same. The locked picker remains only for a server too
old to name a seed.
Also moves host admission and launch-outcome tracking into their own modules: the latest main
merge left structured-agent-session-launch.ts over the max-lines limit.
* test(native-chat): expect the launch intent's new seed argument in exact-call assertions
* refactor(protocol): move the Electron remote client capability list into its own module
Merging main left protocol-version.ts one line over the max-lines limit on this branch. The list of
capabilities the desktop advertises to a paired host moves, unchanged, into
electron-remote-runtime-client-capabilities.ts, the module the next PR in the stack already uses
for it; importers point there.
* fix(native-chat): a paired chat with no saved server model is pickable; Retry shows the server's current seed
A server whose user never saved a chat model sends no seed, and the desktop showed a locked,
model-only picker for it, although that is the common case: no server that can admit a paired chat
predates the seed field. Such a chat now behaves like a local chat with no saved model: the CLI
default, pickable. The lock and its snapshot helper are gone.
Retry kept the first admission's seed while the create probe, which already runs on every attempt,
reported the server's current one and dropped it. The probe's seed now replaces a paired launch's
seed and the picker's, so a retried chat shows what its create will run.
* test(cross-version): stub the launch seed resolver createSupport now reads
* test(protocol): pin the desktop capability divergence against what a paired server receives
Every paired transport sends the shared remote base plus the Electron list, so the
divergence test now compares that union with the renderer's local list instead of
the declared Electron list. A capability added only to the shared base can no
longer slip past it. The two base-only capabilities it surfaced are recorded:
skills.install-result.v2 has no local caller; the authoritative-inventory label is
read by the local tabs sync but dropped by main, and is marked unsettled.
The turn-item and both background-task-stop capabilities were already sent through
the shared base, so the Electron list no longer repeats them. The wire set is
unchanged; this PR's real change on the wire is structured.v1, the Claude
structured capability and the client launch-mode capability.
* fix(native-chat): the desktop tells its own host it picks each launch mode, so retrying an existing chat works with the setting off
* docs(native-chat): name the real exit for the released-phone createSupport rule
* fix(native-chat): the route reads the capabilities a paired host actually receives
The renderer decided whether a paired host would admit a chat from the desktop's Electron list, but
every desktop transport sends that list plus the shared remote base. They agreed only because the
route's checks happened to sit in both. The route input is now built with the same
remoteRuntimeClientCapabilities the transports use (the browser client already sends its list as is),
a…
* fix(packaging): ship serve-sim only in macOS builds (#25128)
serve-sim drives the iOS Simulator, which exists only on macOS. Since 0.1.47
(#24228) it carries a Mach-O addon, and Windows signing rejects every *.node
that is not a PE file, so the v1.4.220 Windows build failed at SignPath.
* Recover working local forge CLIs behind broken PATH launchers
Recover a working local forge CLI when an earlier PATH launcher is broken. Bound executable probes and reuse the verified selection for native operations without replaying authentication or user requests.
Fixes#22975
Co-authored-by: Aashish <145881415+aashish254@users.noreply.github.com>
* Retain browser feedback notes across navigation and reload
Keep saved browser feedback notes across navigation and reload. Retire old document markers and cancel pending captures at document boundaries while preserving the existing note cleanup and delivery behavior.
Co-authored-by: E-BlackTV <emirhancelik1133@icloud.com>
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
* fix(search): negotiate supported agents across mixed host versions (#25009)
Keep older request and reply parsers usable while current peers retain all supported history.
Co-authored-by: nwparker <nwparker@users.noreply.github.com>
* Preserve OpenCode reasoning and recorded patches in native history (#24790)
* Use bounded OpenCode context for vault session continuation
OpenCode database and synthetic row paths are not text transcripts. Use the
vault preview or captured pane context, preserving actual transcript paths
containing a hash and supporting both OpenCode lanes and Windows paths.
Adapted the intent of #11859 and extended it to actual installed v2 vault rows.
Co-authored-by: mrcha033 <mrcha033@users.noreply.github.com>
* Read real OpenCode sessions in terminal-backed native Chat
Reuse the bounded AI Vault SQLite worker for v1 and v2 session pages and live updates. Keep terminal input as the real execution path and pace OpenCode Stop through its two-Escape interrupt.
Co-authored-by: xodmd45-ctrl <xodmd45-ctrl@users.noreply.github.com>
* fix(opencode): publish approval cards for permission requests
* Send OpenCode native approval through its Enter selector
* Resolve mobile Chat readability for folder workspaces
* Bound OpenCode part batches and preserve v2 image attachments
* Prefer live migrated OpenCode sessions over legacy copies
* Consolidate mobile Chat eligibility test imports
* Consolidate OpenCode SQLite protocol type imports
* fix(native-chat): preserve OpenCode reasoning and patch parts
Separate genuine reasoning from answer blocks in both native SQLite schemas and retain recorded patches as completed patch tools. Keep each database row together at page boundaries so the existing raw-row cursors cannot drop half of a mixed row.
Adapted from @akhan157's OpenCode native history work in #13287 at bb661d10d716764fb472d824cd434678875b1947; retains the current bounded reader and account discovery instead of restoring the older capture and cursor implementation.
Verified against genuine private installed 2.0.16 and official 1.18.30 CLI ingestion.
Co-authored-by: Adnan Khan <adnank11427@gmail.com>
* fix(native-chat): keep split OpenCode rows intact on desktop and mobile
Preserve the native reader's bounded OpenCode row groups in paired reads and snapshot/replacement frames so a second presentation-count slice cannot drop reasoning while advancing the database cursor. Sort derived reasoning before its answer under the same provider timestamp while retaining journal order.
These two boundaries were reproduced with genuine installed 2.0.16 and official 1.18.30 sessions in a hidden desktop renderer and the current mobile view over an actual authenticated encrypted pairing.
Completes the semantic presentation from @akhan157's #13287 without importing its older clipping or cursor implementation.
Co-authored-by: Adnan Khan <adnank11427@gmail.com>
* fix(native-chat): keep reasoning and answers together in live windows
* Bound OpenCode transcript RPC pages and present omission notices
* Bound OpenCode transcript RPC pages and present omission notices
* Bound OpenCode transcript RPC pages and present omission notices
* Update native worker oversized-history notice contract
---------
Co-authored-by: mrcha033 <mrcha033@users.noreply.github.com>
Co-authored-by: xodmd45-ctrl <xodmd45-ctrl@users.noreply.github.com>
Co-authored-by: nwparker <nwparker@users.noreply.github.com>
* ci: run the cross-version tests when the chat send builders or the RPC request path change (#25061)
* ci: run cross-version wire suites when agent sends or orchestration change
The selector skipped the cross-version wire job for #24901, which changed the
shared agent-send path, the structured send envelope builders, and orchestration
RPC code. Route the send payload/fingerprint builders, src/main/runtime/orchestration/,
and the orchestration RPC methods to the job, and pin each rule in a test.
* ci: run cross-version wire suites only for code they execute
The agent-session suites now build their send through the shared outbox
builder and check it against the release host's schema and fingerprint,
so the send builder and outbox are selected exactly. Load-only
orchestration rules are dropped; the dispatcher-path files every suite
request runs through are selected instead.
* ci: run the release's own send admission, cover queued sends and the RPC reply builder
* test(cross-version): a wrong send fingerprint must be refused, so an agreed one is not vacuous
* Avoid duplicate ripgrep scans for full file inventories (#23490)
* Avoid duplicate ripgrep scans for unbounded file inventories
* Pin the broad ripgrep pass superset contract
* Select OpenCode plugin exports from the execution host version (#24662)
* fix: wait for OpenCode worker composer before first dispatch
Reuse captured composer readiness on local and paired execution hosts and revoke launching-shell paste anchors.
Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com>
* feat(opencode): probe execution-host CLI capabilities
* fix(opencode): select plugin default for execution host loader
* fix(opencode): limit prompt prefill capability to verified release
* feat(opencode): probe launch capabilities on the execution host
* fix(opencode): select plugin loader for the launched host binary
* fix(opencode): match WSL probe cwd and declared guest environment
* fix(opencode): preserve launch environment deletion boundaries
* STRICT launch CI contract correction
* CAPS launch CI contract correction
* test: initialize Claude prompt state in output retention fixture
---------
Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com>
* Cut CI time in store, Git contention and readiness tests (#25147)
* Check store retention boundaries with a faster independent oracle
* Replace CI diagnostic sleeps with gated contention and scoped transcript clocks
* Add an optional shortcut to toggle child workspaces (#24165)
Adds a user-assignable shortcut for the existing child-workspace chip action. It stays unassigned by default on macOS, Linux and Windows. Final-source rendered checks cover Settings recording/reset, guards, scroll preservation and restart.
Fixes#24163
Continues mmarabel’s original contribution in this PR. Issue #24163 has no sibling implementation PR. Existing bot findings are fixed, withdrawn or addressed in the PR review.
Co-authored-by: mmarabel <166927047+mmarabel@users.noreply.github.com>
* Filter Markdown filenames before sending the listing to the app (#25148)
* Fix Markdown Find editing without moving the caret or viewport (#25144)
* Fix Markdown Find editing without changing the caret or viewport
* Preserve Markdown selections across search focus and stale updates
* Keep opened issue details and edits in the selected repository (#24729)
Keeps a fork issue’s details, metadata and edits bound to the repository the user opened, including same-number issues in fork and upstream. Repairs selected-assignee leakage and delayed failed edits repainting another issue.
Fixes#24378
Incorporates and cross-reviews contributor PR #24379, including its source-resolver correction and regression material. Covers the contributor PR’s Project-row identity and retained-dialog mutation findings. The final published head passes focused tests, hidden macOS rendering and current CI; the callback-timing bot thread has an evidence-based response.
Co-authored-by: Katsuma Takehisa <k.takehisa@nissogr.com>
* Update README downloads badge
* Avoid repeated runtime imports and recovery fixture seeding (#25155)
* fix(agents): keep ~/.copilot/config.json owner-only when Orca trusts a folder (#25087)
* fix(agents): keep ~/.copilot/config.json owner-only when Orca trusts a folder
Marking a folder trusted for Copilot rewrote ~/.copilot/config.json through a
temp file created with the default umask mode (usually 0644), so an owner-only
file that can hold copilotTokens became readable by other local users. Since
the folder-trust change this write also runs on SSH hosts, where other users
exist. The rewrite now always writes the file owner-only (0600).
* test(agents): cover a fresh owner-only Copilot config.json under a permissive umask
---------
Co-authored-by: m4air <m4air@Mac.localdomain>
* Reduce terminal test overhead while preserving full parity checks (#25151)
* Speed up terminal test oracles without reducing replay coverage
* Call asynchronous parser through its checked test interface
* Preserve evidence document final newline for concurrent merges
* Fix ripgrep result completeness, filename handling, and search errors (#25156)
* Preserve ripgrep search results, filename identity, and failure diagnostics
* Fix adversarial Unicode and Explorer filename findings
* Register search failure localization fallback
* Preserve host filename identity through document and watcher consumers
* fix(opencode): submit admitted native startup briefs without overwriting input (#24762)
* fix: wait for OpenCode worker composer before first dispatch
Reuse captured composer readiness on local and paired execution hosts and revoke launching-shell paste anchors.
Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com>
* feat(opencode): probe execution-host CLI capabilities
* fix(opencode): select plugin default for execution host loader
* fix(opencode): limit prompt prefill capability to verified release
* feat(opencode): probe launch capabilities on the execution host
* fix(opencode): select plugin loader for the launched host binary
* fix(opencode): match WSL probe cwd and declared guest environment
* fix(opencode): preserve launch environment deletion boundaries
* wip(opencode): authorize native startup prompt intent at execution owner
* fix(opencode): atomically replace status plugin entrypoints
* fix(opencode): retain plugin permissions across restrictive umasks
* test(opencode): resolve permission fixture from primary cwd
* feat(opencode): install startup prompt plugin independently of status hooks
* fix(opencode): wait for admitted startup intent and preserve failed-launch briefs
* fix(opencode): unsubscribe hook settings during async host shutdown
* STRICT launch CI contract correction
* CAPS launch CI contract correction
* INTENT launch CI contract correction
* test: initialize Claude prompt state in output retention fixture
* Wait for OpenCode location hydration in intent startup
* Bind OpenCode startup readiness to the current location in intent startup
* Retry interrupted OpenCode startup prompt claims
---------
Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com>
Co-authored-by: Ahmed Nagy <ahmednagy25t@gmail.com>
Co-authored-by: Orca startup hydration review <agents@stably.ai>
Co-authored-by: Orca <dev@stably.ai>
* feat(csv-viewer): detect semicolon-separated CSVs (#19894)
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: Neil <neil@stably.ai>
* Reuse buffer cells during terminal cursor context scans (#25161)
* Share PR preflight setup to reduce runner demand (#25150)
* Share PR static analysis and compiler runner
* Preserve evidence document final newline for concurrent merges
* Keep readiness reuse contracts aligned with the physical preflight gate
* Keep Orca CLI first after shell startup (#25130)
* Restore the owning Orca CLI path after shell profiles
* Use a literal marker for the Bash lookup regression
* Preserve plain panes and initialize zsh after prompt hook replacement
* Preserve user line-editor dispatchers during deferred startup
* fix: retain CLI startup when global Zsh replaces prompt hooks
* test: replay global Zsh hook replacement after host startup
* test: isolate controlled Zsh widgets from distro keyboard setup
* fix(shell): preserve user hooks during deferred zsh initialization
* Keep completed Zsh startup hooks retired when the wrapper is sourced again
---------
Co-authored-by: Codex <codex@openai.com>
Co-authored-by: Orca maintenance <orca-maintenance@users.noreply.github.com>
Co-authored-by: Orca campaign <orca-campaign@local.invalid>
* fix(opencode): keep overlay manifest cleanup inside owned directories (#24763)
* fix: wait for OpenCode worker composer before first dispatch
Reuse captured composer readiness on local and paired execution hosts and revoke launching-shell paste anchors.
Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com>
* feat(opencode): probe execution-host CLI capabilities
* fix(opencode): select plugin default for execution host loader
* fix(opencode): limit prompt prefill capability to verified release
* feat(opencode): probe launch capabilities on the execution host
* fix(opencode): select plugin loader for the launched host binary
* fix(opencode): match WSL probe cwd and declared guest environment
* fix(opencode): preserve launch environment deletion boundaries
* wip(opencode): authorize native startup prompt intent at execution owner
* fix(opencode): atomically replace status plugin entrypoints
* fix(opencode): retain plugin permissions across restrictive umasks
* test(opencode): resolve permission fixture from primary cwd
* feat(opencode): install startup prompt plugin independently of status hooks
* fix(opencode): wait for admitted startup intent and preserve failed-launch briefs
* fix(opencode): confine overlay manifest cleanup to owned directories
Co-authored-by: Adnan Khan <adnank11427@gmail.com>
* fix: wait for OpenCode worker composer before first dispatch
Reuse captured composer readiness on local and paired execution hosts and revoke launching-shell paste anchors.
Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com>
* feat(opencode): probe execution-host CLI capabilities
* fix(opencode): select plugin default for execution host loader
* fix(opencode): limit prompt prefill capability to verified release
* feat(opencode): probe launch capabilities on the execution host
* fix(opencode): select plugin loader for the launched host binary
* fix(opencode): match WSL probe cwd and declared guest environment
* fix(opencode): preserve launch environment deletion boundaries
* wip(opencode): authorize native startup prompt intent at execution owner
* fix(opencode): atomically replace status plugin entrypoints
* fix(opencode): retain plugin permissions across restrictive umasks
* test(opencode): resolve permission fixture from primary cwd
* feat(opencode): install startup prompt plugin independently of status hooks
* fix(opencode): wait for admitted startup intent and preserve failed-launch briefs
* fix(opencode): unsubscribe hook settings during async host shutdown
* STRICT launch CI contract correction
* CAPS launch CI contract correction
* INTENT launch CI contract correction
* test: initialize Claude prompt state in output retention fixture
* Wait for OpenCode location hydration in intent startup
* Bind OpenCode startup readiness to the current location in intent startup
---------
Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com>
Co-authored-by: Ahmed Nagy <ahmednagy25t@gmail.com>
Co-authored-by: Adnan Khan <adnank11427@gmail.com>
Co-authored-by: Orca startup hydration review <agents@stably.ai>
* Reduce test deadline waits and exact byte comparison costs (#25187)
* Clean up retired OpenCode configuration copies safely (#25222)
* fix: wait for OpenCode worker composer before first dispatch
Reuse captured composer readiness on local and paired execution hosts and revoke launching-shell paste anchors.
Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com>
* feat(opencode): probe execution-host CLI capabilities
* fix(opencode): select plugin default for execution host loader
* fix(opencode): limit prompt prefill capability to verified release
* feat(opencode): probe launch capabilities on the execution host
* fix(opencode): select plugin loader for the launched host binary
* fix(opencode): match WSL probe cwd and declared guest environment
* fix(opencode): preserve launch environment deletion boundaries
* wip(opencode): authorize native startup prompt intent at execution owner
* fix(opencode): atomically replace status plugin entrypoints
* fix(opencode): retain plugin permissions across restrictive umasks
* test(opencode): resolve permission fixture from primary cwd
* feat(opencode): install startup prompt plugin independently of status hooks
* fix(opencode): wait for admitted startup intent and preserve failed-launch briefs
* fix(opencode): confine overlay manifest cleanup to owned directories
Co-authored-by: Adnan Khan <adnank11427@gmail.com>
* fix: wait for OpenCode worker composer before first dispatch
Reuse captured composer readiness on local and paired execution hosts and revoke launching-shell paste anchors.
Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com>
* feat(opencode): probe execution-host CLI capabilities
* fix(opencode): select plugin default for execution host loader
* fix(opencode): limit prompt prefill capability to verified release
* feat(opencode): probe launch capabilities on the execution host
* fix(opencode): select plugin loader for the launched host binary
* fix(opencode): match WSL probe cwd and declared guest environment
* fix(opencode): preserve launch environment deletion boundaries
* wip(opencode): authorize native startup prompt intent at execution owner
* fix(opencode): atomically replace status plugin entrypoints
* fix(opencode): retain plugin permissions across restrictive umasks
* test(opencode): resolve permission fixture from primary cwd
* feat(opencode): install startup prompt plugin independently of status hooks
* fix(opencode): wait for admitted startup intent and preserve failed-launch briefs
* fix(opencode): unsubscribe hook settings during async host shutdown
* STRICT launch CI contract correction
* CAPS launch CI contract correction
* INTENT launch CI contract correction
* test: initialize Claude prompt state in output retention fixture
* Wait for OpenCode location hydration in intent startup
* Bind OpenCode startup readiness to the current location in intent startup
* Collect retired source-scoped OpenCode configuration overlays conservatively
Credit brennanb2025 for the original bounded, delayed overlay garbage-collection contribution in PR #7627. Preserve ambiguous legacy and shared-service state.
* Correct inaccessible-source fixture without spying on native ESM exports
* Keep delayed OpenCode cleanup within existing file limits
* Reuse the overlay manifest module for existing owned-entry operations
* Use the existing filesystem import in the ownership mock
* test(opencode): keep overlay GC link tests portable
---------
Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com>
Co-authored-by: Ahmed Nagy <ahmednagy25t@gmail.com>
Co-authored-by: Adnan Khan <adnank11427@gmail.com>
Co-authored-by: Orca startup hydration review <agents@stably.ai>
Co-authored-by: OpenCode Campaign <opencode-campaign@users.noreply.github.com>
* Preserve Windows SSH upload failures unless pwsh is missing (#25185)
* test(ssh): reproduce missing-pwsh text in staging paths
* fix(ssh): classify missing PowerShell from command exit evidence
* test: expose generic Windows upload error misclassification
* test: await armed SSH upload before advancing fake clock
* test: retain real immediate delivery around upload timeout control
* fix: classify PowerShell absence from command exits only
* test: expose missing-command text inside SSH stderr paths
* fix: require missing pwsh diagnostic command identity
* test: keep mixed write errors out of missing-command fallback
* fix: require complete missing PowerShell diagnostic
* test: capture complete native missing pwsh diagnostics
* fix: recognize complete missing pwsh native diagnostics
* test(ssh): cover source-derived NormalView localization and wrapping
* fix(ssh): identify complete missing-pwsh records across NormalView layouts
* test(ssh): cover raw-wrap separators and repeated error headers
* fix(ssh): preserve wrapped separators and reject repeated error headers
---------
Co-authored-by: Orca Campaign <campaign@localhost>
* Verify shared preflight selection and record full unit timings (#25239)
* Strengthen shared preflight contracts and record unit timing results
* Record rejected shard-weight holdouts
* Pass wrapped OpenCode run prompts as positional messages (#25001)
* fix: wait for OpenCode worker composer before first dispatch
Reuse captured composer readiness on local and paired execution hosts and revoke launching-shell paste anchors.
Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com>
* feat(opencode): probe execution-host CLI capabilities
* fix(opencode): select plugin default for execution host loader
* fix(opencode): limit prompt prefill capability to verified release
* feat(opencode): probe launch capabilities on the execution host
* fix(opencode): select plugin loader for the launched host binary
* fix(opencode): match WSL probe cwd and declared guest environment
* fix(opencode): preserve launch environment deletion boundaries
* wip(opencode): authorize native startup prompt intent at execution owner
* fix(opencode): atomically replace status plugin entrypoints
* fix(opencode): retain plugin permissions across restrictive umasks
* test(opencode): resolve permission fixture from primary cwd
* feat(opencode): install startup prompt plugin independently of status hooks
* fix(opencode): wait for admitted startup intent and preserve failed-launch briefs
* fix(opencode): confine overlay manifest cleanup to owned directories
Co-authored-by: Adnan Khan <adnank11427@gmail.com>
* fix: wait for OpenCode worker composer before first dispatch
Reuse captured composer readiness on local and paired execution hosts and revoke launching-shell paste anchors.
Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com>
* feat(opencode): probe execution-host CLI capabilities
* fix(opencode): select plugin default for execution host loader
* fix(opencode): limit prompt prefill capability to verified release
* feat(opencode): probe launch capabilities on the execution host
* fix(opencode): select plugin loader for the launched host binary
* fix(opencode): match WSL probe cwd and declared guest environment
* fix(opencode): preserve launch environment deletion boundaries
* wip(opencode): authorize native startup prompt intent at execution owner
* fix(opencode): atomically replace status plugin entrypoints
* fix(opencode): retain plugin permissions across restrictive umasks
* test(opencode): resolve permission fixture from primary cwd
* feat(opencode): install startup prompt plugin independently of status hooks
* fix(opencode): wait for admitted startup intent and preserve failed-launch briefs
* fix(opencode): unsubscribe hook settings during async host shutdown
* fix(opencode): confine overlay manifest cleanup to owned directories
Co-authored-by: Adnan Khan <adnank11427@gmail.com>
* test(readiness): census recorded OpenCode composer boots
* fix(opencode): reject redirected overlay parents before cleanup
* fix(orcad): retain runtime cleanup when subscribing to hook settings
* refactor(launch): extract OpenCode config and attachment authority
* fix(opencode): retain host version selection across relay restarts
* fix(opencode): pass run prompts as positional messages
Preserve run flags and use the existing shell quoting and run-command detector
to append the initial message after --, reusing an existing separator.
TUI launches retain their version-selected prompt transport and draft behavior.
Original run-order work: @coelho-doti (#13065, tracked in #17551).
* fix(opencode): keep wrapped run tasks positional
Recognize supported environment prefixes and PowerShell call operators without
mistaking prompt arguments for executables. Keep environment and run separators
separate, preserve the task text and exclude run commands from native submission.
Source-parent: 23fc08b4e9
Related-to: stablya/orca#17551
Credits: @coelho-doti (stablya/orca#13065)
* Prepare complete private OpenCode launch validation source
Integrate the complete reviewed readiness, capability, native prompt, overlay and positional-run source onto frozen main. Preserve canonical atomic ACL retry, status generator/disposal, restrictive-umask fixtures and unowned source. Keep supported wrapped run commands positional.
Private-validation-source: a44345ce49
Original-full-source: 23fc08b4e9
Original-core-base: 8186ded0bd
Frozen-main: 08ee7ba9ef
Owned-source-paths: 111
Publication-policy: private validation only; preserve the six separate PR boundaries and held model/provider drafts
Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com>
Co-authored-by: Adnan Khan <adnank11427@gmail.com>
Credits: juli-gonzalez readiness contribution; Ahmed Nagy atomic plugin writer; coelho-doti positional run contribution
* Prepare private complete 111-path launch validation on current main
Private validation only. Preserve main credential additions and original launch ownership. Held model and provider topics remain excluded.
* Recognize env options before positional OpenCode run messages
* STRICT launch CI contract correction
* CAPS launch CI contract correction
* INTENT launch CI contract correction
* test(opencode): wait for malformed claim retries before expiring intent
Observe real endpoint I/O completion under fake timers before forcing expiry.
* test: initialize Claude prompt state in output retention fixture
* Wait for OpenCode location hydration in intent startup
* fix(opencode): bind startup readiness to the composer location
* Bind OpenCode startup readiness to the current location in intent startup
* Retry interrupted OpenCode startup prompt claims
---------
Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com>
Co-authored-by: Ahmed Nagy <ahmednagy25t@gmail.com>
Co-authored-by: Adnan Khan <adnank11427@gmail.com>
Co-authored-by: Orca startup hydration review <agents@stably.ai>
Co-authored-by: Orca <dev@stably.ai>
* feat(codex): tell Windows users once that Codex in Orca now shares ~/.codex (#24916)
* feat(codex): tell Windows users once what stays behind when Codex moves onto ~/.codex
When Windows' system-default Codex first runs on ~/.codex (launch prep or
the usage poll), main decides once whether Orca's managed home was ever
used and which MCP servers lived only there, and persists that in UI
state. The renderer shows one dismissible toast when a Codex terminal
exists, after the server-isolation notice rather than on top of it, and
clears the notice when shown.
The "kept only in the managed home" MCP rule is extracted into
isRuntimeOnlyMcpServer, which the config mirror merge now uses too, so
the notice names exactly the servers the mirror would have kept.
* fix(codex): stop counting Orca's own config.toml as use of the old Codex home
Orca's hook install writes that home's config.toml on every startup, so its
presence was true for nearly every Windows user with Codex. The home now
counts as used only with recorded sessions or an MCP server of its own.
Resolver tests keep one case per input source.
* refactor(codex): ask main for the shared-settings notice instead of persisting it
The persisted missing/object/null field, written from launch prep and the
usage poll, becomes a plain codexSharedSettingsNoticeSeen flag mirroring
codexTerminalServerIsolationNoticeSeen. When a Codex terminal first appears
and the flag is unset, the renderer asks codexConfigSync:sharedSettingsNotice
once; main answers read-only (Windows, system default on ~/.codex, managed
home path without mkdir) and maps any read error to null.
Runtime-home routing, launch and the test harness return to main's code.
The notice no longer waits for the server-isolation toast; they may stack.
The Codex-terminal watch moves to codex-terminal-presence.ts.
* refactor(codex): watch for the first Codex terminal in one place for both notices
The server-isolation notice now passes its due check to
whenCodexTerminalAppears instead of keeping its own copy of the presence
scan, input filter and subscription loop. Its behaviour and tests are
unchanged.
* docs(codex): trim isRuntimeOnlyMcpServer's comment to why it is shared
* refactor(codex): keep McpServerTomlOwnership private to its module
* test(codex): cover the shared-settings notice channel without type assertions
Handlers are looked up by channel now that two are registered, so the
status tests no longer depend on registration order.
* refactor(codex): show the Windows shared-settings notice without asking main
Every way of detecting who relied on Orca's old Codex folder had false
positives, so the renderer now shows one static toast on Windows the first
time a Codex terminal exists. This drops the main-process resolver, its IPC
channel, preload line, web stub and shared type, and the MCP-names variant
of the description.
* refactor(codex): restore the MCP server ownership helpers to main's shape
The static notice no longer reads MCP servers, so the shared
isRuntimeOnlyMcpServer extraction has no second caller.
* refactor(codex): let each notice decide when it is due, so the Codex watcher only watches
The isolation notice now selects its due predicate and starts the watcher only while due, so whenCodexTerminalAppears no longer takes an isDue or re-checks hydration and settings. The shared-settings notice uses isLocalWindowsDesktopClient, its test stubs the user agent instead of mocking pane-helpers, and the hydration safeguard it relies on is now tested on the UI slice itself.
* test(codex): drive the Codex notices through a reactive store, and drop a redundant hydration gate
The server-isolation notice now reads "is it due" through a store selector, but its test
mocked the store without re-rendering, so a due change after mount (persisted UI loading,
the setting turning off) was never exercised. The notice tests now share one harness backed
by a real zustand store, the shared watcher gets its own test, and both notices cover the
seen flag loading after mount.
persistedUIReady is dropped from isNoticeDue: the seen flag defaults to true and only
hydration clears it, in the same update that sets persistedUIReady. Both notices now gate
the same way.
* fix(codex): keep the shared-settings toast until dismissed, and shorten it
It is marked seen before it shows, so a 15s auto-close could lose it for good while the user
is typing in the Codex terminal that triggered it. Every other one-shot notice that marks
itself seen on show stays until dismissed; this now does too.
The text drops the sentence that repeated the title and keeps only what to expect and do.
---------
Co-authored-by: Orca Worker <orca-worker@localhost>
* fix(codex): keep Orca-only MCP servers when refreshing the retained shared home (#24983)
* fix(codex): keep Orca-only MCP servers when refreshing the retained shared home
The refresh for panes that outlive an update treated the old shared
home's whole MCP root as owned by ~/.codex, so it deleted servers the
user had added from an Orca terminal, which existed only there. Read the
home's settings baseline instead, as the normal mirror does: drop only
servers the last mirror copied from ~/.codex. No baseline keeps the old
behaviour; an unreadable one skips the refresh. The baseline is not
advanced, keeping the refresh one-way.
STA-9109
* test(codex): type the MCP ownership baseline fixture
---------
Co-authored-by: Orca Worker <orca-worker@localhost>
* fix(jcode): report missing and outdated managed hooks (#25135)
* Delete docs/reference/jcode-hook-events.md (#25288)
* Keep active notebook cells current after external reloads (#25172)
* Replace patched JSON parser with stream-json (#25202)
* Replace patched JSON parser with stream-json
* Isolate dependencies for historical server compatibility builds
* Reuse source-line calculations for Markdown review selections (#25173)
* Reuse source-line calculations for Markdown review selections
* Use typed editor probes in review selection performance coverage
* Keep selected text navigation from rewriting document links (#25175)
* Keep selected text navigation from rewriting document links
* Check model selection before document link arrow coverage
* A resent chat message gets its recorded answer, never an early refusal or a made-up record (#25158)
* fix(native-chat): a resent send id gets its recorded answer, never an early refusal or a made-up record
The host now looks a resent send id up before preparing the session. A row
that settled refused answers with its refusal before the chat is opened. A
resend whose chat cannot be opened or made ready answers unknown instead of a
refusal. A /clear in flight refuses only ids the ledger does not hold.
A send row now records the journal epoch it was admitted into. An unsettled
row with nothing written in that same epoch runs for the first time; under a
later epoch the host answers unknown instead of reconstructing a submission
it never had. The host advertises agent-session.send-answers-proof.v1.
* test(native-chat): pass the ledger row to the thread-goal rerun check
* fix(native-chat): a send's answer commits with its write, and a resend is answered before any write
A send (and /compact) settles its ledger row `succeeded` in the same SQLite transaction as the
submission or queued draft that accepts it, so a row still `pending` proves nothing was written and
a resend runs it for the first time. The unknown-before-run mark and the per-row journal epoch go.
A resent id is answered from its row and the journal before preparation starts an agent and before
any write transaction: a recorded refusal with nothing opened; otherwise the conversation is opened
(no agent start for a send) and replayed, and a conversation that will not open answers unknown.
* fix(native-chat): a ledger refusal is answered first, and a /clear refuses only a send's first run
An id the ledger refuses (expired, conflict, invalid, capacity) is answered as admission would,
with no journal read, preparation or write, so a closed chat or a read-only store answers it too.
A re-read after the replay open that comes back refused returns that refusal.
Whether a /clear is in flight is read when a send arrives and applied in the send's preparation
for a first run only: an id the ledger holds by the send's turn, including one whose earlier
attempt was queued ahead of the clear, is answered from its record. MutationPlan makes
settlesWithWrite and settledOutcome exclusive; the capability text no longer promises a refused
id never sends.
* docs(native-chat): say what a replay's preparation does for every plan
* Fix reordering workspaces with collapsed children (#25302)
* fix(native-chat): show a message Orca accepted and then failed to deliver as "Not sent" in the chat (#24710)
* fix(native-chat): keep a message the host accepted then rejected in the desktop chat as not sent
Draw it in place from the host's history, so a crash that loses the outbox no
longer makes it vanish. A later copy of the same body supersedes it; the outbox
row wins while it holds the message; the phone is unchanged.
* test(native-chat): pin the same-id rule apart from the body match
* test(native-chat): type the rejected-in-place fixture body as a text block
* fix(native-chat): let the host's row own a message it recorded and then rejected
Once the host's journal records a send as rejected, the desktop outbox lets it
go, as it already does for delivered and Stop-withdrawn sends: the host's row
shows it as not sent, with the host's reason and no Retry. The outbox keeps
only sends the host refused before recording them, which keep their Retry.
A send whose own reply says it was rejected is drawn by its outbox entry, with
no Retry, until the journal carries the row; a copy left by an earlier session
is dropped when the chat opens.
- the transcript no longer hides a host row behind an outbox entry with the
same id or the same text; those rules and their cache are gone
- a rejected message the queue holds (a draft's hand-off, or a live card under
its id) is drawn as its card, not as a row
- a later copy of the same text hides a rejected row only when it was sent
once the rejection was known, so a deliberate repeat stays
- delivery notices read the same visibility rule as the transcript; a chat
whose only rejection a Stop withdrew no longer rebuilds them per batch
- the body fingerprint helper goes back to the host, its only user
* test(native-chat): keep one row when copies of a rejected message share an instant
* refactor(native-chat): let the host's notice replace the outbox's under the same id
* test(native-chat): pass the queued card ids in the tool-stream cost transcript
* fix(native-chat): keep the host's record as what lets a rejected message go
- the outbox no longer drops a host-rejected message when a chat opens; the
reconcile lets it go once the journal's submissions say it was rejected, and
that drop is written to storage, so nothing reads as still owed
- a message the host rejected while the chat watched waits for its journal row
with no Retry; one read back from storage with no row loaded keeps its Retry
under a new id, since the host may have lost it
- the delivery notices keep the same map and notice objects across a batch that
words every row the same, so a submission batch re-renders no row
- a rejected command such as /compact stays hidden: its own reply reports it
- the desktop transcript requires the queued card ids, with a controller-level
test that a card holding a rejected message keeps its row hidden
* fix(native-chat): draw a queued message where the host rejected it
A message accepted to hand over later and rejected before any handover now sits
at its rejection, as a handover places one: what the agent did while it waited
happened before it, and the newest history page holds it. One handed over, or
dispatched as it was recorded, keeps its place. An older host does not move it,
so it stays at its submission, still drawn.
A failed start now rejects the queued messages and writes its row in ONE
journal append, the messages first: no reader ever meets one without the
other, and the messages still draw above the row that says why.
* test(native-chat): pin that rows written together roll back together
* fix(native-chat): draw every rejected message where it was rejected
Not only a queued message: one handed over into a turn and then rejected, or
sent directly and rejected, also sits at its rejection, in no turn. A message
in doubt stays where it was, a plain bubble: it may have reached the agent.
* fix(native-chat): decide a rejected message's Retry from the host's stored fact
- a message the host recorded and then rejected has no Retry on any mount,
however that mount learned of it, and a Dismiss that clears it from storage;
a send refused before the host recorded it keeps its Retry
- the rule that keeps a rejected command such as /compact out of the
transcript moves into the one visibility function rows and notices share
- the outbox state docs say what lets a recorded message go: the client holding
its rejected submission, whose row the host places at the rejection
* fix(native-chat): write no start-failure row when a Stop withdrew every queued message first
* test(native-chat): pass the Dismiss action in the delivery-notice hook tests
* fix(native-chat): keep a rejected message's outbox copy until its row loads
An older host leaves a rejected message where it was sent, which may be older
than the loaded window: the chat then holds the rejected submission but not the
row that draws it. The outbox copy now stays until that row loads, marked as
the host recorded it (Dismiss, no Retry, in the host's words), and leaves once
the page holding the row is loaded. Derived from the loaded rows each time.
Tests that label their projection as the phone's now pass the phone's own
setting.
* test(native-chat): type the outbox hook props that carry loaded rows
* fix(native-chat): write nothing when a journal batch settles nothing in the outbox
The outbox re-reads the journal on every batch since it waits for a rejected
message's row to load. Its reconcile now returns each unchanged entry, and the
list, as themselves (a message left in doubt included), so a batch that changes
nothing writes nothing to storage. The reconcile moves to its own module.
A copy the host recorded and rejected owes no delivery, so it no longer keeps a
hidden pane reading the journal.
* test(native-chat): count storage writes on the outbox's own storage object
* fix(native-chat): let a recorded rejected message's outbox copy leave on its own, with no Dismiss
The outbox copy of a message the host recorded and then rejected draws it only
while the host's row is not loaded, and leaves on the batch or page that loads
that row. It owes no delivery and offers no control: sending it again is a new
message. The Dismiss that let the user clear it is gone, from the outbox, the
notices and the session controller.
* Bound OpenCode history reads and repeated worker failures (#25292)
* fix(opencode): keep scan budgets across queue waits and batches
Reuse the scan-owned lifetime proposed in #10708 by @AmethystLiang with the existing shared worker queue.
* test(opencode): check nonempty session fixtures and lint scoped controls
* Derive OpenCode scan deadline message from its budget
---------
Co-authored-by: Neil Parker <nwparker@MacBook-Pro-3.localdomain>
Co-authored-by: OpenCode issue campaign <codex@localhost>
* Update in-app Android APK links to mobile 0.0.52 (#25168)
* Add shared workspace settings note and prevent filter modal expansion (#25300)
* fix(mobile): say that workspace sort, grouping, and filters are shared
The Manual sort option was subtitled 'Server order', but it orders by the
desktop's drag ranks. Sort, grouping, and filters on the phone all write the
host's shared view settings, so changing them also changes every other
device on that host, which the screen never said. Relabel Manual as 'Desktop
drag order' and add 'Shared with other devices on this host' under the Sort
By, Group By, and Filter titles. The note avoids naming a desktop sidebar
because headless hosts have none.
* fix(mobile): prevent filter modal heading expansion
Add flexShrink: 1 to allow the heading container to shrink when
space is constrained. Update comment to clarify why workspace view
is shared across devices.
* update wording
* fix(native-chat): a new structured chat's model list comes from the machine that runs it (#25143)
* fix(native-chat): a new structured chat's model list comes from the host that runs it
agentSession.modelCatalog builds the structured-session host like agentSession.options,
so a host with no saved chats since it started answers instead of refusing. When the
host answers unknown because its first listing runs in the background, the picker
re-reads on a bounded schedule until that listing lands. Local terminal-backed chat
skips the structured catalog when a custom launch command is configured.
* fix(native-chat): wait on the host's first model listing instead of re-reading on a timer
A new structured chat's picker read the host catalog once; on an account the
host had never listed, the answer was "unknown" while a background listing ran,
and the client re-read on a 1-30 s schedule. Replace the schedule with the
host's own completion signal:
- The host answers a cold read with `listingInProgress: true` (new optional
field) once it has started or joined that listing. A read that passes the new
optional `waitForListing` param awaits the same joined listing and answers
with it, or a plain "unknown" if it failed. The at-rest options read never
waits. A host that predates the field never sends it, so the client never
sends the param to a host that would refuse it.
- The picker reads once per open and attach; after the host's report it sends
one waiting read, with a 90 s client timeout above the slowest listing.
While that read is out, the model pill keeps its label but cannot open or be
set (typed /model included). An answer, failure, timeout, hide, attach or
the provider's own list releases it.
- `agentSession.modelCatalog` builds the structured-session host only for a
read that names a session (a structured chat). Terminal-backed chat's
session-less read keeps the non-building gate, so a desktop that never runs
structured chat never opens the session journal.
* fix(native-chat): one waiting model-list read per chat, and no late menu open
The waiting catalog read was owned by one run of the picker's effect. Attach
(a new fence), hide/show or a send re-ran the effect: the cleanup released the
model picker onto the built-in list for a round trip, and the new run sent a
second waiting read while the first, which cannot be withdrawn, kept a remote
call slot until the listing ended.
The waiting read now belongs to the chat (runtime target + agent + session):
a small registry keeps one in flight per chat, every re-run or remount joins
it, and the entry is deleted when the read settles. The picker hold is derived
from that entry being in flight, so it lasts across attach and hide/show and
ends when the read settles, the provider reports its own list, or the pane
switches to another session. Answers still pass the stale and record checks.
A bare /model typed while the list loads no longer opens the model menu by
itself when the list lands: the menu stays keyed on the request, and only its
initial open is suppressed while pending, so the request is spent shut and the
end of the pending period never remounts it.
* fix(native-chat): release the model picker in the same commit as the host list
When the waiting catalog read settled in the chat that started it, the
registry dropped its entry and told subscribers first, and the host list was
applied a few microtasks later. React committed once with the picker enabled
on the built-in list, then again with the host's list.
Joiners now hand the registry their apply callback, and the registry runs
every joiner (with the answer, or nothing when the read failed or timed out)
before it deletes the entry and notifies. The release and the list land in one
commit. An effect cleanup leaves the wait instead of flagging itself stale.
* fix(runtime): queue model catalog reads in the long-wait lane
A model catalog read that waits on a host's first listing replies only when
that listing ends, yet it took one of the 8 foreground call slots for its
server. Enough chats opened during one cold listing would stall that server's
sends and interrupts until a wait settled.
agentSession.modelCatalog now joins worktree.rm in the long-wait lane: same
concurrency, counted apart from the foreground calls. The queue classifies by
method only, and a warm catalog read answers at once, so the whole method
moves.
* fix(agents): recognize Pi bundled npm entrypoint
Carry the focused patch from boris-papevis/orca PR #25040 onto current main, with independent npm runtime and regression verification.
* fix(cursor): keep usage cookies on the selected account (#25243)
Keep Cursor quota requests tied to the selected account by omitting ambient Electron session credentials. Preserve explicit account cookies and redirect handling.
Credit: Li-Sanze for the original credential-mode fix in #23626, carried through #24575; jjongsta and chengjiaxiao for the reports. Native HTTP/HTTPS cookie isolation, mutation controls and proxy behavior were verified before merging. This does not claim to resolve the separate initial-authentication failure in #23612.
* Bound search preview retention and stop canceled remote scans (#25303)
* Install OpenCode hooks in the terminal's config directory (#25296)
* test: reproduce OpenCode plugin installation in the wrong config root
* fix: install OpenCode hooks in the execution config directory
* test: isolate config installation from CLI version probing
* style: format consumer config installation controls
* fix: use checked startup environment and supported relay shell context
* fix: install OpenCode hooks using the selected execution shell
* test(opencode): assert consumer config root in PTY fixtures
---------
Co-authored-by: Codex <codex@openai.com>
Co-authored-by: Orca <orca@stably.ai>
* Reduce avoidable work in PR checks and SSH test setup (#25309)
* Isolate code editor text and Undo history by execution host (#25174)
* Isolate code editor text and undo history by execution host
* Verify editor owner resolution and Windows model path isolation
* Respect native model line endings in content sync history coverage
* Preserve selection and scroll when editor ownership resolves
* Verify owner synchronization against a reachable editor change callback
* Fix: settle sortEpoch in store to prevent React update depth exceeded (#25313)
* fix(sidebar): stop Manual sort from mirroring sortEpoch into state
In Manual mode the sort hook copied every sortEpoch bump into state from an
effect, adding a nested React update per bump. A burst of store bumps at
startup stacked those into 'Maximum update depth exceeded' (React #185).
Manual now reads the live epoch directly; debounced modes are unchanged.
* feat(sidebar): tell people when a drop switches sort to Manual
Reordering by drag still switches the sidebar to Manual so the drop sticks,
but it used to happen silently. Show a toast with a 'Back to <previous sort>'
action; it retires itself on any later sort change so it can't override a
newer choice. Drops while already in Manual stay silent.
* feat(store): settle sortEpoch in the store instead of in React state
Add settledSortEpoch plus a 3 s settle timer owned by a store listener
installed in the state creator. Every write path (slice actions, the web
session sync patch) goes through it, so the settled value stays correct
with no sidebar mounted. Manual, a sort-mode switch, and a bump that
changes the non-archived row count settle in the same notify; other bumps
restart the window. A reset that lands settled clears the timer, and the
disposer runs on HMR teardown.
* fix(sidebar): read the settled sort epoch instead of mirroring it into state
The sort hook no longer copies sortEpoch into React state from an effect in
any mode; it reads the store's settledSortEpoch directly. That pattern added
a nested update per bump and stacked into "Maximum update depth exceeded"
(React #185) under flushSync bursts. Tests cover Manual, add/remove, burst
reset, no-bump row changes, mode switch, and 80 flushSync bumps in Recent
while worktrees are added.
* cleaning up
* fix(store): settle bumps when rows update during pending window
Detect structural changes on worktreesByRepo updates in addition to sort
epoch changes. When a row arrives without its own bump during a pending
settlement window, the changed composition must still trigger settlement.
* fix: update Caffeinate tooltip copy about MacBook lid behavior (#23091)
Update tooltip and settings pane help text to accurately describe
Caffeinate's behavior: it prevents idle sleep while active, but
MacBook lid closing may still trigger sleep per device power policy.
The previous copy incorrectly suggested Orca could prevent lid-close
sleep.
* Simplify the phone-control and phone-size terminal dialogs (#25307)
* Redesign the phone-control and phone-size terminal dialogs
Drop the eyebrow label and circled icon, shorten the copy so it no longer
restates the buttons, and give each state one primary action with a quieter
"all" action. Collapse moves out of the button row into a Minimize icon in
the corner. Behavior is unchanged.
* Point the phone settings copy at the renamed Restore button; drop dead ko overrides
The phone app and desktop update independently, so name only "Restore",
which matches both the old and new desktop banner labels.
* fix(terminal): stop old output bleeding into Claude's screen when revisiting a remote tab (#24926)
* fix(terminal): leave the alt screen before replaying a pushed host snapshot
A remote terminal running a full-screen agent (Claude Code) could show old
output (e.g. a setup script's pnpm log) interleaved with the agent's screen
after switching back to the tab. The host's pushed snapshot is a serialized
image that starts on the normal buffer and enters the alternate screen
itself, but the replay drain cleared with ESC[2J without leaving alt. The
image's history painted into the agent's screen, its own ?1049h was a no-op,
and the agent's diff paints landed on top of the stale cells.
The remote-runtime transport now marks snapshots as serialized images, and
the drain grounds them with the shared snapshot prologue (switching to the
normal buffer first). Raw byte replays (SSH relay ring buffers) keep the
in-place clear.
* fix(terminal): paint folded remote snapshots from the normal buffer everywhere
Review follow-ups:
- Rename the flag to carriesNormalBuffer: what the painters rely on is that
the image starts on the normal buffer and enters alt itself.
- Hidden-output restore had the same bug for remote requested snapshots
(history folded into data, alternateScreen set): the painter entered alt
first and painted the normal buffer into the TUI's screen. Requested remote
snapshots now carry the flag and take the normal-buffer start.
- Flag pushed snapshots replayed after a cancelled shutdown too.
- Pushed snapshots carry only the screen, so over a live TUI the drain keeps
the normal-buffer history it covers instead of wiping it.
- Read the pane's buffer after queued output parses.
- Tests compare whole buffers against a fresh terminal, use production image
shapes, and pin the raw-replay path with the same oracle.
* fix(terminal): let the replay drain own the recovery snapshot clear
The recovery prefix's own \x1b[3J ran after the drain's prologue and wiped the
history the drain keeps under a live TUI. Keep only the latch release, which
still makes an empty recovery snapshot non-empty so it is applied.
* fix(terminal): keep the recovery snapshot's own screen clear
Consumers that write recovery snapshots straight into xterm (no replay drain)
rely on the prefix clearing the stale screen. Restore \x1b[2J\x1b[H and drop
only \x1b[3J, which wiped history the image does not carry.
* test(terminal): drive the drain with the real recovery payload
The multiplexer prepends its own screen clear; replaying that exact payload
pins that the prefix cannot wipe the history a TUI covers.
* fix(terminal): keep TUI-covered history only when the grids match
Second review follow-ups:
- A pushed image carries only the host's screen; the pane's frozen history
continues it exactly only on the same grid. On another grid keeping it
duplicated or dropped lines, so the image replaces it there.
- Tests replay the pane's writes and grid changes into a real terminal and
compare whole buffers with the host, including a mismatched-grid case.
- The split branch shares the normal-buffer preamble; drop the now
single-use abort helper.
- Type serializeBuffer as RemoteRuntimeSnapshotImage so the flag is carried
by type, not by object pass-through.
- Register the grid and raw-replay cases in the reliability gate.
* fix(terminal): keep TUI-covered history only while the host is still on alt
The drain kept the pane's history whenever the pane was on the alt screen. If
the host's TUI exited while the tab was hidden, the shell wrote past that
history and the kept lines no longer continued the host's screen. Require the
host's own alternateScreen too; an absent flag proves nothing, so the image
replaces history as on main.
Also: one buildSnapshotReplayPreamble for every first replay write, the drain's
image and raw clears as separate branches (raw byte-identical to main, comment
restored), and a single paneAtSourceGrid check.
* fix(terminal): gate kept history on the host's shell-owner proof
The host sends alternateScreen only with terminalOwner 'shell', i.e. once it has
proven the TUI exited, so `alternateScreen === true` never held for a live TUI
and the drain wiped the history it should keep. Replace the history only once
the host proves the TUI exited; tests now use production snapshot shapes. Move
the relay-overlap comment into the raw-replay branch it describes.
* fix(terminal): keep TUI-covered history only on a proven shared grid
An image without its grid cannot prove the pane's history continues its screen,
so it now replaces history. Also update a stale recovery-prefix test comment.
* test(terminal): replay host-serialized snapshots through the real wire and drain
Hand-written replay meta hid a gate that production never satisfies. Drive the
host's own emulator, ownership mirror, serializer and recovery publisher through
the real wire, client parser, remote transport and pane drain, for a live TUI
and after the host proves it exited.
* fix(terminal): repaint only the alt frame over a live TUI, leaving history alone
Keeping the pane's history by clearing only the normal screen assumed a pushed
image carries no history, which the host does not guarantee: a 0-row push can
reuse a concurrent requested capture, and its history then landed twice in
scrollback. It also wiped history on a grid mismatch, where main kept it.
When pane and image are both on alt, the image's normal part adds nothing (the
normal buffers froze together), so paint only its alt payload after an alt-side
clear, split at the host's own boundary (splitAtAlternateScreenEntry, now shared
with the daemon). Any other image paints from the normal buffer. This drops
keepScrollback, the owner and grid gates, and the recovery-prefix change, so
history behaves exactly as on main.
* test(terminal): pin the parse wait and the cancelled-shutdown flag
Final-review follow-ups: a drain test where the TUI's ?1049h is still queued
when the image arrives, and a transport test replaying a push buffered during a
cancelled shutdown; each fails when its guard is removed. The requested-image
test now names the exited-TUI case it covers, requestSnapshot shares the
snapshot image type, and the clear comment no longer implies every clear drops
scrollback.
* Admit short required auxiliary checks after PR preflight (#25317)
* Clarify keep-awake tooltip behavior by platform (#25323)
* fix: make keep-awake tooltip lid behavior platform-aware
Extracted platform-specific lid behavior notes into a reusable function and
updated the keep-awake tooltip to show accurate descriptions for each OS:
macOS explains that closing the lid may still sleep the device, Windows
references device power settings, and Linux clarifies Orca's lid-close request
behavior. Updated copy to be device-agnostic instead of Caffeinate/MacBook
specific.
* fix: clarify macOS keep-awake tooltip copy and add translations
Update agent keep-awake descriptions on macOS to accurately explain that the feature prevents idle sleep (not all sleep modes) and only works with the lid open. Add translations for Spanish, French, Japanese, Korean, and Chinese.
* fix(native-chat): show "Sending…" on a message until the host confirms it (#24606)
* fix(native-chat): no "unconfirmed" line while Orca resends a send on its own
A send whose answer was lost (for example after reopening the chat mid-send)
showed "Message delivery is unconfirmed." with a Retry for the moment before
the automatic resend under the same message id confirmed it. One rule now
decides both: the resend runs, and the notice stays quiet, only while the
send's answer is lost, the user never retried it, it was not still going out
when the user pressed Stop, and the host has no record of it yet. Once the
host has any record of it, the line and Retry are exactly as before.
* fix(native-chat): say "Sending…" on a message until the host confirms it
A send whose answer was lost was resent quietly under the same id but looked
like any delivered message. Every message still in the outbox with no failure
to show now says "Sending…", muted, in place of its time, until the journal
holds a row for it. Rows that say a message did not go through keep only that
line and its Retry. The notices read the outbox through the same reconcile as
the transcript, so a row that lands clears the marker in one step.
* fix(native-chat): keep "Sending…" until the host has the message, in the time's slot
A message the user retried, a second message in doubt, or one requeued over a
rejected row had a journal row that did not hold it, so it showed nothing and
looked sent. "Sending…" now stays until the row is pending or accepted.
The marker took the place of the whole meta row, so the copy button went away
while sending and the row jumped when it cleared. The row now stays mounted:
copy keeps its hover reveal and "Sending…" sits where the time goes.
* test(native-chat): move the delivery probe tests into their own file
NativeChatStructuredSessionDelivery.test.tsx went over the 800-line lint
limit after main was merged. The eight tests for the automatic probe of an
unconfirmed message move unchanged to
NativeChatStructuredSessionDelivery.probe.test.tsx, which uses the shared
structured-session test harness for its mocks. The outbox seeding and probe
clock helpers move into that harness so both files share them.
The at-most-once reliability gate now lists the new file, with a fresh
evidence run.
* fix(mobile): keep the working rings turning on the OTA page (#25299)
* fix(mobile): keep the working rings turning on the OTA page
Animated.loop starts a native loop whenever the timing asks for the native
driver; the web has none, so the JS fallback ran one turn and froze at 360deg.
Ask for the native driver only off the web.
Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
* test(mobile): pin the native driver on native spinners
Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
* refactor(mobile): share the working ring rotation between both rings
Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
* docs: align contributor guidance with the PR template (#25034)
* fix(ui): restore IME Enter protection in workspace details (#24099)
Restore IME Enter protection in workspace details by reusing the existing composition tracker. Reset Notes ownership at textarea detachment and preserve sizing behavior. Repair isolated native test-window delivery without changing the production foreground policy or original native input assertions.
Fixes#24097
Related contributor history: #10711, #11067, #13128, #13282.
Original implementation and macOS recordings: @setodeve, commit b30f095.
Verified on required stock Linux X11/Wayland checks and independent frozen-source review.
Co-authored-by: setodeve <keinick11@outlook.com>
* Preserve large paste destinations and native Undo boundaries (#25177)
* Preserve image clipboard targets and content across editor changes (#25176)
* Preserve image insertion targets with one captured destination
* Set image paste test caret through the editor selection
* Validate OpenCode worker model preferences on the execution host (#24624)
* feat(orchestration): support OpenCode worker model selection
Allow supervised OpenCode workers to use per-launch model overrides through the existing launch-preference and receipt path.
Preserve existing OpenCode agent arguments while replacing only model flags, and reject unsupported effort values explicitly.
Keep Native Chat option exposure unchanged and update the worker CLI and orchestration guidance.
* fix: wait for OpenCode worker composer before first dispatch
Reuse captured composer readiness on local and paired execution hosts and revoke launching-shell paste anchors.
Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com>
* feat(opencode): probe execution-host CLI capabilities
* fix(opencode): select plugin default for execution host loader
* fix(opencode): limit prompt prefill capability to verified release
* feat(orchestration): gate OpenCode worker model preferences by host capability
Co-authored-by: user141514 <user141514@users.noreply.github.com>
* feat(opencode): probe launch capabilities on the execution host
* feat(opencode): probe execution-host CLI capabilities
* feat(opencode): probe launch capabilities on the execution host
* feat(orchestration): resolve explicitly configured command aliases
* fix(opencode): select plugin loader for the launched host binary
* fix(opencode): match WSL probe cwd and declared guest environment
* fix(orchestration): verify available OpenCode model on execution host
* fix(opencode): preserve launch environment deletion boundaries
* wip(opencode): authorize native startup prompt intent at execution owner
* fix(opencode): atomically replace status plugin entrypoints
* fix(opencode): retain plugin permissions across restrictive umasks
* test(opencode): resolve permission fixture from primary cwd
* feat(opencode): install startup prompt plugin independently of status hooks
* fix(opencode): wait for admitted startup intent and preserve failed-launch briefs
* fix(opencode): confine overlay manifest cleanup to owned directories
Co-authored-by: Adnan Khan <adnank11427@gmail.com>
* fix: wait for OpenCode worker composer before first dispatch
Reuse captured composer readiness on local and paired execution hosts and revoke launching-shell paste anchors.
Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com>
* feat(opencode): probe execution-host CLI capabilities
* fix(opencode): select plugin default for execution host loader
* fix(opencode): limit prompt prefill capability to verified release
* feat(opencode): probe launch capabilities on the execution host
* fix(opencode): select plugin loader for the launched host binary
* fix(opencode): match WSL probe cwd and declared guest environment
* fix(opencode): preserve launch environment deletion boundaries
* wip(opencode): authorize native startup prompt intent at execution owner
* fix(opencode): atomically replace status plugin entrypoints
* fix(opencode): retain plugin permissions across restrictive umasks
* test(opencode): resolve permission fixture from primary cwd
* feat(opencode): install startup prompt plugin independently of status hooks
* fix(opencode): wait for admitted startup intent and preserve failed-launch briefs
* fix(opencode): unsubscribe hook settings during async host shutdown
* fix(opencode): confine overlay manifest cleanup to owned directories
Co-authored-by: Adnan Khan <adnank11427@gmail.com>
* test(readiness): census recorded OpenCode composer boots
* fix(opencode): reject redirected overlay parents before cleanup
* fix(orcad): retain runtime cleanup when subscribing to hook settings
* refactor(launch): extract OpenCode config and attachment authority
* fix(opencode): retain host version selection across relay restarts
* fix(opencode): pass run prompts as positional messages
Preserve run flags and use the existing shell quoting and run-command detector
to append the initial message after --, reusing an existing separator.
TUI launches retain their version-selected prompt transport and draft behavior.
Original run-order work: @coelho-doti (#13065, tracked in #17551).
* fix(opencode): keep wrapped run tasks positional
Recognize supported environment prefixes and PowerShell call operators without
mistaking prompt arguments for executables. Keep environment and run separators
separate, preserve the task text and exclude run commands from native submission.
Source-parent: 23fc08b4e9
Related-to: stablya/orca#17551
Credits: @coelho-doti (stablya/orca#13065)
* Prepare complete private OpenCode launch validation source
Integrate the complete reviewed readiness, capability, native prompt, overlay and positional-run source onto frozen main. Preserve canonical atomic ACL retry, status generator/disposal, restrictive-umask fixtures and unowned source. Keep supported wrapped run commands positional.
Private-validation-source: a44345ce49
Original-full-source: 23fc08b4e9
Original-core-base: 8186ded0bd
Frozen-main: 08ee7ba9ef
Owned-source-paths: 111
Publication-policy: private validation only; preserve the six separate PR boundaries and held model/provider drafts
Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com>
Co-authored-by: Adnan Khan <adnank11427@gmail.com>
Credits: juli-gonzalez readiness contribution; Ahmed Nagy atomic plugin writer; coelho-doti positional run contribution
* Prepare private complete 111-path launch validation on current main
Private validation only. Preserve main credential additions and original launch ownership. Held model and provider topics remain excluded.
* Recognize env options before positional OpenCode run messages
* STRICT launch CI contract correction
* CAPS launch CI contract correction
* INTENT launch CI contract correction
* test(opencode): wait for malformed claim retries before expiring intent
Observe real endpoint I/O completion under fake timers before forcing expiry.
* test: initialize Claude prompt state in output retention fixture
* Verify OpenCode catalog model launches and preserve current launch behavior
* Verify OpenCode catalog model launches and preserve current launch behavior
* Wait for OpenCode location hydration in intent startup
* Refuse unverified new-worktree OpenCode model launches and record startup attribution
* fix(opencode): bind startup readiness to the composer location
* Bind OpenCode startup readiness to the current location in intent startup
* Restore the owning Orca CLI path after shell profiles
* Use a literal marker for the Bash lookup regression
* Preserve plain panes and initialize zsh after prompt hook replacement
* Preserve user line-editor dispatchers during deferred startup
* fix: retain CLI startup when global Zsh replaces prompt hooks
* test: replay global Zsh hook replacement after host startup
* test: isolate controlled Zsh widgets from distro keyboard setup
* fix(shell): preserve user hooks during deferred zsh initialization
* Retry interrupted OpenCode startup prompt claims
* Keep completed Zsh startup hooks retired when the wrapper is sourced again
* Reject truncated OpenCode catalogs and explain worktree model limits
* Use a template literal in truncated-catalog coverage
* Refuse unfinished OpenCode model catalogs
---------
Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com>
Co-authored-by: user141514 <user141514@users.noreply.github.com>
Co-authored-by: Ahmed Nagy <ahmednagy25t@gmail.com>
Co-authored-by: Adnan Khan <adnank11427@gmail.com>
Co-authored-by: Orca startup hydration review <agents@stably.ai>
Co-authored-by: Codex <codex@openai.com>
Co-authored-by: Orca maintenance <orca-maintenance@users.noreply.github.com>
Co-authored-by: Orca <dev@stably.ai>
Co-authored-by: Orca campaign <orca-campaign@local.invalid>
Co-authored-by: Orca OpenCode issue campaign <opencode-campaign@stably.ai>
Co-authored-by: OpenCode issue campaign <codex@localhost>
* Keep workspace deletion dialogs steady while changes load (#25321)
* Keep workspace deletion warnings from shifting the dialog
* Tighten spacing in workspace deletion confirmations
* Address deletion dialog review and synchronize localization catalogs
* fix(editor): restored tabs for files outside your projects no longer fail with Access denied (#24489)
* fix(editor): read files outside projects without a grant a restart loses
A file opened from outside every project (e.g. ~/notes.txt from the floating
workspace) read through an in-memory grant. After a restart the restored tab
only renewed that grant when it stored a full path, so a tab saved relative to
the floating workspace folder failed with "Access denied" and Retry repeated it.
Single-file reads (read, stat, exists) and open-editor-tab saves now resolve a
path outside every project in place. Paths inside a project keep the full
containment check, so a project's symlinks still cannot escape it, and every
other write stays inside projects.
* fix(editor): re-grant restored floating-workspace tabs by owner, not path shape
Problem: a file opened from the floating workspace (e.g. ~/notes.txt via
Cmd-click in the floating terminal, the floating markdown picker, or a .md
opened from the OS) loads until restart, then shows "Access denied: path
resolves outside allowed directories". Main's external-path grants live only
in memory. On restore the editor re-granted only tabs that stored an absolute
path, but floating tabs store a path relative to the floating root (~ by
default), which is deliberately not an authorized root, so they were never
re-granted. Restored floating notebooks also failed to start a kernel.
The previous commit on this branch let main read and save any path outside a
project without a grant. That widened fs:readFile/stat/pathExists for every
caller, including automatic reads of untrusted content (markdown preview
images), which opened a Windows UNC credential leak and a /dev/zero
main-process memory blowup. This reverts that model entirely.
Fix: one helper decides which client-local path a tab needs re-granted by
ownership: a floating-workspace tab, or a tab stored outside its own project.
It never grants paths a local project root covers (a grant would also
authorize a project symlink's outside target), and skips SSH-owned,
runtime-owned and not-yet-hydrated owners. Every reader that can touch a
restored tab before or without the editor loader uses it: the loader, the
restored dirty-tab conflict scan, and the paired-mobile markdown bridge.
* fix(editor): let main decide which restored-tab paths a project already covers
Problem: the restore re-grant helper decided "already inside a project" in
the renderer from its worktree list. At startup that list only holds repos
the session references, so a floating tab inside an unlisted repo was granted
(including a symlink's outside target), and the renderer's path matcher
disagrees with main's on WSL \\wsl$ vs \\wsl.localhost, which stranded a
folder-workspace tab with "Access denied" after restart. The helper also
treated a folder workspace with a missing or ambiguous host as local.
Fix: the renderer now decides only by owner (a floating-workspace tab, or a
tab stored outside a project whose owner is explicitly local) and asks main
with `skipIfInsideAllowedRoots`. Main checks the path against its own allowed
and registered roots, in both the named and canonical-parent spelling against
both root spellings: a path a project covers gets no grant, an alias spelling
of a project path gets only that spelling, and a project symlink's outside
target is never granted. Explicit-open grants (Cmd-click, drag, explorer) are
unchanged. Tests now prove each reader waits for the grant before reading.
* fix(fs): decide a restored tab's project membership from every ancestor's real path
Problem: the restore re-grant decided "inside a project" from the named path
and the real path of its parent only. When a tab path crossed a project
directory symlink and named the project through a spelling that was neither
the registered root nor its realpath (a second alias, a `..` segment, a case
variant on a case-insensitive disk, a /var-style alias of an ancestor), no
check matched, the path took the full grant, and the symlink's outside target
became readable and writable.
Fix: a path is inside a project when the named path is inside a root, or the
real path of any ancestor folder is inside a root in its registered or real
spelling. Such a path gets at most its named spelling, never its realpath. An
ancestor that fails to resolve for any reason other than "missing" now fails
closed to the named-spelling grant instead of falling through to the full one.
Tests cover each spelling; the non-symlink case also runs on Windows.
* fix(fs): read local files as regular files only, from one bounded handle
Problem: fs:readFile stat'ed a path and then read it to EOF. A character
device such as /dev/zero reports size 0, passes the size limit and never ends,
so the main process buffers until memory runs out; a FIFO hangs the open.
Writes could also target an existing device or FIFO.
Fix: every local fs:readFile (editor and log snapshot) opens the path once,
non-blocking, refuses anything but a regular file, and reads the size check,
binary probe and content from that same handle, capped at the limit even if
the file lies about its size. The AI Vault log tail opens non-blocking too,
and fs:writeFile refuses an existing non-regular target.
* feat(fs): let desktop file requests declare their shape
Problem: main decided every local file request against one allow-list plus a
set of in-memory grants the renderer had to recreate after every restart, so
a file the user opened outside a project (for example from the floating
workspace) was denied once Orca restarted.
This adds the request shape the common pattern uses, alongside the grants for
now:
- no shape (the default): the path must be inside a project root main
recognises, symlinks included. Desktop requests also accept the app-owned
floating-workspace folder; paired-client RPC never does.
- user-file: a single file the user named by absolute path, used in place.
Only fs:readFile/stat/pathExists and saving (fs:writeFile) accept it.
- document-resource: an image or PDF a document references, limited to every
project root when the document is in one, else to the document's folder,
and refused by path text before any disk or network access.
Notebook kernels and AI Vault log tails check their open file as user-named.
* feat(editor): send each local file request's shape from the renderer
Problem: after a restart, a tab opened outside every project (a floating
workspace file, a file opened by absolute path, an OS-opened markdown) could
only be read if the renderer first re-granted its path, and readers that ran
before the editor loaded the tab had no grant at all.
The renderer now says what kind of request it is making, and main checks that:
- A persisted tab opened outside its owner's root (floating workspace, or an
absolute stored path) whose owner is explicitly local reads and saves as a
user-named file, from every reader: the editor loader, the restored-tab
conflict scan, the change banner and compare dialog, the paired-phone
markdown bridge and the save queue. Project tabs stay inside their root.
- Clicks, drops, typed paths and browser-opened notebooks stat as user-named.
- Markdown preview and rich-editor images are document resources, limited to
the document's roots or folder. Images the user pasted or attached into a
chat show as user-named; agent images stay inside the project.
- The image cache keys on the shape, so one shape's image never answers
another's request.
A ratchet test lists every renderer file allowed to create a user-named
request.
* refactor(fs): delete the in-memory path grant system
Problem: main kept a set of paths the renderer had asked it to allow
(fs:authorizeExternalPath). The set lived only in memory, so a file the user
opened outside every project could be read until Orca restarted and was then
denied, and every new reader of a restored tab had to remember to recreate
the grant first. Three rounds of re-deriving grants at restore each found
another reader or path spelling it missed.
Now that every desktop request declares its shape, nothing needs a grant:
- delete the grant set, authorizeExternalPath, the restore re-grant from the
earlier commits on this branch, the fs:authorizeExternalPath channel and its
preload and web-client entries;
- delete every renderer grant call (terminal and markdown link clicks, drops,
typed paths, the file explorer, AI Vault logs, chat attachments, browser
notebooks) and every main one (floating markdown picker and folder, OS-opened
markdown, keybindings.json, pasted images, import and upload sources);
- the floating workspace's picker-approved folders stay a terminal-cwd
allowlist only.
Main now holds no per-path permission, so a restart can't change any answer.
* feat(editor): open project links that lead outside the project as named files
Problem: a file inside a project that is a symlink to something outside it
opened fine from the file explorer or a terminal Cmd-click, then showed
"Access denied" after a restart: its tab was stored as a project file, and a
project request is refused when it resolves out of the project. A folder link
out of the project expanded in the explorer until restart and then failed with
a raw access error.
Now the click decides and the tab keeps that decision. Both gestures stat the
path inside the project first; if only the user-named check passes, the path
leads out of the project:
- a file opens by its absolute path, so it reads and saves as a file the user
named, the same before and after a restart;
- the explorer does not follow a folder link out of the project and says so
("This folder links outside the project, so it can't be opened here.").
Paths that stay inside the project still open as contained project tabs. Also
drops the AI Vault "path not authorized" message, which nothing shows now.
* chore: drop the casts the changed-code quality gate flags on this branch
The FileContent casts in the editor loader and the paired-phone markdown
bridge were never needed (the read result is already assignable). Tests stub
window.api through vi.stubGlobal and pass narrow stores without casting; the
one test store that still needs a cast states why.
* fix(fs): load chat images by type, and keep escaping project links readable
Problems found in review:
- Chat transcript images were trusted by message role: any user-role
"[Image: source: <path>]" (an injected Claude record, `orca terminal send`,
a paired client's image-ref) became an automatic user-named read as the row
scrolled into view, of any file type, and on Windows a network-share path
would have opened an SMB connection to that host.
- A document image named like an image but linking to a text file
(logo.png -> .env) was read as text.
- Windows device names (NUL.png, COM1.jpg) passed the path-text check of the
automatic image loads.
- A project symlink leading out of the project, opened by a typed path, a
tab-strip drop or a browser file:// notebook, was stored as a project tab
and immediately refused.
Fix:
- New chat-image request shape for every transcript image and the composer
preview, whoever's turn named it: an absolute local path whose requested and
real targets are image files, a regular file, size-capped; network-share and
device-namespace paths and Windows device names are refused by path text
before any filesystem call. Pasted screenshots still show after a restart,
and agent images outside the project now render.
- Document resources check the real target's type too, and refuse Windows
device names by path text.
- Typed paths, tab-strip drops and browser notebooks stat through the same
check as the explorer and terminal, and open an escaping link by its
absolute path.
- Tests pin the shape at the change banner, compare dialog, markdown preview
and image prewarm; a second ratchet lists every file that can open a tab the
tab rule reads as user-named, and its comment says what it can't see.
- Stale grant wording removed.
* fix(fs): tighten automatic image loads and the project-link check
Problems found in review:
- Two unit tests went red on this branch: the browser-share test still
expected reads without a shape, and the rename test's electron mock had no
app, which the desktop root check now needs.
- The device-name check ran on the raw path, so `NUL.png\.` or
`COM1.png\x\..` (reachable from markdown ``) reached the
filesystem; a document image whose real target was a device name passed.
- Chat images in a project that lives on a Windows network share no longer
rendered, though the markdown preview showed them.
- Any failed project check (a missing file, a dropped connection) was taken
as "this link leads out of the project" and opened as an absolute tab.
- Every local read allocated about 2 MiB, even for a tiny image.
Fix:
- Device names and device-namespace paths are checked on the resolved path
and on the real target, for chat images and document resources alike.
- A network-share path in an automatic load is read only inside a project
root (the user chose that share when adding the project); anywhere else it
is still refused by path text before any filesystem call.
- Only main's "outside allowed directories" refusal marks a project path as
leading out of the project; other errors surface as before. The message now
lives in shared code so both sides agree on it.
- Reads size their first buffer from fstat and confirm EOF with a 1-byte
probe; a file that grows past its reported size is still read in bounded
chunks up to the cap.
- Fixed the two red tests.
* refactor(fs): name file access by its role, not its structure
Problem: the static-analysis anti-slop check failed the PR because the new
code named the request's file access a "shape" (`shape`, `RequestShape`,
`TabShape`), which describes structure rather than the role.
Rename the main-process module filesystem-request-shape.ts (and its tests) to
local-file-access-resolution.ts, rename the symbols to fileAccess,
FileAccessResolution and TabFileAccessFields, and say "file access" or
"access kind" in the comments and test names. No behaviour change.
* fix(fs): refuse every Windows device-name spelling in automatic image loads
Problem: the device-name check split a file name only on '.', so names such
as NUL:.png, COM1:.png, NUL:stream.png (an alternate data stream) slipped
through, and CONIN$, CONOUT$, CLOCK$, COM0 and LPT0 were not listed. Those
reached the filesystem from a document or chat image before being refused.
Split on ':' as well, list the missing device names, and test each with
Windows path rules and zero filesystem calls. Also cover the case of a local
link that leads onto a network share outside every project (refused for chat
images), and correct the shared comment on chat-image access.
* fix(editor): let users rename and insert images into files opened outside projects
Renaming a file opened outside every project (tab double-click, editor
header) and inserting an image into such a markdown document failed with
"Access denied", even before a restart: both writes only passed the
project-root check. Document resources and chat images were also limited
by file type more strictly than users expect.
- Add a "document-folder" access kind for writes beside a document the
user opened: main allows renaming only that document, to a name inside
its own folder, and importing new files only into that folder, checked
by path text and again by real path, with Windows device names refused.
The renderer sends it only for local user-named, writable tabs (rename,
its undo/rollback, image insert); SSH and runtime requests never carry it.
- Document resources: drop the image/PDF type allowlist; folder
confinement, regular-file reads, the cap and path-text refusals remain.
- Chat images: judge only the real target's type, against every
previewable image type (AVIF added).
* fix(fs): a declared file-access kind never refuses what the project check allows
A full-path tab for a file inside a project (for example a link that
leads out, opened by its absolute path) was renamed under the
document-folder rule, which limited the new name to the file's own
folder, although the same rename with no declared access could move it
anywhere in the project. Any declared kind could be stricter than the
default in the same way.
Every desktop local file request now goes through one resolver,
resolveLocalRequestPath: it runs the default project check first (roots,
Orca's floating folder, symlink containment, outside-root path text
refused before any filesystem call) and only on a refusal applies the
declared kind's rule, which adds paths outside projects. Reads, saves,
rename source and target, and import destinations all use it, so a new
kind gets the rule for free. Automatic loads (document and chat) still
refuse Windows device paths and names by text first, even inside a
project; a device is never a file to show.
The document-resource rule no longer needs its own project branch, and
chat images no longer re-run the roots check for shares.
* fix(fs): symmetric outside-project renames, notebook real folder, same-share images
- Renaming a file opened outside every project accepted a name in a
subfolder (`archive/todo.md`), but the Undo and the rollback rename,
declared from the moved file, were then refused and the file stayed
moved. A rename under document-folder access must now land directly in
the document's own folder (checked by path text before any filesystem
call), so rename, Undo and rollback are symmetric. Image import still
accepts the folder or a folder under it. Inside projects the default
check still allows any in-project target.
- A notebook opened through a link inside a project started its kernel in
the link's folder instead of the real file's folder (main's behaviour),
because notebook and AI Vault log-tail paths skipped the project check.
Both now resolve through resolveLocalRequestPath (project check first,
then the user-file rule).
- A markdown file opened from a Windows share outside every project could
not show the images beside it. Document images on a share are now
allowed inside the document's own folder; the folder text check refuses
every other host and share before any filesystem call.
- resolveDesktopAuthorizedPath is async, so a synchronous failure in the
default check rejects like any other refusal.
* fix(fs): refuse share images outside projects again; keep renames and kernels as on main
- Reverts the same-share document image rule from the previous commit.
Its folder check compared hosts case-insensitively, so a host spelled
with U+212A KELVIN SIGN (or a decomposed accent) passed as the
document's own share and was contacted, reopening the network
credential leak. Document and chat images on a share outside every
project are again refused by path text before any filesystem call;
tests now cover the look-alike hosts with zero filesystem calls.
- Renaming a file opened outside every project into a project folder
passed the project check, but its Undo (declared from the new path)
was refused and the file stayed moved. When the rename source is
allowed only as the opened document, the new name must now land
directly in the document's folder even if a project would accept it
(resolveLocalRenamePaths).
- A notebook opened through a link outside every project started its
kernel in the link's folder; main used the real file's folder. The
kernel cwd is now the real file's folder in every case.
* fix(fs): a file opened outside every project renames to any path, and keeps its access
Renaming a document the user opened (floating workspace or full-path tab) now
follows the user-file rule: the source must be the opened document, and the
new path can be any absolute path, so a rename into another folder, a
subfolder or a project works, and its Undo (declared from the moved file)
comes back from there. Any other rename keeps the project check only. Remove
the same-folder rename rule and its tests; image import stays in the
document's own folder.
After a move, a tab stored by its full path keeps its full path instead of
being recomputed project-relative, so it keeps user-file access for save,
the next rename, image insert and restore after restart. Folder moves go
through the same remap.
Also un-export unused resolver exports and avoid a copy for single-chunk reads.
* fix(test): pass getInsertionRange in the image-insert access test (#25351)
#25176 replaced insertPos with getInsertionRange and #24489 added a test
using insertPos; together they broke main's typecheck.
* Update README downloads badge
* fix(native-chat): start a new chat after an earlier start failed (#24917)
* fix(native-chat): start a new chat after an earlier start failed
A chat whose start the host refused stayed registered under its agent and
workspace, so the + menu and new-tab search kept that agent disabled with a
spinner, and any later "new chat" for that agent (for example "Send notes to
> New agent") restarted the failed chat instead and dropped its own prompt.
Only launches in flight now hold the agent/workspace slot that later starts
join. The registry keeps every launch by session id and derives the holder,
so a failed chat keeps its own Retry while a new start makes a new chat.
* test: import the launch status from its own module
* test: name the owning host on the launch intents the new tests build
* fix(native-chat): a new start never joins an unconfirmed or retried chat
A new start for an agent joined a chat whose create answer was lost, or a
failed chat whose Retry was in flight, and dropped its own text: the
source-control "Fix with AI" action reported success with nothing sent,
"Send notes to > New agent" sent nothing, and after a reload the text became
an unsent draft in the old chat.
Only a new start's own first create now coalesces later starts (double click,
two callers at once). Each attempt records whether it is that first create or
a Retry/re-check of an existing chat; an unconfirmed or retried blank chat
keeps its own Retry and a new start opens a new chat with its text. A resume
still re-checks or joins its conversation's launch, since the host refuses a
second adoption.
* fix(native-chat): a different new request always opens its own chat
A new start used to join any blank chat still in its first create or still
sending its opening text, so a second "Fix with AI" for another check, or
notes sent to a new agent, landed in that first chat. Each first attempt now
keeps the request it was started with (its text and whether it is sent or
drafted). Only a repeat of that request (a double click, a retried call)
joins it, and it shares the text already staged, so the text is sent once.
Any other request opens a new chat with its own text. The + menu, new-tab
search and send-notes menus disable an agent only when their own pick would
join. Resume launches are unchanged.
* fix(native-chat): an empty chat still starting takes the first text sent to it
A blank chat that is still starting (a + pick, the empty-workspace default
chat) is empty, so the first request with text, such as notes sent to a new
agent, now goes into it instead of opening a second chat beside it. That
request becomes the chat's own request: an identical repeat joins and is sent
once, and any other request opens a new chat.
Move the logic that decides which launch a start joins out of the launch
registry into its own module, so the registry stays under the line limit
once #24904 lands beside it. Pin that a repeat arriving while the opening
text is still sending is sent once.
* test: an empty chat delivers the claiming text the way its request asked
* fix(native-chat): only a chat its user has not used yet takes another request's text
A blank chat that is still starting was claimable by the first request with
text even after its user had sent a message into it or typed into its
composer, so notes or a Fix with AI prompt could land in a conversation the
user had already started. Emptiness is now read from what the chat holds:
nothing in its outbox and no text in its composer draft. A chat its user has
used stays theirs, and the request opens a new chat.
If the claiming text cannot be saved, the claim is not recorded: the request
falls through to a new launch, whose save failure shows on that chat as for
any new launch, instead of reporting a failure nothing showed.
* fix(notes): notes handed to a send leave the next send until it settles
Notes sent to an agent stayed in the notes shelf until their chat delivered
them, so a second "Send notes" made while the first new chat was still
starting collected the first notes again. With every different request now
opening its own chat, those notes reached two chats.
When notes or browser annotations are handed to a send (a new agent, a
running agent from the menu, or a sidebar agent row), they are held in
memory against that send's own delivery result and left out of the next
send. Delivered notes are removed as before; a failed, refused or
undelivered send releases the hold, so they come back for the next send.
The notes menu now builds each scope's prompt from the notes it will send.
* test: give the annotation tray fixture its hand-off callback
* fix(notes): offer no send when every note is already on its way
When every note or annotation in a send is held by an earlier send still in
flight, the built prompt is empty. The notes menu already disabled its
trigger then, but its New agent rows still started an agent with no text,
the annotation Send buttons still opened, and a sidebar agent row could be
sent an empty prompt. The New agent rows, the annotation Send buttons and the
sidebar send now offer nothing when there is nothing to send.
* fix(notes): a new chat's staged prompt decides when its notes leave the shelf
Notes sent to a new agent were released back to the shelf as soon as the
chat's start failed, while that failed chat kept the same text staged for its
own Retry. Re-sending and pressing Retry put the notes in two chats, and Retry
alone left delivered notes listed as unsent.
For a new chat, the notes now follow the prompt it staged: held while that
message is in the chat's outbox, cleared from the shelf when any dispatch
(Retry or re-check included) sends it on, and back on the shelf when the
chat is closed and its outbox thrown away. A paired server's chat is found
once its start settles. Running-agent sends keep their own result.
While notes are only on their way, the send button reads "Sending…" rather
than "All notes sent".
* fix(notes): a new chat's saved message keeps its notes out of another send across a reload
The hold that keeps notes sent to a new agent out of the next send lived only
in memory. A reload while that chat had not yet sent them put the notes back
on the shelf while the chat's saved message still carried their text, so a
second "Send notes" sent them twice.
The staged message now saves the send keys of the notes it was built from.
The shelf treats a note as on its way while any saved message carries its key,
read from the saved outboxes on first use and kept current by the outbox's one
write funnel. When a message carrying notes is sent on (its own start, a Retry
or the re-check), those notes are cleared from their shelf; when it is thrown
away with its chat, they come back. Browser annotations sent to a new chat use
the same keys while the app runs. Running-agent sends keep their in-memory hold.
This replaces the per-message watch and outcome promise from the previous
change.
* fix(notes): release a gone chat's notes, and clear sent ones in the web client too
Two gaps in keeping notes out of another send while a saved chat message
carries them:
- The web client never installed the clearing that removes notes once a
chat sends them on, so after a Retry there the notes stayed listed. It is
now installed, once per renderer, by the background services both the
desktop and the web client load with App.
- A saved message carrying notes was thrown away only by this window's own
close. A chat closed from the phone, another window or while Orca was off,
or closed here without a known host, kept its notes held for good. A chat
the host stops listing (affirmed, and not a launch still starting or
failed) now has its queued messages thrown away once that sync lands, as
does a close that can name no host, so the notes come back.
* Revert "fix(notes): release a gone chat's notes, and clear sent ones in the web client too"
This reverts commit ae0b9f3fea.
* Revert "fix(notes): a new chat's saved message keeps its notes out of another send across a reload"
This reverts commit b88d0dc2cc.
* fix(native-chat): a new chat joins only a re-delivery of the same user action
"The same request" was recognised by its content (agent, workspace, trimmed
text, sent or drafted), which split one action whose text changes between
deliveries ("Fix with AI" re-fetches logs) and merged two different actions
that happen to carry the same text.
Each user action now mints one request id where it is handled (the click,
menu pick, notes send, shortcut, Fix with AI press, quick command; a worktree
create uses its creation id; programmatic starts mint once at their entry)
and passes it through the launch plan, its verdict and the structured launch
options, where it is required. A new start joins a chat only when its first
attempt carries the same id: a double click or a caller retrying its own call
makes one chat and sends the first delivery's text once; any other action
opens its own chat, whatever its text. An empty chat that is still starting
is still claimed once by the first action with text, and then belongs to that
action's id. Resume launches keep joining their conversation's launch.
The + menu and new-tab search no longer grey out an agent while one of its
chats starts: every pick is a new action and opens its own chat. The id lives
in memory only; nothing reads it after a reload (a restored launch is reached
by its session id through Retry, never joined).
* fix(native-chat): a new chat with no text reuses an empty one instead of stacking another
Two bare "+ > Claude" picks (or new-tab search, the new-agent shortcut, the dashboard) opened two
empty chats. A request with no text now focuses an existing empty chat for that agent in that
workspace: one still starting (joined in the launch, as a re-delivery is) or one that published and
sits idle (its host's journal holds no request, read live from the status feed). Empty also means
nothing queued, no composer text or images, and no launch draft its composer has not taken. Failed,
unconfirmed, resumed and other-agent/workspace chats are never reused. A request with text still
opens its own chat, or claims an empty starting one as before; request-id dedupe is unchanged.
* fix(native-chat): a new chat with no text reuses an empty chat only in the split it was opened from
Pressing + in the right-hand split focused an empty chat sitting in the left-hand split. The reuse
now looks only in the tab group the pick targets (the caller's group, else the workspace's active
group, which is where its tab would open); a pick in another split opens a new chat there. Applies
to both an empty chat still starting and one that published and sits idle.
* fix(native-chat): text sent to a new agent claims an empty starting chat only in its own split
Notes sent from the right-hand split could land in, and focus, an empty chat still starting in the
left-hand one. A request with text now claims an empty starting chat only in the tab group it opens
in, the same rule a request without text follows; elsewhere it opens its own chat there.
* fix(test): give async image-insert store mocks the openFiles list (#25357)
#25176 and #24489 landed together and collided in
insertRichMarkdownImageFromPath. #25176 added three test files whose
@/store mock returns { settings, folderWorkspaces, worktreesByRepo }.
#24489 made the same function look up the open document with
state.openFiles.find(...) to pass document-folder access to the import.
Under the mock, openFiles is undefined, so the lookup throws, the catch
shows "Failed to insert image", and no image is inserted -- 38 tests
fail on main.
The real store always carries openFiles, so production behavior of both
PRs is intact; the mocks were just written against the pre-#24489 shape.
Add openFiles: [] to the three mocks.
* Keep OpenCode worker model selection separate from the default (#24768)
* feat(orchestration): support OpenCode worker model selection
Allow supervised OpenCode workers to use per-launch model overrides through the existing launch-preference and receipt path.
Preserve existing OpenCode agent arguments while replacing only model flags, and reject unsupported effort values explicitly.
Keep Native Chat option exposure unchanged and update the worker CLI and orchestration guidance.
* fix: wait for OpenCode worker composer before first dispatch
Reuse captured composer readiness on local and paired execution hosts and revoke launching-shell paste anchors.
Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com>
* feat(opencode): probe execution-host CLI capabilities
* fix(opencode): select plugin default for execution host loader
* fix(opencode): limit prompt prefill capability to verified release
* feat(orchestration): gate OpenCode worker model preferences by host capability
Co-authored-by: user141514 <user141514@users.noreply.github.com>
* feat(opencode): probe launch capabilities on the execution host
* feat(opencode): probe execution-host CLI capabilities
* feat(opencode): probe launch capabilities on the execution host
* feat(orchestration): resolve explicitly configured command aliases
* fix(opencode): select plugin loader for the launched host binary
* fix(opencode): match WSL probe cwd and declared guest environment
* fix(orchestration): verify available OpenCode model on execution host
* Add host-owned OpenCode and Devin account profiles
* fix(orchestration): inspect OpenCode models with selected account environment
* fix(orchestration): bind model validation to direct existing-workspace launch
* fix(opencode): preserve launch environment deletion boundaries
* fix(orchestration): limit effective model contract to verified OpenCode release
* wip(opencode): authorize native startup prompt intent at execution owner
* fix(opencode): atomically replace status plugin entrypoints
* fix(opencode): retain plugin permissions across restrictive umasks
* test(opencode): resolve permission fixture from primary cwd
* feat(opencode): install startup prompt plugin independently of status hooks
* Restore inherited account environment and preserve cleanup retries
* fix(opencode): select plugin loader for the launched host binary
* fix(opencode): match WSL probe cwd and declared guest environment
* fix(opencode): preserve launch environment deletion boundaries
* wip(opencode): authorize native startup prompt intent at execution owner
* fix(opencode): atomically replace status plugin entrypoints
* fix(opencode): retain plugin permissions across restrictive umasks
* test(opencode): resolve permission fixture from primary cwd
* feat(opencode): install startup prompt plugin independently of status hooks
* Check relay environment values before merging
* fix(opencode): wait for admitted startup intent and preserve failed-launch briefs
* fix(opencode): confine overlay manifest cleanup to owned directories
Co-authored-by: Adnan Khan <adnank11427@gmail.com>
* fix: wait for OpenCode worker composer before first dispatch
Reuse captured composer readiness on local and paired execution hosts and revoke launching-shell paste anchors.
Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com>
* fix(orchestration): validate configured aliases with target shell grammar
* fix(orchestration): use actual shell and refuse assignment-only aliases
* fix(opencode): wait for admitted startup intent and preserve failed-launch briefs
* Support verified OpenCode v2 model selection in fresh native workers
* fix(opencode): refuse unsupported startup preferences before model probing
* feat(opencode): probe execution-host CLI capabilities
* fix(opencode): select plugin default for execution host loader
* fix(opencode): limit prompt prefill capability to verified release
* feat(opencode): probe launch capabilities on the execution host
* fix(opencode): select plugin loader for the launched host binary
* fix(opencode): match WSL probe cwd and declared guest environment
* fix(opencode): preserve launch environment deletion boundaries
* wip(opencode): authorize native startup prompt intent at execution owner
* fix(opencode): atomically replace status plugin entrypoints
* fix(opencode): retain plugin permissions across restrictive umasks
* test(opencode): resolve permission fixture from primary cwd
* feat(opencode): install startup prompt plugin independently of status hooks
* fix(opencode): wait for admitted startup intent and preserve failed-launch briefs
* fix(opencode): unsubscribe hook settings during async host shutdown
* fix(opencode): confine overlay manifest cleanup to owned directories
Co-authored-by: Adnan Khan <adnank11427@gmail.com>
* test: preserve typed calls in configured worker target checks
Replace Reflect.apply with the existing typed prototype call pattern so the unchanged regression cases pass the anti-slop lint gate.
* test: check typed model host calls and terminal delay state
Replace Reflect dispatch and property reads with checked access and typed calls. Complete the existing resume request fixture with its required identity fields without changing the rejection boundary or assertions.
* test(readiness): census recorded OpenCode composer boots
* fix(opencode): reject redirected overlay parents before cleanup
* fix(orcad): retain runtime cleanup when subscribing to hook settings
* refactor(launch): extract OpenCode config and attachment authority
* fix(opencode): retain host version selection across relay restarts
* fix(opencode): pass run prompts as positional messages
Preserve run flags and use the existing shell quoting and run-command detector
to append the initial message after --, reusing an existing separator.
TUI launches retain their version-selected prompt transport and draft behavior.
Original run-order work: @coelho-doti (#13065, tracked in #17551).
* fix(opencode): keep wrapped run tasks positional
Recognize supported environment prefixes and PowerShell call operators without
mistaking prompt arguments for executables. Keep environment and run separators
separate, preserve the task text and exclude run commands from native submission.
Source-parent: 23fc08b4e9
Related-to: stablya/orca#17551
Credits: @coelho-doti (stablya/orca#13065)
* Prepare complete private OpenCode launch validation source
Integrate the complete reviewed readiness, capability, native prompt, overlay and positional-run source onto frozen main. Preserve canonical atomic ACL retry, status generator/disposal, restrictive-umask fixtures and unowned source. Keep supported wrapped run commands positional.
Private-validation-source: a44345ce49
Original-full-source: 23fc08b4e9
Original-core-base: 8186ded0bd
Frozen-main: 08ee7ba9ef
Owned-source-paths: 111
Publication-policy: private validation only; preserve the six separate PR boundaries and held model/provider drafts
Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com>
Co-authored-by: Adnan Khan <adnank11427@gmail.com>
Credits: juli-gonzalez readiness contribution; Ahmed Nagy atomic plugin writer; coelho-doti positional run contribution
* Prepare private complete 111-path launch validation on current main
Private validation only. Preserve main credential additions and original launch ownership. Held model and provider topics remain excluded.
* Recognize env options before positional OpenCode run messages
* STRICT launch CI contract correction
* CAPS launch CI contract correction
* INTENT launch CI contract correction
* test(opencode): wait for malformed claim retries before expiring intent
Observe real endpoint I/O completion under fake timers before forcing expiry.
* test: initialize Claude prompt state in output retention fixture
* Verify OpenCode catalog model launches and preserve current launch behavior
* Verify OpenCode catalog model launches and preserve current launch behavior
* Refuse unverified new-worktree OpenCode models and complete host audits
* Wait for OpenCode location hydration in intent startup
* Refuse unverified new-worktree OpenCode model launches and record startup attribution
* fix(opencode): bind startup readiness to the composer location
* Bind OpenCode startup readiness to the current location in intent startup
* Restore the owning Orca CLI path after shell profiles
* Use a literal marker for the Bash lookup regression
* Preserve plain panes and initialize zsh after prompt hook replacement
* Preserve user line-editor dispatchers during deferred startup
* fix: retain CLI startup when global Zsh replaces prompt hooks
* test: replay global Zsh hook replacement after host startup
* test: isolate controlled Zsh widgets from distro keyboard setup
* fix(shell): preserve user hooks during deferred zsh initialization
* Retry interrupted OpenCode startup prompt claims
* Keep completed Zsh startup hooks retired when the wrapper is sourced again
* Reject truncated OpenCode catalogs and explain worktree model limits
* Use a template literal in truncated-catalog coverage
* Refuse unfinished OpenCode model catalogs
---------
Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com>
Co-authored-by: user141514 <user141514@users.noreply.github.com>
Co-authored-by: Ahmed Nagy <ahmednagy25t@gmail.com>
Co-authored-by: Adnan Khan <adnank11427@gmail.com>
Co-authored-by: Orca startup hydration review <agents@stably.ai>
Co-authored-by: Codex <codex@openai.com>
Co-authored-by: Orca maintenance <orca-maintenance@users.noreply.github.com>
Co-authored-by: Orca <dev@stably.ai>
Co-authored-by: Orca campaign <orca-campaign@local.invalid>
Co-authored-by: Orca OpenCode issue campaign <opencode-campaign@stably.ai>
Co-authored-by: OpenCode issue campaign <codex@localhost>
Co-authored-by: Nathan Parker <nwparker@users.noreply.github.com>
* Add repository OpenCode permission defaults (#25326)
* test(config): reproduce rejected repository OpenCode config
* Add repository OpenCode permissions and allow its reviewed root config
* fix: preserve sensitive OpenCode confirmation prompts
---------
Co-authored-by: Orca campaign recovery <campaign-recovery@example.invalid>
Co-authored-by: Orca OpenCode Campaign <opencode-campaign@local.invalid>
* fix(relay): skip the dead-cell sweep's host scan when no cell qualifies (#25350)
evacuateDeadCells ran one query that joined every assignment to its cell's
liveness and fence state, ordered by primary key with LIMIT 100. In
production no row ever matched (the only dead cells are stale existing-only
cells without a committed fence), so the planner walked the whole
relay_assignments primary key every call: 551 ms mean over 85k calls,
~12% of relay database time.
A cell-level pre-check now evaluates the predicate's cell-only half over
the ~33-row cell tables (3 ms in production). It is a superset of the cells
the host query can act on, so the sweep returns early when it is empty and
otherwise restricts the host query to those cells. The host predicate is
unchanged.
* fix(relay): prune released control-connection reservations in bounded batches (#25352)
relay_control_connection_reservations was never deleted: 13.8M rows and
9.1 GB in production, 99.999% of them in state 'released' and ~400k more
a day. Nothing reads a released row (every reader filters it out by
state), but each placement still locks all of its host's rows, ~160 on
average.
A new director sweep step deletes released rows older than a day. It walks
the heap in TID ranges of 16 pages, because without an index on
released_at a `LIMIT n` delete plans as a sequential scan from page 0 that
gets slower as the head of the heap empties (production EXPLAIN). Each
statement selects its rows FOR UPDATE SKIP LOCKED, so a row a request holds
is skipped rather than waited on, and deletes them by `ctid = ANY(ARRAY(...))`
so the delete is always a TID scan. A tick stops at 400 rows, 128 pages or
250 ms, which drains the backlog over about three days at five directors.
* chore(relay): move US cells c32 and c33 to the general same-cap list after promotion (#25367)
Both were promoted to general on 2026-10-01 (selector 344 shows them general),
but the same-cap job still classed them migration-only. Rollback mode on either
would isolate (general -> migration-only) before its no-op check failed, demoting
a live cell. They stay out of the fleet pool list (pool 10, not 16).
Claude-Session: 1145a80d-dec4-4a9b-9373-bbbb876b9041
* Support modern Qoder commands and verify authenticated resume
Start and resume Qoder through the existing execution-host selector when only the documented qoder command exists. Preserve legacy qodercli preference, explicit commands, quoting and session identity; disconnected SSH execution refuses without local fallback.
Mobile history uses command-at-create only with the optional owned-create capability and an existing stable mutation identity. Reconcile authoritative execution-host inventory before retrying creation, adopt the same surviving operation, preserve WSL/incarnation metadata and restore only missing original launch metadata. Changed retry settings cannot replace original capture. Bounded evidence expires after 15 minutes; unavailable inventory or original evidence refuses recovery and leaves surviving execution untouched. Authoritative inventory proving absence preserves the existing recreation behavior; this is not a durable exactly-once ledger for completed one-shot side effects. Older hosts retain the acknowledged create-then-send path.
Scope mobile launch authority to the current committed host/client generation, and recheck operation ownership after asynchronous preparation before later sends. Retire the mutation once the host acknowledges the resume; later ownership changes stop navigation without reporting a completed resume as failed or reusing a cached legacy pane. Preserve genuinely interrupted mutation identity. Preserve current-main OpenCode validation and merged Pi/Cursor behavior. Correct unchanged-main editor test fixtures to their production insertion-range and store contracts while retaining original assertions and production behavior.
Credit: Neil Parker (@nwparker); Soperf and jyang for Qoder integration/history groundwork in #24614; actual Pullfrog and CodeRabbit reviews and the independent Source reviewer for the mobile retry, launch capture, evidence lifetime and connection ownership findings. Conservative positional process recognition and existing folder-history matching-worktree limitations remain documented.
* fix(relay): retain confirm results for a week and audit events for 90 days (#25353)
* fix(relay): retain confirm results for a week and audit events for 90 days
relay_confirm_results (8.3M rows, 5.8 GB) and relay_audit_events (8.4M
rows, 3.6 GB) were never deleted. The director's credential cleanup now
reaps both after its existing passes:
- Confirm results older than 7 days. The only reader replays a stored
result for a retry of the same request on the same connection basis; a
different basis is already refused as a tuple mismatch. committed_at has
no index, so this uses the TID-window reaper from the reservation prune,
capped at 250 rows a tick (the 7.4M-row backlog drains over 2-3 days).
- Audit events older than 90 days, ordered by `at` so the batch walks
relay_audit_events_at. Nothing in the relay reads them back. The oldest
row is from 2026-07-14, so this deletes nothing until 2026-10-12.
reapBatch now deletes by `ctid = ANY(ARRAY(...))` on Postgres: with
`ctid IN (...)` the planner can choose a hash join over a sequential scan
of the whole table.
* chore(relay): record the decided audit retention
* Reduce repeated CI setup and overlap mobile typechecks (#25359)
* Measure remaining CI import, diagnostic and checkout savings
* Qualify remaining CI candidates on hosted runners
* Qualify independent mobile typecheck overlap on Actions
* Keep explicit RPC test registries from loading unused methods
* Qualify complete RPC registry cohort and mobile cancellation
* Promote measured CI setup and typecheck savings
* Recognize the shared RPC test guard in lint policy
* Align the mobile barrier contract with independent typechecks
* refactor(relay): stop creating three tables nothing writes (#25354)
* refactor(relay): stop creating three tables nothing writes
relay_confirmable_splices, relay_cell_drain_attempts and
relay_migration_leases are created at every boot and referenced nowhere
else on main except tests. In production all three hold 0 rows and
pg_stat_user_tables shows 0 inserts, 0 sequential and 0 index scans since
the 2026-09-28 stats reset.
This removes them from the schema and the transaction-phase table, and
drops the test references. It does not drop them: an older image still
runs CREATE TABLE IF NOT EXISTS at boot, and a drop racing that create can
fail the older boot's schema step. A follow-up can drop them once no image
that creates them can be rolled back to.
* docs(relay): note the account-erasure prerequisite for dropping retired tables
* fix(orchestration): accept max and ultra effort for Codex models Orca does not list yet (#25375)
Codex models newer than Orca's built-in list (gpt-6.x) were capped at xhigh,
so worker-start refused --effort max/ultra that the installed Codex supports.
Unknown Codex models now accept the full known effort vocabulary; unknown
effort names are still refused.
Refs STA-9312
* fix(mobile): honour the desktop pinned-worktree placement setting (#25301)
* fix(mobile): honour the desktop pinned-worktree placement setting
Mobile always kept pinned rows in their groups as well as in Pinned. Desktop's
showPinnedWorktreesInGroups (default off) shows them only in Pinned. Read that
setting with its own settings.get operation, refreshed with the list's view
settings, and leave host and device-local pins out of their groups unless it
is on.
Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
* fix(mobile): keep lineage children with a pinned parent
Under single-location a pinned parent left its group while its unpinned child
stayed behind as a root. Visible descendants now follow a pinned ancestor into
Pinned, nested under it, as on desktop. Drop the inert host-id half of the
pinned-policy stale-reply guard; a host switch replaces the client.
Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
* refactor(mobile): fold pinned placement into the desktop view-settings sync
One "sync from desktop" callback now also reads settings.get for pinned
placement, unawaited so a slow read never holds the ui.get merge; the separate
callback and its plumbing are gone. Re-record the three host.view-settings
goldens, which gain only that settings.get send. The pin expansion walks the
lineage children index the renderer now shares, over visible rows only.
Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
* refactor(mobile): one stale-reply rule for the desktop view-settings sync
Both reads in syncViewSettingsFromDesktop now drop a reply only when the client
was replaced; the host-id half compared a captured value with itself. Pin the
no-wait invariant: the ui.get merge applies while settings.get never answers.
Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
* fix(mobile): follow a pin through rows hidden by search
Desktop's Pinned section walks lineage over every worktree and keeps the
visible descendants, so a grandchild still follows a pinned root when search
hides the middle row. Mobile walked visible rows only and left it in its group.
Build the children index from the unfiltered list; membership stays visible-only.
Drop the hostId arg the view-settings sync no longer reads. Pinned-lineage
tests move to their own file to stay under max-lines.
* refactor(mobile): read pinned placement in its own hook
The placement read was folded into the desktop view-settings sync, so that
callback juggled an awaited and an unawaited read and three goldens recorded a
settings.get that never answers. useHostShowPinnedInGroups now owns the setting
and the catalog refreshes it beside the view-settings sync on connect, focus and
mount; the sync, screen state and goldens are back to main.
The reader returns desktop's boolean, as its siblings do, so transport no longer
imports a worktree type and the two-literal policy union is gone. makeSection
always applies lineage, the pinned walk is a Set worklist, and the lineage
children index drops a set that duplicated its map's keys.
* fix(mobile): key pinned placement to the client that reported it
The host screen is reused across hosts, so the previous host's "show in groups"
value survived a switch until the new read landed, or for good if it failed.
The setting now carries the client that reported it and counts only for that
client, which also makes a late reply from a replaced client inert and drops
the clientRef plumbing.
* fix(mobile): drop a replaced client's late placement reply
Keying the value to its client hid a previous host's value but still let that
host's late reply overwrite the current host's, reverting the list to the
default until the next refresh. Restore the clientRef write guard; the client
key still covers the effect-long window before clientRef follows a switch.
* refactor(mobile): treat pinned placement like the screen's other host state
The placement hook kept its own host-switch handling (a client-keyed value plus
a clientRef guard) beside the screen's existing one. showPinnedInGroups now
lives in HostScreenState, resets with the other host-scoped values in
useHostScreenIdentity, and is read in the catalog refresh behind the same
clientRef check as the catalog fetch. The hook and the catalog argument go.
* feat(relay): declare Asia spare cell c34 as migration-only (#25336)
Adds a sixth asia-east2 cell at the C31 shape (cap 3000, 6000 request
units, pool 16, e2-standard-4) in asia-east2-c, pinned to the f30b5cb1
cell image. It gets its own topology and registration wave but no
promotion wave, so the admission script and workflow refuse to promote
it; it stays a migration-only landing zone and out of the fleet pool list.
Claude-Session: 1145a80d-dec4-4a9b-9373-bbbb876b9041
* Keep OpenCode foreground evidence when its background service starts (#25378)
* Keep large CSV previews responsive and add column resizing and links (#25381)
* Bound CSV preview memory and virtualize resizable linked cells
* Fix dense CSV previews and retain bounded viewport pages
* Align CSV record limits across BOMs and line endings
* Respect headful mode in CSV visibility checks
* Fix duplicate session option flags and preserve argument values (#25382)
* Fix duplicate session option flags and preserve argument values
* improve tests
* fix(activity): keep code blocks in row previews from rendering as scroll boxes (#25410)
The activity row previews the agent's last reply with the compact markdown
renderer, which draws fenced code blocks and tables as their own scrollable
boxes. Those boxes escaped the row's line clamp, so a reply with a code block
put a tall grey box with scrollbars in the middle of the list.
Row-scoped overrides now flatten code blocks and tables into plain wrapping
text, so the existing clamp cuts them like any other line. Other markdown
surfaces are unchanged.
* Add translations for terminal shell settings (#25418)
* Add translations for terminal shell settings and search
Wrap Terminal Pane shell configuration strings and terminal search keywords with translation calls to enable localization across supported languages. Add translated strings to all locale files and update the localization coverage allowlist to reflect properly translated content.
* fix translation
* fix(native-chat): show a reply cut off by an Orca crash or quit like a finished turn, with one explanation (#25043)
* fix(native-chat): read a crash-cut reply like a finished turn, with one explanation
A reply that an Orca crash or restart cut off said it failed three times: the
turn bar read "Failed after N", the chat's notice row said Claude stopped, and
the sidebar dot stayed red after the chat was read.
The turn bar now reads "Worked for N" for a proven crash/restart cut, the
notice row stays the one explanation, and the sidebar card's dot and agent
rows read failed only until the user has visited the chat (the same
acknowledgement that un-bolds the row), then read done. A failure, a user's
Stop, a replaced turn and an unconfirmed end keep their labels. The stored
outcome is unchanged.
* fix(native-chat): explain a turn a quit or eviction cut, once
A turn cut off by quitting Orca, an idle eviction or a teardown recorded the
same outcome as a crash-cut turn but wrote no notice row, so with the turn bar
now reading "Worked for N" nothing in the chat said it stopped.
The host stop's settle now writes the existing providerExited notice for the
latest turn when it ends as news (no person's Stop decided it): in the same
write as the host's own turn end, or right after the adapter's. It is keyed by
the turn, and skipped when an error row already explains that turn, so a retry
or an earlier exit row never leaves two.
* test(native-chat): narrow the turn scope and add the seen map in the crash-cut tests
* fix(native-chat): derive a cut turn's one notice on read instead of writing it at stop time
A turn cut short when the agent stopped without anyone asking now reads
"Worked for N", so it needs a row saying it stopped. Writing that row only on
the quit and eviction paths missed journals written before this change, a quit
that died between its drain and its settle, and any future stop cause.
The transcript now derives it from the journal on desktop and phone alike: a
root turn that ended interrupted with no verdict and no row about the stop
gets one notice in the provider-exit row's words. A stored exit row, matched by
its exit fact or its writer's identity rather than its tone, stays the
explanation, and so does the restart continuation's own outcome note, so a
refused resume says it once. The host's stop path is back to what it was.
* fix(sidebar): read a cut-short turn's red mark from its acknowledgement on every surface
A turn cut short with nobody asking reads failed only until the user has seen
it. The previous revision passed an optional "seen" flag to each caller, so any
surface that did not pass it, the chat's own tab dot among them, stayed red
beside a sidebar that read Done.
The verdict's display now takes the acknowledgement as part of what it reads:
the entry's stateStartedAt beside the time the user last acknowledged it, both
required, judged by one shared rule. Each surface joins it once where it builds
its rows: the sidebar's agent rows (and so the notes send menu), the workspace
card summary, the terminal tab bar, Cmd-J recent rows, and Activity threads.
Failures, Stops, replaced turns and unproven ends keep their marks as before;
the phone, which has no acknowledgement record, keeps the unseen reading.
* refactor(attention): use the one acknowledgement rule where it was copied
Auto-acknowledgement, the Activity unread count, dashboard row buckets and
notification acknowledgement each spelled the same "acknowledged at or after
the current state began" comparison; they now call the shared rule.
* test(mobile): type the cut-turn notice test's client and hook holder
* test: pin an older host's exit row by its writer and the sidebar rows' acknowledgement join
* test(sidebar): re-read the card and the tab when only an acknowledgement changes
* fix(native-chat): keep a cut turn's notice through a resume that carries on
A resume after a quit writes its "asked this agent to continue" note after its
own message, so counting that note as the cut's explanation removed the notice
once the resume went on, and made it flash away under automatic resume. Only the
notes that say the chat was not carried on (refused, not connected, not
confirmed) stand in for the notice now.
A row about the whole conversation now explains a cut turn only when no other
turn lies between them, and a failed start's row, which is about a start, never
does. The host writers and the rule share the row identity prefixes, with the
contract that a new row explaining a stop carries the provider-exited fact or
one of them.
* fix(sidebar): judge a cut as seen by the main agent's clock when a subagent holds the row
A subagent can keep a row working after its main agent was cut, and the row's
clock then predates the cut, so a look at the working chat counted as having
seen the cut and no red mark showed. The mark now compares the acknowledgement
with the later of the row's and the main agent's clocks; auto-acknowledgement of
the chat on screen reads the same clock, and its stamp covers it, so looking at
the chat still clears the mark. Bold rows, dashboard buckets, notifications and
unread counts keep the row's clock.
* fix(native-chat): tie a conversation-wide exit row to a cut only with no message sent since
A send after a quit's cut whose new agent died before its turn opened leaves a
provider-exit row about the whole conversation. That row is about the send,
not the earlier cut, so the cut kept no explanation. An exit row now explains
a preceding cut only when no message was sent in between; the restart notes,
which follow the continuation's own message, still need only no turn between.
The notes that say a resume did not carry the chat on are now told apart from
the continued note by their tone ('error' or 'warning'), which every host that
wrote them has set, instead of by their words.
* fix(native-chat): keep an owner's proven death the explanation of its cut after a send
A chat read before the startup reconcile settles its cut turn unverifiable;
if the user then sends a message, the reconcile proves the old agent dead and
writes its row about the conversation after that message. The row names the
old owner's death, never the send, so a message since no longer detaches it
from the cut. Only a provider-exit row, which a later start can write, still
needs no message sent since.
* test(sidebar): give the activity-status store mock the acknowledgement map
The card summary now reads acknowledgedAgentsByPaneKey; this test's hand-built
store state lacked it, so every summary read threw.
* refactor: move the seen-gated red mark out of this change
This change now keeps only the cut turn's label and its one derived notice.
The red mark that clears once the user has seen a cut turn moves to its own
change, so each can land alone; until it lands, the sidebar, tab bar, Cmd-J,
Activity and the notes send menu read a cut turn as failed, as before.
* test(native-chat): pin that a restart note after a continuation's turn leaves the cut's notice
* test(native-chat): keep a cut turn's notice beside a later message drawn as not sent
* Show provider credit balances alongside usage limits (#25408)
Display provider credit and currency balances separately from rate limits. Preserve quotas when balance data is unavailable and translate complete labels.
Supersedes #9363; credit to @mmarabel for the original implementation.
Co-authored-by: mmarabel <166927047+mmarabel@users.noreply.github.com>
* Edit CSV tables directly and remember column widths (#25442)
* Add direct CSV table editing and persistent column widths
* Preserve pending CSV edits across concurrent saves and panes
* Keep CSV input stable during autosave and table commands
* Cancel delayed menu paste after CSV pane focus changes
* Prove delayed CSV menu paste starts before focus changes
* test(linear): cover numeric-leading issue identifiers (#10241)
Expand numeric-leading Linear identifier coverage in the current parser and workspace source flow while rejecting numeric-only team keys in bare inputs and URLs. Main already implements the production behavior through #23423.
Repair two stale localization test assumptions found by full CI. All changes are regression tests.
Validation: 369 focused tests across 46 suites, local typechecking/lint/formatting, and full final-head CI passed.
Co-authored-by: Ken Fukuyama <kenfdev@gmail.com>
* Group CSV editor modules and tests in their own folder (#25476)
* fix: close worktree dialog before slow script checks (#25472)
* fix: move worktree script checks out of creation dialog
* fix: retain creation host across background preparation
* fix(mobile): stop Android from selecting words while the chat transcript scrolls (#22871)
* fix(mobile): stop Android from selecting words while the chat transcript scrolls
On Android every paragraph, heading, quote, code block and table cell in
the native chat transcript was a selectable TextView. Android starts a
word selection, with the magnifier, on a double tap or a long press, and
two flicks in the same spot while scrolling a FlatList register as a
double tap, so scrolling the chat kept selecting words. iOS is unaffected:
its UITextView path arbitrates scroll against selection itself.
Android now renders transcript text without inline selection: one gate in
MarkdownText covers every selectable span, and the user bubble follows
it. A long press on a message opens a sheet with "Copy message" and
"Select text", the latter a screen whose only content is one selectable
Text, so a selection can only start where the user asked for it. The
message row owns that sheet and mounts it only while open. iOS and web
keep their inline selection and get no long-press handler.
Verified on a Pixel 10 Pro Fold (Android 17): an adb double tap on the
transcript selects nothing, the same double tap inside "Select text"
selects a word, tool rows inside the bubble still expand on tap, and the
long press opens the sheet.
* fix(mobile): scope the Android selection gate to the transcript and route span long presses
Review follow-ups on #22871:
- The gate now applies only where `rangeSelectable` is passed (the chat
transcript). Task comments and file previews keep their selectable text
on Android as before.
- On the Android transcript, spans that take taps (links, file paths)
also take the row's long press, so a link under the finger no longer
swallows the copy/select sheet.
- Copied text keeps its whitespace; only whitespace-only blocks are dropped.
- The Android markdown test compares `String(node.type)` instead of a
type assertion, which the changed-code quality gate rejects.
* fix(mobile): route long presses on Markdown images to the row on Android
An image block is a Pressable of its own, so on the Android transcript it
now carries the row's onLongPress like tappable spans do; a long press on
an image opens the copy/select sheet instead of being swallowed. Test
extended with an image block.
* test(mobile): pin the Android long press from a chat row to its actions sheet
The existing row suite runs as iOS, where the bubble has no long press.
This one runs as Android: the bubble's long press mounts the actions
sheet with the message, the markdown receives the same handler, closing
unmounts the sheet, and the user bubble carries no inline selection.
* fix(mobile): preserve Android message selection while replies stream
---------
Co-authored-by: Neil <neil@stably.ai>
* Remove completed SSH picker E2E plan (#24824)
Co-authored-by: kazooooo-ma <zooooomer-com@gmail.com>
* fix(codex): resume account switches without blanking the terminal (#25485)
fix(codex): restart account switches without blanking or repinning the pane
Integrate the atomic pane replacement from #24350 and adapt the explicit
conversation resume from #14877. Keep terminal protocol replies flowing
while account notices block user input. Ordinary restores retain provenance.
Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com>
Co-authored-by: itisvincent <vincentcgamer@gmail.com>
Co-authored-by: rayim <rayim@fxy.global>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* Submit initial OpenCode 2.0.12 prompts through the native startup plugin (#25428)
* fix(opencode): submit initial prompts for reviewed 2.0.12
Route the reported release through the existing native startup intent.
Retain conservative behavior for other unreviewed versions.
* Wait for OpenCode startup fixture file reads in the hydration test
---------
Co-authored-by: nwparker <nwparker@users.noreply.github.com>
* feat(jira): pick the assignee when creating an issue — Automatic, me, or anyone (#24376)
* feat(jira): pick the assignee when creating an issue — Automatic, me, or anyone
The new-issue dialog gains an Assignee picker: Automatic (Jira applies the
project's default assignee), one-click "Assign to me" resolved from the
target site's stored identity, or any user via search. The selection resets
with the custom field values on project/type switches, since account ids
are site-scoped.
Creates now also send userFieldKeys, so the host shapes user-typed values
into the {accountId}/{name} refs Jira requires — previously the dialog
never passed them and user create fields were sent as bare strings. Keys
are only named when a value is present, keeping older remote hosts without
the user-fields capability working for assignee-less creates.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(jira): address assignee review — project-scoped assignable search, per-site picker remount, keep assignee across type changes
- Search the create dialog's assignee picker against /user/assignable/search?project=…
(new listAssignableUsersForProject, IPC split into jira-user-search.ts for the
max-lines budget); remote environment targets fall back to the existing
site-wide search since older hosts have no such RPC
- Remount the picker per target project so cached results from the previous
project or site cannot be selected after a switch
- Reset the picked assignee on project selection (account ids are site-scoped)
instead of on every create-fields reload, so issue-type changes keep it
- Hide the assignee field and omit it from the payload when the target create
screen does not accept an assignee — Jira rejects fields absent from the screen
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(jira): scope create assignee to the effective project and provider
* fix(jira): isolate assignee search candidates across provider changes
* fix(jira): search assignable users with Server-compatible project keys
* fix(jira): refresh assignee identity with provider connection status
* fix: distinguish hook confirmation from browser confirm
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Neil <neil@stably.ai>
* Reduce repeated terminal scans and unused Qoder test imports (#25425)
Name the local hook confirmation function to avoid misclassifying internal selectors as dialog text.
* fix: prevent IME confirmation from submitting text fields (#25480)
Keep IME confirmation out of text-field submission and command selection. Reuse shared gesture ownership, preserve marked-release redispatch, guard overlay Escape capture, and retain Markdown save shortcuts.
Verified with independent adversarial reviews, 891 tests across 71 files, six hidden Electron tests, project typecheck, full lint, the changed-code quality gate, and all final-head CI checks.
Fixes#25035.
Co-authored-by: kambarakun <kambarakun@gmail.com>
* Update README downloads badge
* refactor(terminal): one commit module for terminal topology closes, with a boundary check (#25329)
* refactor(terminal): move the topology revision and leaf-lookup helpers into terminal-topology
advanceTerminalTopologyRevision, findTerminalTabIdForLeaf and
hasHostAuthoritativeTerminalMembership move verbatim from the renderer-save
membership rebase into persistence/terminal-topology, the home of the commit
boundary. Importers are repointed; no behavior change.
* test(terminal): test-only guard for topology writes outside the commit boundary
The three session sinks now publish through one commitWorkspaceSessionPartition
helper, which hands the prior and published partition to the topology write
guard. Production never arms the guard, so each sink pays one global lookup.
The unit suite arms it in report mode: a sink write that changes class-(a)
topology (membership, root, bindings, titles, incarnations, sleeping records,
remote session ids, tombstones, default-applied, revision) outside a commit
scope is attributed to its writer's file by stack, and fails the test unless
the writer is on the unrouted-writers allowlist that later routing PRs shrink.
Renderer saves and test seeding are exempt. Deep-freeze is available but stays
off suite-wide until the in-place writers return new sessions.
* refactor(terminal): add the topology commit module with bindLeaf, closeLeaf and closeTab
terminal-topology-commit.ts is the boundary for class-(a) terminal topology.
bindLeaf forwards to persistPtyBinding, whose write now runs in a commit
scope; the spawn commits and the relay reattach bind through it. closeLeaf and
closeTab wrap the existing close mutation in a commit scope and one
persistence.terminal-topology span (kind, outcome, refusal reason; no ids),
and the runtime close goes through them. Session output is unchanged: tests
compare it byte for byte with the old writers for local, ssh: and folder
workspaces.
* chore(terminal): drop an unused lint suppression from the topology write guard
* fix(terminal): attribute topology guard writers relative to the repo root
The guard read a frame's file through its last /src/ segment, so a test under
tests/ (folder-upgrade-identity-persistence.unit.test.ts) had no source frame
and failed as an unknown writer. Frames are now taken relative to the repo
root the setup passes in, tests/ counts as test seeding, Windows backslashes
are normalized before the node_modules skip, and nested src/ paths keep their
full path. The two runtime funnel files skip only their funnel function, so
another writer in them still shows. The R9 allowlist key names the file whose
frame actually writes. The class-(a) diff and the attribution move into their
own files; the stack limit is restored in a finally.
* refactor(terminal): drop bindLeaf until binding reaches a sink; add the boundary ratchet
bindLeaf and the commit scope inside persistPtyBinding changed nothing: the
binding write never reaches a session sink, and a scope inside the Store method
would have admitted every direct caller once it did. Both return in B1-4; the
spawn commits and the relay reattach call persistPtyBinding directly again.
The runtime close now calls one closeLeafOrTab entry, so its callbacks keep
their contextual types. A census test is the primary enforcement: only
persistence/terminal-topology and the callers it lists may call
persistPtyBinding, the session setters or the three sinks, and every
unrouted-writer allowlist entry must name an existing file.
* fix(test): resolve the topology guard's repo root without the global URL
Under happy-dom the global URL is not Node's, so fileURLToPath(new URL(...))
threw in the setup file and failed every happy-dom test file.
* refactor(terminal): drop the runtime topology write guard; the boundary ratchet enforces
The AST boundary ratchet is the enforcement for B1. The stack-attributed
runtime guard, its class-(a) diff, the unrouted-writer allowlist, the vitest
setup and the freeze option are removed; it saw two writers in the whole unit
suite and its real value starts only once binding reaches a sink (B1-4).
Also: one closeLeafOrTab wraps the close in the span (no per-kind copies or
narrowed types), the span has one finish like persistence.pty-binding, the
sink helper is publishWorkspaceSessionPartition (it publishes; the commit
boundary is the module), the ratchet drops the private publishSession row and
checks that every listed caller file exists, and the close comparison keeps
its two meaningful cases with span cases chosen by name.
* refactor(terminal): trim the B1-1 commit module and ratchet to what they enforce
- Point the acknowledged-tab-retirement audit fixture at the moved
advanceTerminalTopologyRevision; its old import no longer resolved.
- Drop publishWorkspaceSessionPartition: it was the removed guard's
interception point, so the three session sinks return to origin/main.
- One traced(kind, mutate) wrapper in the commit file replaces the span
factory; closeLeafOrTab is one call.
- The ratchet walks src/main with the shared scanSourceTree, drops the
loading-store-internal rows and the redundant file-exists test; exact-set
equality already fails on a missing file.
- The commit test is a pure unit test of the span outcomes: no Store
harness, electron mock or self-comparing close.
* refactor(terminal): census the runtime session controller's write and drop stage ids from comments
The controller's setter was named set, which the boundary census could not
list without matching every Map.set, so a new OrcaRuntime mixin could write
sessions through it unseen. Rename it setForWorktree and census it.
Comments now describe state instead of citing plan stage ids.
* refactor(terminal): census writer references and trace refusals by callback
- traced() takes refusalOf instead of assuming an Error refusal, and only
mutate() sits in the try, so a span outcome of threw means the write threw.
- The boundary ratchet counts references, not just direct calls: non-null
calls, bracket keys, aliases, destructures, .call/.bind and parenthesized
callees all count; declared names and type positions do not.
- Census terminalSurfaceCloseMutation (boundary-only) and the partition sinks
setLocalWorkspaceSession / setHostWorkspaceSession.
* test(terminal): count writer uses in extends clauses and instantiations, skip type-only imports and local declarations
The census skipped ExpressionWithTypeArguments as a type, which also holds
`extends f(x)` and `x<T>` value expressions. Type-only import/export
specifiers and declared names (variables, parameters, accessors, enum
members) no longer count as uses. The audit fixture is listed in the table
instead of a separate exemption.
* test(terminal): count quoted and assignment-pattern destructures of layout writers
* test(terminal): count every mention of a layout writer except its definition
Telling definitions from uses per syntax kind kept missing nested and
for-of destructures. Exempt only the writer's own function or class-member
definition; any other mention (including object-literal keys) counts, so the
census errs toward a loud false alarm rather than a silent miss. Quoted names
count only in member-name position.
* test(terminal): count every string literal naming a layout writer
Member-name positions missed wrapped keys like store[('name')] and
store['name' as const]. Counting every string literal outside types is
shorter and errs toward a loud false alarm.
* test(terminal): exempt only class members and functions as writer definitions
Object-literal methods and accessors were exempt while equivalent arrow
properties counted; all object-literal keys now count alike.
* test(terminal): parse files with unicode escapes in the writer census
A name spelled with a \u escape never appears verbatim, so the text
prefilter skipped it.
* test(terminal): parse any file with an escape in the writer census
\x, identity and line-continuation escapes also decode to a writer name
without it appearing verbatim.
* fix(native-chat): a Stop binds only the turn it actually stopped (#24864)
* fix(native-chat): a Stop binds only the turn it actually stopped
A Stop pressed before any turn showed used to claim, at end-write time,
whatever turn the stopped send later opened, even when the Stop stopped
nothing. A turn that then died on its own read as "Interrupted" (your
cancellation) instead of "Failed".
Now a person's Stop that named no turn binds, in memory only, every turn
that ends while the Stop settles, and afterwards only the turn its
interrupt took. The settle ends a still-running stopped turn once. A
relaunch finds nothing in memory, so an unsettled turnless Stop binds no
turn. A Codex Stop whose answered turn does not open within its wait, or
whose send's answer was lost, now answers refused, so the host ends the
child and the turn can never run.
* fix(native-chat): keep the person's queue pause and close binding after a Stop settles
A host stop or eviction with no turn running now defers to a person's
Stop while its queue pause still holds with nothing sent since, read from
rows, so a held card is not handed off on reopen after a Stop that did
nothing or whose kill failed.
A person's close that named no turn opens a settle around its child's
end, so a turn that end cuts reads as theirs.
A press opens its settle only when the latest Stop event is its own or
the one in force it repeats: a late Stop, a card's interrupt or a lost
event row reopens no earlier Stop.
A Codex Stop that cannot reach a turn still able to open says the Stop is
unconfirmed rather than that no turn ran, and a second Stop still reaches
a turn an earlier wait left unopened.
Also drops the unused openedBy plumbing and the unreachable "a written
cancellation stays one" rule, and pins a relaunch after a named Stop.
* fix(native-chat): a Codex Stop agrees on both presses when a turn is still owed, and pin the close's settle
A Codex Stop that waited for a turn Codex answered a send into now answers
"may still open" whenever that turn neither opened nor ended and its send
is still owed, however the wait ended (it ran out, or the thread went
idle). Before, a first press after an idle thread said no turn was
running and kept Codex, while an identical second press ended it.
Adds a test that a person's close the conversation outlives (as /clear
does) closes its settle, so a later turn that ends on its own reads as a
failure.
* test(native-chat): a Stop whose event row failed binds no turn to an earlier Stop
With one ordered journal writer the Stop's event is in the fold when its
write returns, so the press reads whether it owns the latest Stop from the
fold instead of awaiting the write. Pins the case the read must refuse.
* test(native-chat): name the settle, not a stream drain, in the Stop's own-end test
* test(native-chat): a Codex Stop answered before Codex ends the turn reads interrupted throughout
Codex answers an interrupt it took before it sends turn/completed (interrupted):
on TurnAborted the app-server answers pending interrupts, then ends the turn, on
one channel. The test fake did the reverse. It now answers first and ends the
turn on a later read, and the tests that read the turn's end right after a Stop
wait for it.
New end-to-end test through the shipped host, journal and Codex adapter: with
the real order, every end row of the stopped turn reads interrupted by the Stop
(named, unnamed, and a Stop pressed while turn/start was in flight). Breaking the
settle window turns the in-flight case red: the Stop's own end row then has no
verdict, which reads as failed until Codex's end lands.
* fix(native-chat): a Codex Stop interrupts a turn Codex answered but has not opened at once
A Stop that named no turn, made after Codex answered a send but before the turn
opened, used to wait up to 5 s for the turn to open before interrupting, and
ended the Codex process when it didn't. The stated reason, that Codex refuses an
interrupt until it opens the turn, holds only part of the time: with no turn
active, Codex takes an interrupt once its thread runs (turn_interrupt_inner),
and refuses it with -32600 "no active turn to interrupt" before that or once
the turn has ended.
The Stop now sends the interrupt at once. Only on that refusal, while the turn
has neither opened nor ended, does it wait for the turn to open (bounded at
5 s) and send it once more. A turn that ended meanwhile was nothing to stop. One
that never opens, or that an earlier wait already gave up on, fails the Stop,
and the host ends the child as before. Sends still wait for the turn to open
before steering into it.
The test fake models Codex taking an interrupt once the thread runs (run()).
* fix(native-chat): every Codex Stop waits for an answered turn to start, as the first did
A Stop whose interrupt Codex refused as finding no active turn skipped the wait
when an earlier wait, a Stop's or a send's, had already given up on that turn.
Every press now waits its own bound and retries once if the turn starts, so a
turn that opens during a later press is still stopped. Both presses still reach
the same verdict when it never starts.
* fix(native-chat): a Codex Stop that ends the child before any turn opened withdraws its send
A Stop on a Codex turn that was answered but never opened ends the Codex
process. That end settled the send as in doubt (unknown, recovered), and the
client's outbox holds every later send behind a send in doubt until the person
presses Retry, which re-sends the very message they stopped. The chat looked
stuck.
Codex records a prompt only once its turn has started, so a send whose turn
never opened never ran. When the Stop's refusal says so (turnMayOpen), the child
end now settles the unanswered sends as withdrawn, the verdict Codex's own
interrupted-turn end already gives an unechoed send. The flag rides on the owed
wind-down, so a retry after a failed child end withdraws them too. Claude's
child end still leaves its unanswered send in doubt.
* fix(native-chat): derive the withdrawal of a Codex send whose turn never opened
Replaces the flag the Stop carried to the child's end, and its copy on the owed
wind-down, with a reading of the journal at the settlement that lands. A Codex
child's unanswered send is withdrawn when a person's Stop is in force since it
was sent and no turn row ran, or was written, after it; any other end (a turn
that opened, a host's close, a crash, Claude) still leaves it in doubt. A
retried wind-down reads the same rows, so it withdraws the same sends.
* fix(native-chat): keep a send in doubt when a turn was open for it
The derived withdrawal read a turn as open for a send only if it still ran or
was written after the send. A send steered into a running Codex turn whose
interrupt failed met neither once the adapter's end settled that turn ahead of
the host's settle, so it read withdrawn, though Codex drains a steer into the
running turn and may hold it. A turn that ended after the send was handed over
was open for it too: such a send stays in doubt, as before.
Pins that case, and that a send made after the Stop, to a child that then dies
before its turn opens, stays in doubt.
* fix(native-chat): withdraw a Codex send by whether it started its own turn, not by timing
Whether a turn was open for a send was read from end times: a turn that ended
after the send's handover counted. A send made while a Stop ended the turn is
handed over once that turn reads ended, yet Codex's own end for it can arrive
later, so such a send whose own turn never opened read in doubt again, and the
chat's queue held behind it.
The handover already records where the send went: its message joins the turn
running then (a steer) or belongs to no turn (it starts its own). Only a send
that started its own turn, with none opened since, is withdrawn; one that
joined a running turn, or has no recorded place, stays in doubt.
The Codex test fake takes an answered interrupt as Codex does, dropping the
turn before its end arrives.
* fix(native-chat): leave no Stop row when the Stop took back a send that never ran
A Stop on a Codex send whose turn never opened ends the child, and the child's end
takes the send back into the composer. The Stop still wrote "Cancellation
requested." at the conversation level, so with the send gone it sat under the
previous finished turn and read as if that turn had been stopped. A Stop that found
no turn running and whose child end took back every send it found now writes no
row; a Stop of a running turn, or one that leaves a send in doubt, still does.
* fix(native-chat): count a send whose answer was lost when a Stop takes it back
The no-row rule counted only pending sends, but the child's end also takes back a send this process left in doubt when Codex's turn/start answer was lost. That case still wrote "Cancellation requested." under the previous turn. Both now read one predicate, so they cannot drift apart.
* test(native-chat): pin which sends a Stop's child end can take back
A send an earlier process left in doubt is never withdrawn and never holds the row back, and a queued card's send is never counted.
* test(native-chat): read the outbox reconcile from where main moved it
* fix(native-chat): a chat's finished-turn alerts come from the host recorded on its tab, not its workspace id (#25081)
* test(native-chat): a mirrored paired chat carries its host, so a workspace-id collision no longer hides its owner
* fix(native-chat): a chat's finished-turn alerts come from the host stamped on its tab, not its workspace id
* fix(native-chat): the attention bridge reads a chat's owner from the same rule as the chat pane
* fix(native-chat): never delete chat history on read; a chat Orca can't load says why once (#24576)
* fix(native-chat): an older Orca keeps a chat with newer content read-only, and an unreadable annotation costs only itself
A body or lifecycle mutation of a kind this build does not know, inside a row of a known kind,
now latches the chat read-only with every row kept, as a newer row kind or version already does.
It used to read as damage, and the open deleted the journal from that row on.
Each optional field of a body schema now declares what an unparseable value means:
- droppable (an annotation): removed from the row in memory; the chat stays writable;
- must-understand (a prompt's questions, a plan approval's plan, a turn's lifecycle, a goal
change): the row is unreadable and the chat latches read-only.
Only a required field that fails is damage, repaired as before. A test holds every optional
field reachable from a body schema to a policy; the known body kinds are read off the schema.
Prompt options and questions no longer reject a key this build does not know, which deleted the
journal from that row on. The context-usage drop is now one case of the droppable rule.
* feat(native-chat): a chat an older Orca keeps read-only says so before a send is refused
A chat a newer Orca wrote opens read-only, and until now nothing said so: the person found out
when a send failed with "Chats were saved by a newer Orca". The host now names the reason on
every whole history page it serves (`page.readOnly: 'written-by-newer-orca'`: hydration, history
and catch-up resets), the shared client reducer keeps it from the latest whole page, and the
desktop status strip and the phone's composer area say "This chat was saved by a newer Orca, so
it's read-only here. Update Orca to continue this chat."
Every read-only latch the host has is a newer Orca's (its database, a row version, a row kind, a
body kind or must-understand fact), so the reason is derived from the journal's latch, not stored.
The field is optional: older clients ignore it (cross-version test against the newest release's
reducer), and older hosts never send it, so a new client against one says nothing, as today. A
reason this client does not know shows nothing rather than words that may be wrong.
The window-merge helpers move out of the client reducer into their own module.
* fix(native-chat): only a newer build's value goes read-only; damage repairs as before
An older Orca keeps a chat read-only only on evidence a newer build wrote it: a value outside a
closed set this build knows (a body kind, a nested discriminant or literal, a mutation kind), read
off zod's issues. That evidence wins over damage beside it. Anything else that fails (a wrong type,
a missing field, a bad optional value) is damage, repaired as before, so damage no longer freezes
a chat behind an "update Orca" it cannot fix. Drops the per-field droppable/must-understand
policy. A lifecycle mutation's kind is decided before its item id, so a newer kind without one is
kept. The context-usage drop is unchanged from main.
* fix(native-chat): a read-only chat locks its composer and says why there
Removes the separate read-only line (desktop status strip, phone notice component). The host's
`readOnly` on whole pages now feeds the existing composer lock instead: desktop disables the
composer with "Saved by a newer Orca. Update Orca to continue this chat." as its placeholder, and
the phone adds a 'read-only' input-lock reason with the same words. A read-only chat shows no
prompt card it would refuse; the draft stays in the composer. The phone's send-failure line is
back exactly as on main.
* fix(native-chat): a read-only chat pages back through its history
A read-only journal answered every history request with a reset, so a chat this PR now keeps
read-only more often could show only its newest page. Backward reads now serve the snapshot the
open folded, as the first page already does; a forward read of rows still resets.
* test(native-chat): an unknown key in a question entry is read and kept
* test(mobile): the read-only overlay test typechecks
* chore(native-chat): satisfy the changed-code gate in admission and the read-only overlay test
* fix(native-chat): a new value inside a known block or goal arm reads as a newer build's
The open fallback arm of the block and goal unions now aborts, so zod reports the known arm's own
failure instead of only the fallback's; a future closed set there reads unreadable, damage there
still repairs. The schema headers name the context-usage exception and say a new open-string
value must be safe for every older build (rewind keeps only known states; pages carry no row
version).
* fix(native-chat): a read-only chat reconnects for a whole page, so an updated host unlocks it
The client keeps `readOnly` until a whole page replaces it, but reconnected at its cursor and got
only batches, so a desktop attached to a host updated in place stayed locked with "Update Orca".
While it holds the latch it now subscribes without a cursor; the snapshot re-derives the state on
any host version. The phone already subscribes without one.
* fix(native-chat): every desktop write control follows the one read-only fact
The structured session's transport state derives one fact from the page's `readOnly`: the words
for why the host refuses writes. Every write control reads it. While it holds, the chat has no
turn and no work, as the host projects it, so the working row and Stop go away; a pending prompt
stays shown above the composer with its card disabled; queued cards' Send now, Delete and Edit,
the queue's Resume, the goal banner's actions, the model and option pickers, dictation and
background-task Stop are disabled or withheld. The composer placeholder stays the one place the
reason is said. `lockReason` now locks the composer by itself.
* fix(mobile): every phone write control follows the one read-only fact
The phone session derives one fact from the page's `readOnly`, the words for why the host refuses
writes, and every write control reads it: no turn or work while it holds, so no Stop; the pending
prompt stays shown with its card disabled; queued cards and Resume are inert; the model and option
pickers stay shut; the composer field is not editable, so its placeholder keeps saying why, and the
lock applies at once instead of after the transport lock's settle.
* test(native-chat): the newest release hydrates a read-only chat and scrolls back through it
* test(native-chat): a read-only chat reads idle even with a send its provider never answered
* test(native-chat): composer lock test without type assertions
* fix(native-chat): round-3 read-only fixes
- An old /model or option request no longer reopens its picker when a read-only chat unlocks: the
menu is keyed by the request alone, and a lock only keeps it from mounting open.
- A disabled question card still steps through its questions; only answering is locked.
- A failed send's notice in a read-only chat says only that the message was not sent, with no
Retry: the composer already says why, and no retry can land. Its wiring moves into
useStructuredAgentSessionDeliveryNotices.
- The host's background-task stop flags stand; the client no longer overrides them.
- The view reads the read-only fact through one local flag.
- The schema header says what an older build's rewind does with each open string.
* test(native-chat): count picker mounts without an effect
* fix(native-chat): read-only keeps other failures' words and shuts open option menus
- In a read-only chat, only a send the newer-Orca refusal stopped is shortened to "Your message
was not sent."; any other saved failure keeps its own words, and none offers a Retry.
- A model or option menu open when the lock lands has its items disabled, so it cannot send a
change the host would refuse.
* test(native-chat): lock-lands picker test uses a typed surface
* fix(native-chat): a locked composer draws its reason
The editor's placeholder used the default that draws nothing while the editor is not editable, so
a read-only chat's composer was empty and its reason lived only in the aria-label. The placeholder
now draws while disabled, and its words are empty unless the composer is locked with a reason, so
every other disabled composer (a pending prompt, no terminal) looks as before.
* fix(native-chat): sync the locked-placeholder flag in an effect, not during render
* fix(native-chat): a read-only chat is not offered for resume or counted as failed to resume
After a relaunch, a chat whose journal this build keeps read-only (a newer Orca's) was listed in
"Resume interrupted chats?", failed with "Orca couldn't resume this chat. Open it to continue
manually.", and left a status-bar "N chats failed to resume" that never cleared, since nothing can
continue a chat this build cannot write. The resume set now skips such a chat after the checks
that end an offer (fork, moved on), so the offer is kept, not spent, and an updated Orca offers it
again; the failure ledger keeps an already-filed failure for it but does not show it. Both read
the chat's open journal, which listing and acting open first.
* fix(native-chat): a refused resume continuation keeps the refusal's reason
A send refused while continuing a chat after a restart was filed with its code alone, so a newer
Orca's refusal lost its `journalWrittenByNewerOrca` reason. The continuation now carries the whole
refusal, as a refusal from the agent's start already did, so the filed failure keeps it.
* fix(native-chat): a newer Orca's chat keeps its resume offer however it is met
- A newer Orca's whole database counts as read-only for resume, so a chat whose journal cannot
be opened at all (a table the newer schema changed) is not offered, run or counted either.
- The checks made right before sending no longer skip read-only chats: turning one away there was
read as the user having moved on and deleted the offer. Its send is refused instead.
- Settling a resume files nothing for a newer Orca's refusal; the existing rollback reopens the
offer for an updated Orca.
- The continuation records the refusal as a reference, without the wire prose.
* chore(native-chat): one import of the session wire types in the continuation
* docs(native-chat): resume and read-only comments match the current rule
* test(native-chat): a closed set of a journal row cannot change without the row version
The test walks the body schema and the row and mutation kind tables, collects every closed set (a
row kind, a mutation kind, each enum, literal or discriminant), and compares them with a snapshot
recorded beside the row version. A new value makes older builds go read-only, so it fails until
the reader ships first or `v` is bumped and the snapshot updated. Tags a catch-all arm accepts as
any string (block types, goal states) are listed apart: older builds read a new one as-is.
* feat(native-chat): a body or plan subject of a newer kind is kept and the chat stays writable
An item body kind and an approval subject kind this build does not know now read through the
same catch-all blocks and goal states use (`openDiscriminatedUnion`), instead of making the chat
read-only. The item is kept, drawn by nobody, and ignored by everything that reads items (turns,
prompts, status); a rewind carries it as it was, kept by its place like every other row. An
approval whose subject this build cannot draw shows its `detail`, which Orca's writers fill with
the subject's text. A sent message of a kind this build does not know stays a newer build's.
* fix(native-chat): rewind narrows a retained body by its kind before normalizing it
* fix(native-chat): one chat that cannot open no longer stops the startup restore of the rest
* fix(native-chat): a damaged chat fails to load and nothing is deleted
A row this build cannot parse, a gap, or an epoch without its first row used to
be repaired on open: every row from the damage on was deleted and a rebuild from
the provider transcript was attempted. The open now fails through one refusal
(failLoadOnJournalDamage, journalCorrupt), every row stays, and a damaged
per-chat file is kept whole and never copied. A newer build's row still wins
and keeps the chat read-only. The body classifier for closed-set values, the
repair marker and disclosure, and journal recovery from the transcript go.
* chore(native-chat): no reset or adapter left on the open's fixtures
* feat(native-chat): an approval of a newer Orca's subject kind can only be cancelled
Desktop and phone show its detail and the line "This request needs a newer
version of Orca.", disable every answer, and keep the card's cancel, which ends
the turn. The chat stays writable.
* test(native-chat): the closed-set guard says a missed version bump fails older builds' load
* test(native-chat): the closed-set guard names what a missed version bump does
* fix(native-chat): a row the reader rejects is never written, and a chat that cannot load keeps its tab
Every journal insert reads its serialized row back with the reader inside the
write's transaction and refuses one it rejects, so Orca's own writer can no
longer leave a chat that fails to load. A restored chat whose open fails keeps
its tab and says why when opened. An epoch named with no rows is founded afresh
again, deleting nothing. The at-rest test now expects a damaged chat to be
refused; the replay gate keeps its 2026-09-11 evidence as it was run.
* fix(native-chat): a newer Orca's approval subject is carried as it was, and its card cancels with or without a turn
The approval subject's type is open, so code that reads a plan narrows first; the
host's prompt bounding carries an unknown subject instead of rebuilding it as a
plan, which threw in every start's stale settlement. A Codex rewind keeps a
newer Orca's item in its place. A whitespace-only tag is damage. The card's
cancel reaches the host without a running turn on hosts that take that. A
provider row the reader rejects ends its turn as interrupted and the next send
works. A guard fails if an approval subject kind is added before clients can be
gated (STA-9262).
* fix(native-chat): a card's cancel sends nothing without a turn again, and rewind keeps a newer row after any held row
The host's cancel request has to name a turn on every version, so the turnless
prompt cancel is reverted on desktop and phone. The rewind merge moves its anchor
for every row the provider still holds, so a newer Orca's row stays after one
whose kind this build does not know.
* fix(native-chat): a card this build cannot answer leaves the composer open, and a send starts a turn
An approval of a subject kind this build cannot draw, raised by a newer host's background agent
while no turn runs, left nothing to press: its answers are disabled, its cancel needs a turn, and
the desktop composer gave the card its slot. A send the host queues waits behind any pending
prompt, so even the phone's open composer only queued.
While every pending prompt is one this build cannot answer, the desktop composer stays open, and
desktop and phone send without asking to queue: the send starts a turn, and the card's cancel then
settles it. A chat a dead run left is unaffected: opening it already cancels those prompts.
* feat(native-chat): a chat a newer Orca saved fails to load and says to update, with nothing to send into
Opening a chat a newer Orca saved (a row kind, batch-change kind or sent-message kind this build does
not know, a newer row version, or a newer history store) used to open it read-only: the history
shown, the composer locked with a reason, every control greyed. Now it fails the load, like a
damaged chat, with its own words: "This chat was saved by a newer Orca. Update Orca to open it."
Every row is kept. The read is final, so the pane stops retrying; reopening the tab reads again.
A chat whose load failed for good (damaged, or a newer Orca's) offers no composer under the error,
on desktop and on the phone, so the failure is said once and no send can be refused a second time.
A restart offer for a newer Orca's chat is spent without a word: no failure filed, nothing counted.
Removed with the read-only mode: the journal's read-only latch and every check of it, the history
page's readOnly field (never released), the cursorless reconnect, read-only paging, the composer
lock and placeholder, the greyed controls, and the resume exclusion.
* test(native-chat): a newer Orca's records open no chat, and a status fake carries its submissions
* test(native-chat): the status fake's cast says what the feed reads
* fix(native-chat): a chat's read that failed for good takes the whole pane, over a loaded transcript too
* test(native-chat): the final-read-failure test names its reasons
* fix(native-chat): a newer Orca's chat keeps its restart offer, and its refused load is logged once
* refactor(native-chat): the newer-Orca chats a restart listing skips live beside the candidate reader
* fix(native-chat): a refused chat load keeps where it failed, so the log names it and a new reason logs again
* chore(native-chat): the reducer's window merge back where main has it, and comments say a newer Orca's chat fails to load
* test(agent-hooks): the rename test's journal fakes carry submissions, as a journal snapshot does
* test(agent-hooks): the rename test's journal fakes say what the status feed reads
* fix(native-chat): Dismiss all ends the restart offers this Orca lists, and keeps a newer Orca's hidden ones
* test(native-chat): a resend against a newer Orca's store answers unknown, and the phone says nothing beside the read's words
This build never opens a chat a newer Orca's database holds, so a resent send id there cannot be
answered from its journal: it answers "outcome unknown", never a refusal and never a made-up
record, with no second delivery and no write. On the phone, a send error left beside a read that
failed for good is not shown; the pane keeps only the read's words.
* fix(codex): stop prompting a Codex restart when only its home changed (#25421)
* fix(codex): stop prompting a Codex restart when only its home changed
A Codex terminal that outlived the move to ~/.codex was blocked behind "This
Codex session is using an outdated configuration" until the user restarted it,
which starts a fresh codex and ends the conversation. That terminal keeps
working on Orca's old Codex home, which is refreshed from ~/.codex, so the
change did not need the user's answer; the prompt also never appeared for
Codex in Git Bash terminals, whose process tree Orca cannot see through.
The restart prompt now covers only an account switch, which the user caused.
The pane record keeps its home route, which the retained-home refresh and the
shared-server check still read; only the prompt and the custom-home downgrade
that existed to keep it from guessing are gone.
* refactor(codex): drop what the home-route prompt left behind
- Record a pane's own CODEX_HOME override as-is. The filter that kept only
overrides Orca could re-derive existed for the removed route comparison;
its one remaining reader, the shared-server check, returns null for the
shared-home route those panes record either way.
- Treat custom-home like shared-home in resolveCodexPaneHome: the removed
downgrade only wrote it without an override, so it never named a home.
- Inline the restart notice key; its route/account prefix was its only job.
- Delete the two pane-local override tests, including a POSIX-only one that
still expected custom-home and would have failed on Linux and macOS CI.
- Cover the account recheck branches the deleted route-recheck file was the
last to exercise: main reporting a pane current, and main not answering.
* refactor(codex): retire custom-home and the last re-check helpers
- Read an older build's custom-home record as shared-home, which it always
was, and drop custom-home from the route type and every check.
- Delete shellStartupCodexHomeOverrideMatches and its comparison helper;
nothing re-checks a recorded override any more.
- Build the pane launch record in one return: the route is always set, and
only a resumed launch differs, in how it picks the account.
- Drop the restart dialog's notice key; its focus effect now depends on the
pane and both account labels directly.
- Give the account recheck test a typed window stub, so CI's type-assertion
gate passes, and remove timing entries for deleted test files.
* fix(codex): drop the removed dialog strings main added to es.json
* refactor(codex): stop recording a pane's custom CODEX_HOME
The pane record kept a pane's CODEX_HOME override so the removed route
prompt could re-check it later. Its one other reader, the shared-server
check, only looked at it for a real-home pane, and a custom CODEX_HOME
always routes a pane to Orca's mirror, so it was never used.
Drop both record fields, their validators, equality checks and spawn
plumbing. getCustomCodexHomeOverrideForLaunch folds into the existing
hasCustomCodexHomeOverrideForLaunch, and real-home resolves to ~/.codex.
Records from older builds still parse; the parser keeps only known keys.
The fish test's decoy no longer sets CODEX_HOME, so the boolean check
still fails if the launch env XDG_CONFIG_HOME is ignored.
* test(codex): drop setup the yes/no CODEX_HOME checks no longer need
---------
Co-authored-by: Orca Worker <orca-worker@localhost>
* Fix legacy worker recovery snapshot timing for rollback safety (#25413)
* test: add legacy worker recovery persistence snapshot budget tests
Rollback requires the final stored state after processing all workers,
not intermediate snapshots after each candidate. This bounds the snapshot
count and preserves concurrent edits during the durable write.
* fix test
* clean up code
* fix(jira): read a missing or malformed Jira status as disconnected (#25579)
The paired web client has no Jira preload, so window.api.jira.status()
resolves undefined from the fallback proxy. Since #24376 the first status
check stored that undefined and the sidebar crashed reading `.connected`.
Parse the status reply at the runtime-client boundary so every reader
gets a well-formed JiraConnectionStatus.
* feat(agent-launch): keep long prompts off the launch line and paste them after readiness (step 1 of 7) (#24257)
* feat(agent-launch): host-side prompt delivery for agent.launch
The host's agent.launch typed any launch prompt into the shell as part of the
launch command. A long or multi-line prompt then ran line by line in the
shell, and an agent that never showed readiness or crashed at startup had
nothing guarding where its text went.
agent.launch now carries a prompt on the typed line only when the line stays
one line, control-free and at most 512 bytes; otherwise the agent starts
clean and the host pastes the prompt once the agent's own ready signal fires
(bracketed paste plus its composer marker or a quiet render, read only after
the shell's last hand-off, never while the pane's own shell is proven in
front), with main's draft-paste bytes and an Enter 50 ms later. Orchestration
worker starts wait on tui-idle as before. A replay-safe launch admits and
claims its ledger row in one write, Qwen Code gets a second Enter, the
desktop and phone share one launch-refusal classifier, and hosts advertise
agent.launch.prompt-carry.v1.
Split out of #23748, which moves the desktop source-control buttons onto
this path.
* fix(agent-launch): keep a short-lined multi-line prompt on a local zsh launch line, as main did
#24257 moved every multi-line or over-512-byte prompt off the typed launch line and pasted it after readiness. The phone's AI buttons and review notes, whose multi-line prompts main typed whole into zsh, then reached Claude 0.5-3 s later and their RPC reply waited for the paste.
The host now names the shell a local macOS or Linux line is typed into, the way the spawn picks it, and a multi-line prompt rides a zsh line when every line is at most 512 bytes and the whole line at most 8 KB. A real-zsh test types such a line through Orca's own ready barrier and startup write, including when a slow user config makes the write land early. Elsewhere the measured unsafe cases keep the paste: bash 3.2 runs multi-line lines piecemeal, fish drops an early multi-line write, and any shell loses a line over 1 KB written early.
* test(agent-launch): keep the real-zsh launch-line test out of the Windows lane's gate scan
The Windows lane registration check read `const ZSH_PATH = process.platform === 'win32'` (the head of a multi-line ternary) as a Windows-true flag, so `describe.skipIf(!ZSH_PATH)` looked like a Windows-only suite. The file is POSIX-only; the zsh lookup is now a function.
* refactor(protocol): move the agent.launch capabilities into their own module
Main's protocol-version.ts sits at the 300-line cap, so the prompt-carry capability pushed it over. The four agent.launch capabilities and their doc move to agent-launch-runtime-capability.ts, re-exported by name and spread into RUNTIME_CAPABILITIES at the same position; the advertised lists and every export are unchanged.
* refactor(protocol): import the agent.launch capabilities from their own module
`export *` from protocol-version left the four names undefined under the mobile recording loader, which resolves a relative import through a Proxy with no own keys, so 37 phone recordings lost agent.launchReplay. Importers now name agent-launch-runtime-capability directly; protocol-version only spreads its list.
* refactor(agent-launch): drop the unshipped viewMode field and trusted local caller id
Both were inert in step 1 and existed only for step 2. agent.launch will become a
public plugin API, so every wire field is permanent once shipped; a top-level
viewMode reads as "choose terminal vs chat", which the host decides. Step 2
introduces placement and view intent under a placement object instead.
* fix(agent-launch): read Codex's provisional startup from the rule files' hold anchor
Main (#24375) moved Codex's provisional-header check into codex.json's
provisional_startup hold anchor and deleted codex-terminal-readiness.ts, so the
launch readiness hold now asks showsHoldAnchor, as main's own settled check does.
* fix(agent-launch): hold rule-file name titles to quiet for a launch, and census the zsh fixture
Main (#24375) answers a name-only title from each agent's rule file ahead of the
sustained-title lane, so gemini.json's name_title settled a launch readiness wait
on the shell's auto-title while Gemini was still booting. A launch now asks quiet
of every weak idle verdict, as that lane did.
Main's readiness census requires a recorder for every runtime fixture; the zsh
prompt recording is a non-agent control. Gemini's synthetic baseline is
regenerated for this PR's stated change: a bare gemini title is no longer its
rest mark, so name-only rows settle weak, and a fresh working or blocked status
is no longer overridden.
* fix(agent-launch): paste a launch prompt only when the launched agent is proven in front
A launch pasted its prompt unless a shell was proven in the terminal's
foreground, so any read that could not prove one let the prompt through. After
an agent exited at startup, its shell turned bracketed paste on at the next
prompt, readiness fired on it, and the prompt was typed into the shell:
- macOS: a pane runs its shell under login, so the process-group fence's root
was never the shell's group and never proved it; the cached foreground name
could also still name the exited process.
- Windows Git Bash and WSL: the shell-alone-in-its-job check never answers.
Now one fresh read of the terminal's foreground decides: agent, shell or
unknown. Only 'agent' lets a write through (paste, Enter, second Enter, reused
panes too); 'shell' still drops a ready signal. A Windows host never proves the
agent, so there the launch line carries the prompt at any size, as on main.
* test(agent-launch): cover the Windows QA stub, a grok override that exits at once
* fix(agent-launch): keep the local socket alive while a prompted launch waits for its agent
A launch with a prompt now waits up to 60 s for the terminal agent to be
ready before it writes the prompt, and reports not-delivered when the agent
never is. The local runtime socket closes a connection idle for 30 s unless
the request is a long poll, so a launch whose agent exited at startup lost its
reply and the caller saw 'runtime closed the connection' instead of
not-delivered. Classify a prompted agent.launch and agent.launchReplay as a
long poll, as orchestration.workerStart already is for the same wait.
* refactor(agent-launch): narrow the launch params by 'in' instead of a cast
* fix(agent-launch): find a launched agent behind a wrapper that leads its process group
A tcsh or nu launch line runs the agent from /bin/sh '<script>', and a
wrapper script that does not exec its agent does the same: the wrapper leads
the terminal's foreground process group and the agent is a member of it. The
fresh foreground read names the group's leader, sh, so a prompted launch was
refused or pasted late (M4Air tcsh: 2 of 4 not delivered, 2 pasted ~9 s late).
Before that read, take the host's process-group observation as positive proof
when it names the launched agent among the foreground group's members and is
younger than a ready signal's quiet window. It never proves a shell.
* fix(agent-launch): judge the foreground-group proof by when its capture began, not how long ps took
The age the host stamps on a process-group observation runs from the start
of its whole-machine ps, so on a loaded Mac a capture begun after the read
was asked for still read as older than 1 s and the proof was dropped. Count
an observation whose capture began after the read was asked for, less the
window a shared capture is reused across.
* test(agent-launch): keep the crash-guard live test out of the Windows lane's gate scan
The Windows-lane registration scan read the const assigned from a platform
check as a Windows-only gate, though the suite runs everywhere but Windows;
find zsh in a function instead, as the real-zsh typed-line test does.
Under load the fresh foreground scan can fail to answer, which lets the
shell's prompt settle readiness (2 of 4 paired runs). The guard still refuses
that write, so assert the refused write, the property that must always hold.
* perf(agent-launch): read a local pane's foreground from its own terminal, not the whole process table
The foreground read that gates every launch paste ran the daemon's
inspectProcess capture and then a fresh scan, each a whole-machine ps; the
fresh one also waits for any capture already running before it starts its own.
Measured here at load 5: 1.2 s a read (M4Air QA: 3.4-5.0 s, and worker starts
17.6-32 s against main's 9-12 s at load 25-84).
On a local macOS or Linux host, take the pane's root pid from the provider's
session inventory and run one ps limited to that pane's terminal. Its
foreground process group decides: the launched agent or any non-shell member
is the agent (a wrapper that did not exec its agent leads the group), a group
of shells alone is the shell. Same pane, same verdict: 2.7 ms a read. SSH hosts
keep the relay's observation and name.
* test(mobile): re-measure the web app's script sweep after agent.launch's capabilities moved out of protocol-version
The mobile web bundle check failed at 124 assets against a ceiling of 123. Main already sat
exactly on that ceiling: its sweep table read 69 scripts at 16 routes while the tree builds 73,
the whole margin of 4. This branch imports the agent.launch capabilities from their own module,
so protocol-version is no longer pulled into the root layout and four other routes. That moves
which routes share which modules, and the Qoder capability module, imported by protocol-version
and the AI-vault resume path, no longer shares an importer set with anything, so it gets a chunk
of its own: 74 scripts.
The fence says to re-derive the bound rather than raise it, so the sweep is re-measured on this
head (every prefix of the sorted route list). The worst route now adds 10 scripts (session), not
9, which moves the pinned shell crossing from 32 to 30 routes; main re-measured on its own lands
on the same crossing.
* fix(agent-launch): a worker's brief needs its agent found in front, and Grok's start answers on its composer
A paired-server worker start whose agent exited at startup typed its brief into the server's
shell, which ran it: the idle wait can settle on a shell back at its prompt, and the brief was
written with no foreground read. Both worker-start paths now check before each brief write, as a
launch prompt is checked: on a host that can find the agent in front it must be there; on one that
cannot (Windows) a shell proven in front still refuses, and anything else writes as before.
A Grok worker start waited ~10 s more than main: its only rest signal is its bare name, which a
launch holds to quiet output, and Grok animates its logo for ten seconds after its composer glyph.
A worker start for an agent whose rest signal is its bare name and whose composer draws a marker
(Grok, DSH, mimo-code) now also answers on that marker, whichever comes first.
* fix(mobile-chat): a failed message's text is added to the draft, and an expired resend no longer blocks that text (#25149)
* fix(mobile-chat): a returned message is appended to the draft, and an expired resend id is spent
* test(mobile-chat): leave the idempotent append to the shared rule's own test
* docs(native-chat): say a whitespace-only draft counts as empty
* docs(mobile-chat): don't overclaim where returned text survives
* feat(native-chat): answer /context in chat view for OpenClaude and OMP (#22294)
* feat(native-chat): answer /context in chat view for Claude
Over a terminal-backed chat, Claude's /context paints a grid only the hidden
terminal sees and records nothing in the transcript, so the chat showed
nothing. The chat host now answers it itself: the transcript decoder keeps
each assistant response's API usage and model, and the composer replies with
the same estimate the CLI statusline shows (input + cache tokens against the
model's window) instead of typing the command into the PTY. The command is
also listed in the curated Claude catalog so the slash menu offers it.
* fix(native-chat): only state a /context window the host can establish
The /context answer sized the window from the transcript's model id, which
never carries the [1m] suffix, so a 1M session read as 200k (e.g. 450k / 200k,
225%). The window now comes from the session's tracked model resolved through
the host CLI's own model listing (the resolvedModel field the list_models probe
already returned), and only a [1m] id that matches the model the transcript
last answered with is sized; anything else reports the used figure alone.
Also:
- Offer /context only in the desktop terminal-backed composer; the shared
catalog is mobile's too, and mobile cannot answer it.
- Answer a typed /context even with images attached, as the picker does, and
keep the attachments armed instead of sending them to the terminal.
- Report nothing until a response lands after a /compact or /clear sent from
the chat, instead of the pre-compaction size.
* test(native-chat): pin resolvedModel through Claude model discovery
* fix(native-chat): say /context is unavailable when the host never reports usage
A host that predates usage decoding, or a scraped view, decodes Claude's
replies without a model or usage, so the chat promised an answer after the
next response that would never come. Once the agent has answered and no
answer names its model, say usage is not available for this session.
* feat(native-chat): answer /context for OMP instead of Claude
Claude and Codex answer /context themselves in the structured chat lane, so
the terminal-backed Claude answer, its [1m] window rule, and the resolved-model
plumbing through the model listing go away.
OMP's /context only paints a panel inside its TUI. The OMP decoder now keeps
the provider, model and usage of each reply (none for aborted or errored
replies, which OMP's own gauge skips) and turns compaction rows into the
existing compaction boundary. The OMP model listing keeps each model's
context window, and the chat answers /context from the last reply's prompt
size against the window of the provider/model that served it.
* feat(native-chat): show OpenClaude's own /context report in chat
OpenClaude writes its /context grid to the transcript as a
<local-command-stdout> row, which the chat hid as harness noise. The live
message preparation now turns the stdout row that answers an opted-in command
(/context for OpenClaude) into command output with the ANSI colors stripped,
and the notice row renders command output in monospace so the grid keeps its
columns. Replies to other local commands stay hidden, and Claude is not opted
in. OpenClaude's slash menu now lists /context; Claude's does not.
* test(native-chat): pin OpenClaude /context output through live message preparation
* test(native-chat): pin the OMP context window through model discovery
* test(native-chat): pin monospace rendering of command output
* test(native-chat): restore the notice row suite the command-output test replaced
The previous commit rewrote NativeChatNoticeRow.test.tsx wholesale, deleting
main's compaction, tone, plan, provider-notice and old-reader coverage. Restore
it and pin monospace command output through the message row instead.
* fix(mobile): keep OpenClaude /context off mobile, which cannot show its report
The shared slash catalog now lists /context for OpenClaude because the desktop
chat surfaces its transcript reply. Mobile's transcript fold does not, so the
command did nothing there. Mobile now offers the catalog minus commands answered
only by a surfaced reply, which returns its OpenClaude menu and send
classification to main's behavior.
* refactor(native-chat): drop the unread estimated flag from context usage
Left from the earlier design; the answer text states the estimate itself.
* fix(native-chat): keep /model replies hidden after an OpenClaude /context
Skill surfacing ran first and rewrote non-catalog envelopes such as /model into
plain user text, so command-output pairing no longer saw them. A later /model
reply then paired with the earlier /context envelope and appeared in the chat.
Pair outputs against the raw envelopes before skill surfacing.
* fix(native-chat): word the OMP /context answer for a compacted session and show it as an aside
After a compaction the agent has already answered, so the unknown-usage
line now promises the next response instead. The host answer is one
sentence, so it draws as the muted line it replaces rather than a
monospace grid. The provider/model selector now comes from one helper
shared by the OMP listing parser and the answer, and new tests drive
real OpenClaude and OMP transcript lines through the decoders.
Co-Authored-By: Claude <noreply@anthropic.com>
* refactor(native-chat): pair command replies by row link and declare /context replies on the catalog
A `<local-command-stdout>` row now answers the command row its `parentUuid`
names, carried on decoded messages as the optional `parentId`, instead of the
newest command row at or before its timestamp. A reply with no loaded linked
row (older host, command outside the tail window) stays hidden.
How each agent's `/context` is answered is declared once, as `reply` on its
catalog row (`composer` for OMP, `transcript` for OpenClaude). The slash menu,
the composer's answer, the transcript surfacing and the mobile catalog all
derive from it, and both send paths share one intercept. Mobile no longer lists
OMP `/context`, which did nothing there.
* perf(native-chat): skip command-output surfacing for agents that declare no transcript reply
Every Claude-format row now carries its parent link, so once a session holds any
local-command reply (/model, /compact) the surfacing pass built a map of every
message on each live update and changed nothing for agents like Claude. Agents
whose catalog declares no transcript reply now return before the scan.
---------
Co-authored-by: Claude <noreply@anthropic.com>
* fix(native-chat): the agent's exit ends its record, and an unconfirmed stop is joined instead of held (#24862)
* fix(native-chat): a child's root exit is reported even during its close, and bookkeeping after it never reads as unproven
- Both connections report the root process's exit once, with `expected` set when a close had
begun. A close that came back unproven and whose root exits later is finished by the adapter,
and its end reaches the host like any other.
- A Claude close whose resume-point write fails after the exit was proven, and a Codex close whose
terminal row is refused, now end the session and report the failure, instead of keeping a dead
child indexed as if its exit were unproven.
- A Codex close whose forced tree kill can't prove the descendants gone but saw the root exit
reports the descendants and counts the root exit.
- Every child exit with an identity, expected or not, is forwarded to the host.
* fix(native-chat): the exit ends the child's record; an unfinished stop is the child's own close, which everyone joins
- The host keeps no stored "stop still owed" record any more. A stop begins the child's close
(`child.close`), which lives on the child and ends with it. A second Stop, the idle reaper,
quit, a send and an option/answer/goal/rewind all join that close instead of retrying a
separate obligation.
- A caller waits on the close only as long as the step deadline; the close itself is never
abandoned. A proof that lands after every caller stopped waiting reaches the host as the
adapter's report of that exit, which ends the record through the same handler.
- Once the exit is proven, draining, settling, the lease release and the adapter's
acknowledgement are each attempted and reported on failure; none keeps the child on record.
A start, and the handle's close, write a release that failed from this host's proof of that
exit, so a failed write never refuses a send.
- A start that meets a close still unverifiable is refused with `previousExitUnverifiable`, so the
queued message is rejected with a send-again reason; nothing is held and nothing starts beside
the old process.
- The idle sweep goes back to idle reaping only.
- Removes #24333's retry entry points, the wait row and its hold rule, the ask/failure cursors on
the stored record, and the stop's own wake.
Tests replace the #24333 unproven-stop test: a send joining an unproven close and an in-flight
one, a late proof past the caller's bound, a root exiting after its close gave up, a proven exit
whose resume-point write and lease release both failed, an unverifiable close rejecting the send
and refusing an option change, a surviving descendant, quit and the idle reaper; and Codex's
unverifiable, late-exit and joined-close cases.
* fix(native-chat): a message refused because the old process's exit is unverifiable says so, and to send again
The start failure for a refusal with reason `previousExitUnverifiable` reads "Orca couldn't
confirm Claude's previous process ended. Send your message to try again." instead of "Claude
couldn't restart." The status-row kind and the refusal reason stay in the shared lists for rows
and hosts that still carry them; the catalogs keep one sentence for both.
* fix(native-chat): a close's verdict is the root's exit alone, and what follows it is logged
- A Claude close resolves as soon as the root's exit is proven: the session ends and its `ended`
report goes out then. Saving the resume point runs afterwards and a failure is logged, so a slow
or hung write never reads as an unproven exit or keeps a dead child on record.
- A root that exits after its close came back unproven finishes that close through the same path
as any close, so the session's child work is published as ended (background tasks and subagents
no longer stay shown running for a dead agent), and a failure there is logged.
- Codex logs a refused final row, and reports a root exit whose forced tree kill could not prove
the rest of the tree gone the way Claude does, so the host logs it and blocks nothing.
- Both adapters take the host's logger for this bookkeeping.
* fix(native-chat): one handler ends every child's exit, and a join waits on the adapter's own close
- One exit handler (`structured-agent-session-child-exit`) ends a child's record for an exit
expected or not. `expected` only changes what the chat is told: the stop's cause, its end at the
stop's ask, the settlement id, and no crash outcome row. The lease release keeps the exit's
evidence; the handoff guard, lifecycle barrier, sink release and adapter acknowledgement apply to
both. A Claude journal-sink failure ends in the same step as its stop, as Orca's own fault.
- Joining a close is asking the adapter, whose close is memoized while it runs and bounded by its
own kill escalation; the host keeps no attempt of its own and no 10 s caller bound. An ask after
a close came back unproven runs the stop again.
- A close's end is stamped where its stop was asked for (a repeated ask moves it), so the closed
chat and failed start checks order a message accepted meanwhile after it.
- A start refused because the old exit is unverifiable rejects what was queued in the same step.
- The end of a close the host asked for no longer waits on the cross-session recovery chain.
- The kill no longer waits for the stop event's write; the journal writes rows in order.
* fix(native-chat): an exit's lease release lands whatever the length of its reason
A crash's reason can carry kilobytes of the provider's stderr, and a lease whose death detail is
over 512 characters fails the store's own check. The exit handler cut it, but the release a start
or the chat handle's close re-derives did not, so after a crash whose own release failed every
message was refused as not resumable until restart. The record's builder now cuts the detail to
the record's bound, so no writer can hand it one too long.
* fix(claude): a proven close waits at most 2 s for the output it already wrote
Once the root's exit is proven, the close still waited for the SDK's output reader to end. Something
outside the process tree that holds the output open would keep that close, and every send, Stop
and quit joining it, waiting with no bound. The wait is now bounded; past it the close resolves as
proven and the open output is logged.
* fix(codex): an exit reported inside Orca's close keeps the reason Orca closed it for
The connection reports the app-server's exit inside the close that ends it, so that report ended
every Codex close and replaced the close's own reason (for example, a provider frame that could not
be recorded) with the connection's stderr text in the ended record and the lease's exit evidence.
The session now records Orca's close with its reason, and the exit it ends keeps that reason. The
test connection reports its exit inside close the way the real one does.
* fix(native-chat): quit stops delivery before it drains exit recovery
Every exit now wakes delivery, and teardown drained exit recovery before it stopped delivery, so an
exit settled in that window could start a fresh agent that teardown then killed. Teardown stops
delivery first; queued messages wait for the next launch.
* docs(native-chat): the unverifiable-exit refusal no longer names a caller's wait
The caller's bounded wait was removed; the comment describes the close as it is now.
* fix(native-chat): a stop whose kill did not take is logged, and the next ask kills again
When a close's kill leaves the agent's root running, the host now logs it. Tests pin what a later
ask does: each connection runs its whole stop again (Codex sends SIGKILL a second time), refuses
input meanwhile, and proves the exit once the kill takes.
* fix(native-chat): a start refused over the old process says Orca couldn't stop it
The host reaches an unverifiable verdict only after its own kill left the agent's root running, on
the machine that runs the agent, so the sentence now says that: "Orca couldn't stop {agent}'s
previous process." The refusal reason, failure kind and wire shapes are unchanged. The host test
also checks the failed kill is logged.
* docs(native-chat): an unverifiable close verdict is a root that survived the kill
The host's close runs where the agent runs, so lost contact never yields this verdict; the comment no longer says it does.
* fix(native-chat): a kill that did not take is reported once, by whoever met it
The log added at the close fired beside a Stop's own failure report for the same event. A stop
still reports it through its failure; a send or option change refused over it now logs it at the
refusal, the only place it is otherwise invisible.
* test(native-chat): a second Stop joins a close the first could not prove and retries its kill
* fix(native-chat): say a start refused beside an unstopped process plainly
The rejection now reads "Couldn't stop {{agent}} from before. Send your message again to try once more."
This kind has its own send-again step; every other failure keeps "Send your message to try again."
* feat(activity): filters, matching header, row right-click menu, and stay in the activity view (#25376)
* fix(sidebar): stop workspace reveals from leaving the activity view
Reveals are skipped while the activity view is showing, and activation no
longer lifts workspace-list filters there. Only explicit requests (Go to
workspace, reveal-current-workspace, Cmd+J project row, agent send target,
Cmd+1-9) switch to the workspace list via showSidebarWorkspaceList.
* test(sidebar): keep test harnesses out of the sidebarBody writer census
* feat(activity): workspace-origin filters and a matching options trigger
Activity view gains its own persisted Hide automation-created, Hide CLI-created
and Hide other-client agents toggles, applied through the existing activity
scope filter so the selected-row exemption and identity-preserving return keep
working. The options trigger now uses the workspace view's SlidersHorizontal
icon and shared numeric badge, counting every active activity filter, and Add
project stays visible so no header button shifts between views.
* feat(activity): right-click menu on activity rows
Open, Mark as read/unread, Go to workspace (real workspaces only), Copy (agent
title, plus branch and path for real workspaces), and Clear from list for
finished threads, reusing the row's existing actions and clearActivityThread.
* fix(sidebar): leave the activity view only once activation commits
Go to workspace and Cmd+1-9 switched the sidebar before activating, so a
blocked folder activation still left the activity view. Activation now owns
the switch via showWorkspaceList, applied after its failure gates. The
separate showSidebarWorkspaceList action and the one-line sidebar-body-reveal
module go away: setSidebarBody is the single writer, and the census pins its
callers.
* fix(activity): close the row preview when its right-click menu opens
* fix(activity): keep the row preview closed while its right-click menu is open
* fix(ui): give right-click menus the same solid surface as dropdowns
* fix(activity): match the agent row menu to the workspace row menu
* fix(activity): offer the same copy items as the workspace row menu
* Add confirmed deletion of nested worktrees (#25668)
* Preserve complete SQL checkpoints before updates and align restart coverage
---------
Signed-off-by: Neil <neil@stably.ai>
Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
Co-authored-by: Orca Integration Recovery <orca-validation@invalid.example>
Co-authored-by: Justas Brazauskas <brazauskasjustas@gmail.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Brynn Bendixen <brynnbendixen@gmail.com>
Co-authored-by: brynnclaw <261708852+brynnclaw@users.noreply.github.com>
Co-authored-by: Neil <neil@stably.ai>
Co-authored-by: 小七 <ggbdpq@gmail.com>
Co-authored-by: lurunzi <lurunzi@gmail.com>
Co-authored-by: Dongho Lee <126236161+LDH1103@users.noreply.github.com>
Co-authored-by: LDH1103 <ldh517525@gmail.com>
Co-authored-by: Wayn_Liu <115852642+Waynting@users.noreply.github.com>
Co-authored-by: Wayn_Liu <wayntingliu@gmail.com>
Co-authored-by: VXNCXNX <VXNCXNX@users.noreply.github.com>
Co-authored-by: Michiel de Gooijer <mdgooijer@gmail.com>
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: Marcus <70784566+marcuslannister@users.noreply.github.com>
Co-authored-by: marcuslannister <marcus@lannister.cc>
Co-authored-by: m4air <m4air@m4airs-Air.localdomain>
Co-authored-by: Florian Schwarzmeier <1506919+fsmeier@users.noreply.github.com>
Co-authored-by: SongMarco <snubflow@gmail.com>
Co-authored-by: marco song <marco.song@mvlchain.io>
Co-authored-by: SongMarco <20613630+SongMarco@users.noreply.github.com>
Co-authored-by: Luca Critelli <lucacri@gmail.com>
Co-authored-by: JeongUk Park <jeongph.dev@gmail.com>
Co-authored-by: B1nh M1nh <43268322+b1nhm1nh@users.noreply.github.com>
Co-authored-by: Zhichang Yu <yuzhichang@gmail.com>
Co-authored-by: Kh05ifr4nD <meandSSH0219@gmail.com>
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Wooseong Kim <2222333+innocarpe@users.noreply.github.com>
Co-authored-by: Wooseong Kim <innocarpe@gmail.com>
Co-authored-by: Nawapat Buakoet <nawapat.b@covest.finance>
Co-authored-by: Frederic Barthelemy <git@fbartho.com>
Co-authored-by: mrcha033 <mrcha033@users.noreply.github.com>
Co-authored-by: xodmd45-ctrl <xodmd45-ctrl@users.noreply.github.com>
Co-authored-by: Luchong <lu740528977@gmail.com>
Co-authored-by: czzczz <chanzrz_zbf@foxmail.com>
Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com>
Co-authored-by: razshlomo <razshlomo@users.noreply.github.com>
Co-authored-by: Raz Shlomo <12373339+razshlomo@users.noreply.github.com>
Co-authored-by: Ahmed Nagy <ahmednagy25t@gmail.com>
Co-authored-by: KAPUIST <thsxornjs12@gmail.com>
Co-authored-by: JianJia2018 <39438074+JianJia2018@users.noreply.github.com>
Co-authored-by: kespineira <kespineira@users.noreply.github.com>
Co-authored-by: kevimux <kevimux@users.noreply.github.com>
Co-authored-by: Jinjing <6427696+AmethystLiang@users.noreply.github.com>
Co-authored-by: Kelvin Amoaba <97001695+AmoabaKelvin@users.noreply.github.com>
Co-authored-by: Pablo Werlang <19828711+werlang@users.noreply.github.com>
Co-authored-by: innocarpe <innocarpe@users.noreply.github.com>
Co-authored-by: Jinwoo Hong <73622457+Jinwoo-H@users.noreply.github.com>
Co-authored-by: drakeo338 <paranoyouz@gmail.com>
Co-authored-by: Saurav M Hiremath <sauravhiremath@gmail.com>
Co-authored-by: DakaAlvarez <149860458+Dacadev97@users.noreply.github.com>
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
Co-authored-by: Orca Worker <orca-worker@localhost>
Co-authored-by: m4air <m4air@Mac.localdomain>
Co-authored-by: griffinmartin <griffinmartin@users.noreply.github.com>
Co-authored-by: Orca QA <orca-qa@users.noreply.github.com>
Co-authored-by: Aashish Mahato <145881415+aashish254@users.noreply.github.com>
Co-authored-by: Emirhan Celik Sahin <152696449+E-BlackTV@users.noreply.github.com>
Co-authored-by: E-BlackTV <emirhancelik1133@icloud.com>
Co-authored-by: nwparker <nwparker@users.noreply.github.com>
Co-authored-by: mmarabel <166927047+mmarabel@users.noreply.github.com>
Co-authored-by: Katsuma Takehisa <k.takehisa@nissogr.com>
Co-authored-by: Orca startup hydration review <agents@stably.ai>
Co-authored-by: Orca <dev@stably.ai>
Co-authored-by: Kevin Saliou <kevin@saliou.name>
Co-authored-by: Codex <codex@openai.com>
Co-authored-by: Orca maintenance <orca-maintenance@users.noreply.github.com>
Co-authored-by: Orca campaign <orca-campaign@local.invalid>
Co-authored-by: Adnan Khan <adnank11427@gmail.com>
Co-authored-by: OpenCode Campaign <opencode-campaign@users.noreply.github.com>
Co-authored-by: Orca Campaign <campaign@localhost>
Co-authored-by: Nicholas Ting <nicholas.ting@gridsight.ai>
Co-authored-by: Neil Parker <nwparker@MacBook-Pro-3.localdomain>
Co-authored-by: OpenCode issue campaign <codex@localhost>
Co-authored-by: Borys Papevis <36397618+boris-papevis@users.noreply.github.com>
Co-authored-by: Orca <orca@stably.ai>
Co-authored-by: PM <76590223+pqminh27@users.noreply.github.com>
Co-authored-by: kei <83833293+setodeve@users.noreply.github.com>
Co-authored-by: setodeve <keinick11@outlook.com>
Co-authored-by: user141514 <user141514@users.noreply.github.com>
Co-authored-by: Orca OpenCode issue campaign <opencode-campaign@stably.ai>
Co-authored-by: Orca campaign recovery <campaign-recovery@example.invalid>
Co-authored-by: Orca OpenCode Campaign <opencode-campaign@local.invalid>
Co-authored-by: Ken Fukuyama <kenfdev@gmail.com>
Co-authored-by: Daiki Hirayama <33761105+chokolademilch7@users.noreply.github.com>
Co-authored-by: kazuma.miki <77340746+kazooooo-ma@users.noreply.github.com>
Co-authored-by: kazooooo-ma <zooooomer-com@gmail.com>
Co-authored-by: itisvincent <vincentcgamer@gmail.com>
Co-authored-by: rayim <rayim@fxy.global>
Co-authored-by: hnkƶ <hnkz.64@gmail.com>
Co-authored-by: kambarakun <kambarakun@gmail.com>
* fix(native-chat): an older Orca keeps a chat with newer content read-only, and an unreadable annotation costs only itself
A body or lifecycle mutation of a kind this build does not know, inside a row of a known kind,
now latches the chat read-only with every row kept, as a newer row kind or version already does.
It used to read as damage, and the open deleted the journal from that row on.
Each optional field of a body schema now declares what an unparseable value means:
- droppable (an annotation): removed from the row in memory; the chat stays writable;
- must-understand (a prompt's questions, a plan approval's plan, a turn's lifecycle, a goal
change): the row is unreadable and the chat latches read-only.
Only a required field that fails is damage, repaired as before. A test holds every optional
field reachable from a body schema to a policy; the known body kinds are read off the schema.
Prompt options and questions no longer reject a key this build does not know, which deleted the
journal from that row on. The context-usage drop is now one case of the droppable rule.
* feat(native-chat): a chat an older Orca keeps read-only says so before a send is refused
A chat a newer Orca wrote opens read-only, and until now nothing said so: the person found out
when a send failed with "Chats were saved by a newer Orca". The host now names the reason on
every whole history page it serves (`page.readOnly: 'written-by-newer-orca'`: hydration, history
and catch-up resets), the shared client reducer keeps it from the latest whole page, and the
desktop status strip and the phone's composer area say "This chat was saved by a newer Orca, so
it's read-only here. Update Orca to continue this chat."
Every read-only latch the host has is a newer Orca's (its database, a row version, a row kind, a
body kind or must-understand fact), so the reason is derived from the journal's latch, not stored.
The field is optional: older clients ignore it (cross-version test against the newest release's
reducer), and older hosts never send it, so a new client against one says nothing, as today. A
reason this client does not know shows nothing rather than words that may be wrong.
The window-merge helpers move out of the client reducer into their own module.
* fix(native-chat): only a newer build's value goes read-only; damage repairs as before
An older Orca keeps a chat read-only only on evidence a newer build wrote it: a value outside a
closed set this build knows (a body kind, a nested discriminant or literal, a mutation kind), read
off zod's issues. That evidence wins over damage beside it. Anything else that fails (a wrong type,
a missing field, a bad optional value) is damage, repaired as before, so damage no longer freezes
a chat behind an "update Orca" it cannot fix. Drops the per-field droppable/must-understand
policy. A lifecycle mutation's kind is decided before its item id, so a newer kind without one is
kept. The context-usage drop is unchanged from main.
* fix(native-chat): a read-only chat locks its composer and says why there
Removes the separate read-only line (desktop status strip, phone notice component). The host's
`readOnly` on whole pages now feeds the existing composer lock instead: desktop disables the
composer with "Saved by a newer Orca. Update Orca to continue this chat." as its placeholder, and
the phone adds a 'read-only' input-lock reason with the same words. A read-only chat shows no
prompt card it would refuse; the draft stays in the composer. The phone's send-failure line is
back exactly as on main.
* fix(native-chat): a read-only chat pages back through its history
A read-only journal answered every history request with a reset, so a chat this PR now keeps
read-only more often could show only its newest page. Backward reads now serve the snapshot the
open folded, as the first page already does; a forward read of rows still resets.
* test(native-chat): an unknown key in a question entry is read and kept
* test(mobile): the read-only overlay test typechecks
* chore(native-chat): satisfy the changed-code gate in admission and the read-only overlay test
* fix(native-chat): a new value inside a known block or goal arm reads as a newer build's
The open fallback arm of the block and goal unions now aborts, so zod reports the known arm's own
failure instead of only the fallback's; a future closed set there reads unreadable, damage there
still repairs. The schema headers name the context-usage exception and say a new open-string
value must be safe for every older build (rewind keeps only known states; pages carry no row
version).
* fix(native-chat): a read-only chat reconnects for a whole page, so an updated host unlocks it
The client keeps `readOnly` until a whole page replaces it, but reconnected at its cursor and got
only batches, so a desktop attached to a host updated in place stayed locked with "Update Orca".
While it holds the latch it now subscribes without a cursor; the snapshot re-derives the state on
any host version. The phone already subscribes without one.
* fix(native-chat): every desktop write control follows the one read-only fact
The structured session's transport state derives one fact from the page's `readOnly`: the words
for why the host refuses writes. Every write control reads it. While it holds, the chat has no
turn and no work, as the host projects it, so the working row and Stop go away; a pending prompt
stays shown above the composer with its card disabled; queued cards' Send now, Delete and Edit,
the queue's Resume, the goal banner's actions, the model and option pickers, dictation and
background-task Stop are disabled or withheld. The composer placeholder stays the one place the
reason is said. `lockReason` now locks the composer by itself.
* fix(mobile): every phone write control follows the one read-only fact
The phone session derives one fact from the page's `readOnly`, the words for why the host refuses
writes, and every write control reads it: no turn or work while it holds, so no Stop; the pending
prompt stays shown with its card disabled; queued cards and Resume are inert; the model and option
pickers stay shut; the composer field is not editable, so its placeholder keeps saying why, and the
lock applies at once instead of after the transport lock's settle.
* test(native-chat): the newest release hydrates a read-only chat and scrolls back through it
* test(native-chat): a read-only chat reads idle even with a send its provider never answered
* test(native-chat): composer lock test without type assertions
* fix(native-chat): round-3 read-only fixes
- An old /model or option request no longer reopens its picker when a read-only chat unlocks: the
menu is keyed by the request alone, and a lock only keeps it from mounting open.
- A disabled question card still steps through its questions; only answering is locked.
- A failed send's notice in a read-only chat says only that the message was not sent, with no
Retry: the composer already says why, and no retry can land. Its wiring moves into
useStructuredAgentSessionDeliveryNotices.
- The host's background-task stop flags stand; the client no longer overrides them.
- The view reads the read-only fact through one local flag.
- The schema header says what an older build's rewind does with each open string.
* test(native-chat): count picker mounts without an effect
* fix(native-chat): read-only keeps other failures' words and shuts open option menus
- In a read-only chat, only a send the newer-Orca refusal stopped is shortened to "Your message
was not sent."; any other saved failure keeps its own words, and none offers a Retry.
- A model or option menu open when the lock lands has its items disabled, so it cannot send a
change the host would refuse.
* test(native-chat): lock-lands picker test uses a typed surface
* fix(native-chat): a locked composer draws its reason
The editor's placeholder used the default that draws nothing while the editor is not editable, so
a read-only chat's composer was empty and its reason lived only in the aria-label. The placeholder
now draws while disabled, and its words are empty unless the composer is locked with a reason, so
every other disabled composer (a pending prompt, no terminal) looks as before.
* fix(native-chat): sync the locked-placeholder flag in an effect, not during render
* fix(native-chat): a read-only chat is not offered for resume or counted as failed to resume
After a relaunch, a chat whose journal this build keeps read-only (a newer Orca's) was listed in
"Resume interrupted chats?", failed with "Orca couldn't resume this chat. Open it to continue
manually.", and left a status-bar "N chats failed to resume" that never cleared, since nothing can
continue a chat this build cannot write. The resume set now skips such a chat after the checks
that end an offer (fork, moved on), so the offer is kept, not spent, and an updated Orca offers it
again; the failure ledger keeps an already-filed failure for it but does not show it. Both read
the chat's open journal, which listing and acting open first.
* fix(native-chat): a refused resume continuation keeps the refusal's reason
A send refused while continuing a chat after a restart was filed with its code alone, so a newer
Orca's refusal lost its `journalWrittenByNewerOrca` reason. The continuation now carries the whole
refusal, as a refusal from the agent's start already did, so the filed failure keeps it.
* fix(native-chat): a newer Orca's chat keeps its resume offer however it is met
- A newer Orca's whole database counts as read-only for resume, so a chat whose journal cannot
be opened at all (a table the newer schema changed) is not offered, run or counted either.
- The checks made right before sending no longer skip read-only chats: turning one away there was
read as the user having moved on and deleted the offer. Its send is refused instead.
- Settling a resume files nothing for a newer Orca's refusal; the existing rollback reopens the
offer for an updated Orca.
- The continuation records the refusal as a reference, without the wire prose.
* chore(native-chat): one import of the session wire types in the continuation
* docs(native-chat): resume and read-only comments match the current rule
* test(native-chat): a closed set of a journal row cannot change without the row version
The test walks the body schema and the row and mutation kind tables, collects every closed set (a
row kind, a mutation kind, each enum, literal or discriminant), and compares them with a snapshot
recorded beside the row version. A new value makes older builds go read-only, so it fails until
the reader ships first or `v` is bumped and the snapshot updated. Tags a catch-all arm accepts as
any string (block types, goal states) are listed apart: older builds read a new one as-is.
* feat(native-chat): a body or plan subject of a newer kind is kept and the chat stays writable
An item body kind and an approval subject kind this build does not know now read through the
same catch-all blocks and goal states use (`openDiscriminatedUnion`), instead of making the chat
read-only. The item is kept, drawn by nobody, and ignored by everything that reads items (turns,
prompts, status); a rewind carries it as it was, kept by its place like every other row. An
approval whose subject this build cannot draw shows its `detail`, which Orca's writers fill with
the subject's text. A sent message of a kind this build does not know stays a newer build's.
* fix(native-chat): rewind narrows a retained body by its kind before normalizing it
* fix(native-chat): one chat that cannot open no longer stops the startup restore of the rest
* fix(native-chat): a damaged chat fails to load and nothing is deleted
A row this build cannot parse, a gap, or an epoch without its first row used to
be repaired on open: every row from the damage on was deleted and a rebuild from
the provider transcript was attempted. The open now fails through one refusal
(failLoadOnJournalDamage, journalCorrupt), every row stays, and a damaged
per-chat file is kept whole and never copied. A newer build's row still wins
and keeps the chat read-only. The body classifier for closed-set values, the
repair marker and disclosure, and journal recovery from the transcript go.
* chore(native-chat): no reset or adapter left on the open's fixtures
* feat(native-chat): an approval of a newer Orca's subject kind can only be cancelled
Desktop and phone show its detail and the line "This request needs a newer
version of Orca.", disable every answer, and keep the card's cancel, which ends
the turn. The chat stays writable.
* test(native-chat): the closed-set guard says a missed version bump fails older builds' load
* test(native-chat): the closed-set guard names what a missed version bump does
* fix(native-chat): a row the reader rejects is never written, and a chat that cannot load keeps its tab
Every journal insert reads its serialized row back with the reader inside the
write's transaction and refuses one it rejects, so Orca's own writer can no
longer leave a chat that fails to load. A restored chat whose open fails keeps
its tab and says why when opened. An epoch named with no rows is founded afresh
again, deleting nothing. The at-rest test now expects a damaged chat to be
refused; the replay gate keeps its 2026-09-11 evidence as it was run.
* fix(native-chat): a newer Orca's approval subject is carried as it was, and its card cancels with or without a turn
The approval subject's type is open, so code that reads a plan narrows first; the
host's prompt bounding carries an unknown subject instead of rebuilding it as a
plan, which threw in every start's stale settlement. A Codex rewind keeps a
newer Orca's item in its place. A whitespace-only tag is damage. The card's
cancel reaches the host without a running turn on hosts that take that. A
provider row the reader rejects ends its turn as interrupted and the next send
works. A guard fails if an approval subject kind is added before clients can be
gated (STA-9262).
* fix(native-chat): a card's cancel sends nothing without a turn again, and rewind keeps a newer row after any held row
The host's cancel request has to name a turn on every version, so the turnless
prompt cancel is reverted on desktop and phone. The rewind merge moves its anchor
for every row the provider still holds, so a newer Orca's row stays after one
whose kind this build does not know.
* fix(native-chat): a card this build cannot answer leaves the composer open, and a send starts a turn
An approval of a subject kind this build cannot draw, raised by a newer host's background agent
while no turn runs, left nothing to press: its answers are disabled, its cancel needs a turn, and
the desktop composer gave the card its slot. A send the host queues waits behind any pending
prompt, so even the phone's open composer only queued.
While every pending prompt is one this build cannot answer, the desktop composer stays open, and
desktop and phone send without asking to queue: the send starts a turn, and the card's cancel then
settles it. A chat a dead run left is unaffected: opening it already cancels those prompts.
* feat(native-chat): a chat a newer Orca saved fails to load and says to update, with nothing to send into
Opening a chat a newer Orca saved (a row kind, batch-change kind or sent-message kind this build does
not know, a newer row version, or a newer history store) used to open it read-only: the history
shown, the composer locked with a reason, every control greyed. Now it fails the load, like a
damaged chat, with its own words: "This chat was saved by a newer Orca. Update Orca to open it."
Every row is kept. The read is final, so the pane stops retrying; reopening the tab reads again.
A chat whose load failed for good (damaged, or a newer Orca's) offers no composer under the error,
on desktop and on the phone, so the failure is said once and no send can be refused a second time.
A restart offer for a newer Orca's chat is spent without a word: no failure filed, nothing counted.
Removed with the read-only mode: the journal's read-only latch and every check of it, the history
page's readOnly field (never released), the cursorless reconnect, read-only paging, the composer
lock and placeholder, the greyed controls, and the resume exclusion.
* test(native-chat): a newer Orca's records open no chat, and a status fake carries its submissions
* test(native-chat): the status fake's cast says what the feed reads
* fix(native-chat): a chat's read that failed for good takes the whole pane, over a loaded transcript too
* test(native-chat): the final-read-failure test names its reasons
* fix(native-chat): a newer Orca's chat keeps its restart offer, and its refused load is logged once
* refactor(native-chat): the newer-Orca chats a restart listing skips live beside the candidate reader
* fix(native-chat): a refused chat load keeps where it failed, so the log names it and a new reason logs again
* chore(native-chat): the reducer's window merge back where main has it, and comments say a newer Orca's chat fails to load
* test(agent-hooks): the rename test's journal fakes carry submissions, as a journal snapshot does
* test(agent-hooks): the rename test's journal fakes say what the status feed reads
* fix(native-chat): Dismiss all ends the restart offers this Orca lists, and keeps a newer Orca's hidden ones
* test(native-chat): a resend against a newer Orca's store answers unknown, and the phone says nothing beside the read's words
This build never opens a chat a newer Orca's database holds, so a resent send id there cannot be
answered from its journal: it answers "outcome unknown", never a refusal and never a made-up
record, with no second delivery and no write. On the phone, a send error left beside a read that
failed for good is not shown; the pane keeps only the read's words.
Keep IME confirmation out of text-field submission and command selection. Reuse shared gesture ownership, preserve marked-release redispatch, guard overlay Escape capture, and retain Markdown save shortcuts.
Verified with independent adversarial reviews, 891 tests across 71 files, six hidden Electron tests, project typecheck, full lint, the changed-code quality gate, and all final-head CI checks.
Fixes#25035.
Co-authored-by: kambarakun <kambarakun@gmail.com>
fix(codex): restart account switches without blanking or repinning the pane
Integrate the atomic pane replacement from #24350 and adapt the explicit
conversation resume from #14877. Keep terminal protocol replies flowing
while account notices block user input. Ordinary restores retain provenance.
Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com>
Co-authored-by: itisvincent <vincentcgamer@gmail.com>
Co-authored-by: rayim <rayim@fxy.global>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* Add direct CSV table editing and persistent column widths
* Preserve pending CSV edits across concurrent saves and panes
* Keep CSV input stable during autosave and table commands
* Cancel delayed menu paste after CSV pane focus changes
* Prove delayed CSV menu paste starts before focus changes
Restore IME Enter protection in workspace details by reusing the existing composition tracker. Reset Notes ownership at textarea detachment and preserve sizing behavior. Repair isolated native test-window delivery without changing the production foreground policy or original native input assertions.
Fixes#24097
Related contributor history: #10711, #11067, #13128, #13282.
Original implementation and macOS recordings: @setodeve, commit b30f095.
Verified on required stock Linux X11/Wayland checks and independent frozen-source review.
Co-authored-by: setodeve <keinick11@outlook.com>
* Redesign the phone-control and phone-size terminal dialogs
Drop the eyebrow label and circled icon, shorten the copy so it no longer
restates the buttons, and give each state one primary action with a quieter
"all" action. Collapse moves out of the button row into a Minimize icon in
the corner. Behavior is unchanged.
* Point the phone settings copy at the renamed Restore button; drop dead ko overrides
The phone app and desktop update independently, so name only "Restore",
which matches both the old and new desktop banner labels.
* Isolate code editor text and undo history by execution host
* Verify editor owner resolution and Windows model path isolation
* Respect native model line endings in content sync history coverage
* Preserve selection and scroll when editor ownership resolves
* Verify owner synchronization against a reachable editor change callback
Keep Cursor quota requests tied to the selected account by omitting ambient Electron session credentials. Preserve explicit account cookies and redirect handling.
Credit: Li-Sanze for the original credential-mode fix in #23626, carried through #24575; jjongsta and chengjiaxiao for the reports. Native HTTP/HTTPS cookie isolation, mutation controls and proxy behavior were verified before merging. This does not claim to resolve the separate initial-authentication failure in #23612.
Adds a user-assignable shortcut for the existing child-workspace chip action. It stays unassigned by default on macOS, Linux and Windows. Final-source rendered checks cover Settings recording/reset, guards, scroll preservation and restart.
Fixes#24163
Continues mmarabel’s original contribution in this PR. Issue #24163 has no sibling implementation PR. Existing bot findings are fixed, withdrawn or addressed in the PR review.
Co-authored-by: mmarabel <166927047+mmarabel@users.noreply.github.com>
* ci: run cross-version wire suites when agent sends or orchestration change
The selector skipped the cross-version wire job for #24901, which changed the
shared agent-send path, the structured send envelope builders, and orchestration
RPC code. Route the send payload/fingerprint builders, src/main/runtime/orchestration/,
and the orchestration RPC methods to the job, and pin each rule in a test.
* ci: run cross-version wire suites only for code they execute
The agent-session suites now build their send through the shared outbox
builder and check it against the release host's schema and fingerprint,
so the send builder and outbox are selected exactly. Load-only
orchestration rules are dropped; the dispatcher-path files every suite
request runs through are selected instead.
* ci: run the release's own send admission, cover queued sends and the RPC reply builder
* test(cross-version): a wrong send fingerprint must be refused, so an agreed one is not vacuous
Keep older request and reply parsers usable while current peers retain all supported history.
Co-authored-by: nwparker <nwparker@users.noreply.github.com>
Redirect the managed Windows payload file into curl instead of starting
pipeline shells, register native Windows delivery coverage, and document
Jcode v0.89.0+ as the upstream launcher requirement for invisible hooks.
Negotiate Jcode history in both directions with mixed-version Orca hosts,
preserving supported search filters and old-client response compatibility.
Co-authored-by: czzczz <chanzrz_zbf@foxmail.com>
Co-authored-by: JianJia2018 <39438074+JianJia2018@users.noreply.github.com>
* fix(native-chat): a host admits structured sessions by client capability, not its own chat setting
A host's experimentalStructuredNativeChat decided whether any paired client could reach
agentSession.* at all, and whether session.tabs.* showed it structured tabs. That setting is the
host user's own launch preference: whether a new agent opens as a chat or a terminal is decided by
whoever launches it. Using it as admission control meant a client whose own preference was
"structured chat" was refused on a host whose preference was "terminal", and chats opened while
the setting was on were withheld from mobile once it was turned off.
The gate now asks one thing: did the client advertise agent-session.structured.v1 (in-process
callers negotiate nothing and are always admitted). Tab projection and restore follow the same
rule. With the setting no longer gating anything, the separate cleanup gate (close, cancel,
unsubscribe, release), which existed only so those kept working after the setting was switched
off, is identical to the main gate and is folded into it. The settings listener that republished
tabs when the setting changed is removed, since projection no longer depends on it.
The host setting still picks the default for launches that start on the host itself
(agent.launch from mobile, orchestration worker-start).
* fix(native-chat): the desktop declares structured chat support to paired hosts
The desktop renderer advertised agent-session.structured.v1 (and the Claude, turn-item and
background-task capabilities that go with it) to its own main process but not to a paired Orca
server. The server therefore refused every agentSession.* call from the desktop and stripped
structured chat tabs out of the tab list it published to it, so a structured chat running on a
paired server never appeared on the desktop, even though the renderer already mirrors a host's
agent-session tabs and drives each one against the server that owns its workspace.
The same renderer reads structured chats on either host, so the remote Electron list now carries
the same structured-session capabilities as the local one, and the capability test pins that
nothing is advertised only locally.
* feat(native-chat): open structured chats on the paired server that owns the workspace
With the structured-chat default on, an agent launched in a workspace that lives on a paired Orca
server always opened as a terminal (or the terminal-backed chat view). Three things kept it off
the structured path: the launch check refused every host but this machine, a remote workspace was
handed to the host-published terminal path before the structured route was even considered, and
the structured launch pipeline sent create and every follow-up call to this machine's runtime.
A workspace's owning runtime is fixed, so the pipeline now derives it from the workspace instead
of assuming this machine (structured-agent-session-owner.ts, the same derivation the chat pane
already uses to read a session). The launch intent carries that target; the pre-create support
check, create, the publication check and fence read, the launch prompt send, held option picks,
the "focus this chat" marker, the placeholder tab's host, and tab close/purge all use it.
The launch check now accepts a paired server and asks that server's own capabilities (read from
the status the client already cached for it) rather than this machine's. An SSH workspace stays
terminal-backed: no Orca runtime runs there. The host still answers createSupport before
anything is created, so an older server that refuses shows the failure in the chat tab.
A chat the user closed before its create landed is now also retired on the paired server when it
publishes, as the local sync already does. Orchestration workers placed on another runtime are
unchanged: federation creates terminal agents only.
* fix(native-chat): negotiate client-chosen launch mode so released phones and old servers keep terminals
Hosts advertise agent-session.structured.client-launch-mode.v1: they admit
structured sessions by client capability alone. A remote client that does
not advertise it (phones released before agent.launch) asks createSupport
to pick the launch mode, so the host keeps answering that with its own
setting, exactly as before. Cleanup methods keep their own named gate so a
future admission condition cannot make close or cancel refusable.
* refactor(runtime): keep the Electron client capability list in its own module
protocol-version.ts is at its line budget; the list is what the desktop
advertises to paired hosts, not the host's own contract.
* fix(native-chat): the desktop declares it picks each launch mode itself
Paired hosts and the desktop's own main process then answer createSupport
by the workspace rather than by their own chat setting.
* fix(native-chat): pin each structured chat to the host it was launched on
- Route: a paired server opens a chat only when it advertises the
client-chosen launch mode; an older server keeps its terminal. Its
capabilities come from the store's host status, not the compatibility
cache that is empty after boot or reconnect.
- A launch command override is this machine's: the route applies it only
locally, and a host's createSupport refuses on its own override.
- The owning host is resolved once, from the same value the route used,
and carried on the launch intent, its persisted record (legacy records
load as local), the provisional tab and every mirrored chat tab. Close,
purge, retry and reload read it instead of re-deriving it from a
worktree id two hosts can share; an owner that cannot be named refuses.
- Cancellation tombstones record their host: only that host's
authoritative inventory retires one, restored cleanup closes it there,
and a paired host's tombstone expires after 30 days if it never answers.
- A paired server's frame settles launches it published, as the local
inventory already does for this machine.
* fix(native-chat): a paired server that declines a chat opens its terminal instead
createSupport only reads, so both of its non-answers are settled before
anything is created:
- A paired server that answers it cannot run the chat (a WSL repo, a
Claude account mismatch, its own launch command override) closes the
chat tab and opens the terminal the route would have chosen, with a
notice saying why. This machine's own decline stays a failed chat.
- A host that could not be asked closes the chat tab and leaves one
failure toast, instead of a lingering "could not confirm" chat.
* test(native-chat): a provisional chat carries its launch's host and hands pre-create failures on
* chore(native-chat): justify the two type assertions this change's lines touch
* test(native-chat): state why each staged test fixture is cast
* fix(native-chat): chats that already exist keep showing whatever the chat setting says
The structured chat setting decides only what new agents open as. With it
off, this machine's structured chats used to be hidden while the host,
which no longer reads the setting, still reported them to the workspace
activation gate, so a workspace holding only a chat opened empty. The
local chat mirror and its startup restore now run whatever the setting
says, the continue-after-restart offer follows the chats that exist, and
the setting's copy says it applies to new agents.
* fix(native-chat): the browser client keeps its host terminal on paired servers
A browser client whose own preferences turn structured chat on took the
structured route for every paired-server workspace, but its handshake
never says it reads structured sessions, so the server refused the chat
and the user got a failed chat tab where a host terminal used to open.
The route for a paired host now also asks what this client advertises to
it: the desktop's list does, the browser client's does not. Its handshake
list is now a named constant the route reads, so the two cannot drift.
The chat setting's copy now says it runs on paired Orca servers too;
WSL and SSH hosts still use terminal chat.
* fix(native-chat): a retried launch a paired server declines opens its terminal too
A launch restored after a reload settles only through its Retry, so a
declining paired server left a failed chat there while a first launch got
the server's terminal and a notice. The chat's Retry now hands the same
pre-create failures to the same replacement, carrying the prompt the
launch had staged.
* refactor(native-chat): a paired host's cancelled-chat record ends on its 30-day TTL
The paired census re-read a host's whole inventory after every
authoritative frame to retire tombstones, and a tombstone restored after
a reload needed a second such frame, so in practice it retired nothing.
A tombstone guards a random session id and is inert once stale; the chat
is already closed on its host whenever a frame shows it. The census, its
trigger in the mirror layer and its cleanup are removed; the owner-scoped
tombstones, close-on-sight, the TTL and publication marking from frames
stay.
* fix(native-chat): a chat's pane and status read from the host recorded on its tab
The chat pane and its sidebar status still derived the host from the
workspace id, which two hosts can share; a paired chat in a non-active
same-id workspace was read from this machine. Both now read the owner
stamped on the tab, as close, purge and publication already do.
* fix(native-chat): "Resume in chat" follows the terminal resume's host rule
Agent Session History offered "Resume in chat" for a conversation
recorded on this machine into a paired server's workspace, where its
transcript does not exist. A chat now resumes a conversation only on the
host that recorded it, as the terminal resume does, and that host is the
one asked whether it can resume history.
* fix(native-chat): the chat setting says older paired servers keep terminal chat
* test(native-chat): pin that a host advertises the client-chosen launch mode
* fix(native-chat): mirror this machine's chats only where it holds them
Round 1 ran the local chat mirror for everyone so existing chats show
whatever the setting says. That gave every desktop a permanent
session-tabs listener, which turns on the runtime's phone replication
paths, plus two full session-tab censuses at startup, and made the
browser client mirror its remote host a second time.
The runtime now says whether it holds structured chats: its structured
host is built only when saved chats were restored at startup or a client
created one here, and it announces the moment one is built. The mirror,
the startup restore and the continue-after-restart offer run only when
the setting launches chats or the host holds some, and never in the
browser client. A chat a paired client creates here with the setting off
still appears at once. The chat behaviour settings show wherever chats
exist, and the setting's copy says it picks what new agents open as. The
toggle-off teardown this made dead is removed.
* test(native-chat): route a paired-server launch over the capability lists both sides really advertise
* test(native-chat): record install listeners without a cast
* fix(native-chat): a paired server admits a chat before any of it exists here
The desktop opened a paired server's chat tab, launch record, queued
prompt and focus intent before asking the server, so a "no" needed a
replacement that undid and redid all of it, and every piece it missed
was a bug: the workspace deselected, the caller told "failed" while a
terminal ran its prompt, the caller's arguments and other queued prompts
lost, and a create whose reply was lost treated as never sent.
A paired launch now asks the server first and commits nothing until it
answers. Admitted opens the chat as before. Declined runs the caller's
own launch as the server's terminal, with the existing notice (a resume
fails instead, having no terminal equivalent). Unreachable opens nothing
and names the server in one toast. The new-tab launcher reports the
host's surface for paired workspaces, as it did before paired chats,
with the prompt delivery of whichever surface got the prompt. The
replacement and its error classes are gone, and the probe inside a
launch is back to its old meaning: a "no" is a failed chat with Retry,
and no answer leaves "Could not confirm" with Retry and the prompt kept,
here as on this machine.
* fix(native-chat): mirror this machine's chats only once it holds one, not once its host is built
Session history, resume preparation, terminal resume commands and replay-safe phone launches all
build the structured host for users who never had a chat, which turned on the chat mirror and the
structured-only settings rows until the next restart. The signal is now derived from the host's
records (or a records file still owed its import) and pushed when the first chat is restored or
created. A throwing listener no longer fails the install that fired it.
* fix(native-chat): a fork's reveal never seeds a terminal beside the surface the launcher opens
Forking into a paired-server workspace revealed it as if nothing would open there, so the reveal
created a blank host terminal beside the forked chat (and beside a forked agent terminal on main).
The launcher always opens the fork's surface itself, so the reveal now says so for every surface,
as the fix-checks launch already does.
* fix(native-chat): a declined direct launch keeps the caller's CLI args; an unreachable resume toasts once
When a paired server declines a "Fix checks" chat in a new workspace, the terminal that opens
instead now carries the recipe's saved CLI arguments, launch platform and launch source, as the
terminal route did. "Resume in chat" to a server that cannot be reached showed the admission's
"Could not reach" toast and the vault's generic one; the admission marks its failure notified and
the vault adds nothing.
* fix(native-chat): a declined background create opens its terminal without switching workspaces
Since #23974 a worktree create the user moved away from must not pull them onto the new
workspace. When a paired server declined that create's chat, the fallback terminal opened as a new
agent tab, whose host create selects the workspace. The create now opens its own agent terminal the
way main's background branch does: in place from the request's startup plan (so its CLI args carry),
without selecting the workspace. A create the user is still watching keeps the new-tab fallback.
* test(native-chat): name the launch's host in main's new outbox fence test
Main's new staging-failure test calls settleStructuredAgentLaunchPrompt without the target this PR
made required; it is a local launch, as in the sibling tests.
* fix(native-chat): a paired server's new chat shows no model until the server reports the one it started
A chat on a paired server starts with the server's saved model and options, but the picker showed
this desktop's saved selection (or the catalog default) until the server reported a model, and a
pick made in that window was remembered on the server under that guessed model. A paired launch
now carries no desktop seed, and until the server reports its model the picker names no model and
takes no picks. Local chats are unchanged.
* test(native-chat): seed the paired repo without a cast
The repo literal already satisfies Repo, so the changed-lines cast gate has nothing to excuse.
* feat(native-chat): createSupport reports the saved selection a new chat on this host starts with
A chat on a paired server starts with the server's saved model and options, which the desktop could
not read, so its picker showed a guess. createSupport's answer, which the desktop already waits for
before a paired launch, now also carries that seed as a new optional field (older clients ignore it).
Create and createSupport read it through one resolver so they cannot drift.
* fix(native-chat): a paired server's new chat shows the selection the server will start it with
The paired server now names its saved model and options in the admission answer the desktop
already waits for. That seed goes into the launch intent and its persisted record, so the picker
shows the server's model at once, stays pickable like a local chat, and remembers picks on the
server under that model; a reload shows the same. The locked picker remains only for a server too
old to name a seed.
Also moves host admission and launch-outcome tracking into their own modules: the latest main
merge left structured-agent-session-launch.ts over the max-lines limit.
* test(native-chat): expect the launch intent's new seed argument in exact-call assertions
* refactor(protocol): move the Electron remote client capability list into its own module
Merging main left protocol-version.ts one line over the max-lines limit on this branch. The list of
capabilities the desktop advertises to a paired host moves, unchanged, into
electron-remote-runtime-client-capabilities.ts, the module the next PR in the stack already uses
for it; importers point there.
* fix(native-chat): a paired chat with no saved server model is pickable; Retry shows the server's current seed
A server whose user never saved a chat model sends no seed, and the desktop showed a locked,
model-only picker for it, although that is the common case: no server that can admit a paired chat
predates the seed field. Such a chat now behaves like a local chat with no saved model: the CLI
default, pickable. The lock and its snapshot helper are gone.
Retry kept the first admission's seed while the create probe, which already runs on every attempt,
reported the server's current one and dropped it. The probe's seed now replaces a paired launch's
seed and the picker's, so a retried chat shows what its create will run.
* test(cross-version): stub the launch seed resolver createSupport now reads
* test(protocol): pin the desktop capability divergence against what a paired server receives
Every paired transport sends the shared remote base plus the Electron list, so the
divergence test now compares that union with the renderer's local list instead of
the declared Electron list. A capability added only to the shared base can no
longer slip past it. The two base-only capabilities it surfaced are recorded:
skills.install-result.v2 has no local caller; the authoritative-inventory label is
read by the local tabs sync but dropped by main, and is marked unsettled.
The turn-item and both background-task-stop capabilities were already sent through
the shared base, so the Electron list no longer repeats them. The wire set is
unchanged; this PR's real change on the wire is structured.v1, the Claude
structured capability and the client launch-mode capability.
* fix(native-chat): the desktop tells its own host it picks each launch mode, so retrying an existing chat works with the setting off
* docs(native-chat): name the real exit for the released-phone createSupport rule
* fix(native-chat): the route reads the capabilities a paired host actually receives
The renderer decided whether a paired host would admit a chat from the desktop's Electron list, but
every desktop transport sends that list plus the shared remote base. They agreed only because the
route's checks happened to sit in both. The route input is now built with the same
remoteRuntimeClientCapabilities the transports use (the browser client already sends its list as is),
and a test pins each against the real handshake.
* test(cross-version): a released client still gets the host-setting createSupport answer; a launch-mode client gets supported plus the seed
* test(native-chat): let main's child-records test resolve each chat's owner
Main's new test mocks worktree-runtime-owner with only the runtime environment id, but the status
projection in this PR also resolves each structured chat's owner from the worktree. The mock keeps
the module's real exports and overrides only what the test pins.
* fix(deps): take #24204's lockfile that the merge reverted
* test(native-chat): let the Codex child-approval e2e unit test resolve each chat's owner
Its worktree-runtime-owner mock exported only the runtime environment id, but this PR's status
projection also resolves each structured chat's owner from the worktree. The mock now keeps the
module's real exports and overrides only that id, as structured-child-records-switch does.
* test(native-chat): move the close-race launch cases into their own file
Merging main added launch tests on both sides and took structured-agent-session-launch.test.ts past
the 800-line limit. The three cases where a tab close races a launch move to
structured-agent-session-launch-close-race.test.ts, with the same setup the other split launch
suites copy.
Forward wrapping to both diff panes through the existing editor option path and clean up listeners.
Co-authored-by: Wooseong Kim <innocarpe@gmail.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Neil <neil@stably.ai>
* Add Qoder session history and search with real CLI coverage
* Allow the real Qoder marker file to end with a newline
* Keep Qoder tool output out of history previews and search
* Keep Qoder search pages readable by older clients
* Verify persisted Qoder history after a real generated and resumed task
* Negotiate Qoder filters before searching an older execution host
* Combine search client imports for the CI plugin gate
* Keep the relay search oracle aligned with legacy agent filtering
* Register supervised Qoder China and Qwen lifecycle integration
* Cover Qoder China mobile assets and mixed-host resume gates
* Verify Qoder provider tags against the older released wire parser
* Verify China and Qwen keep independent Windows hook scripts
* Verify Qoder registrations against the installed older Windows release
* test(qoder): align search capability contracts and pin old-host fencing
* fix(qoder): rank exact picker identities and command aliases first
* test(qoder): preserve the regional CLI shared icon expectation
Keep the full bundled-asset and no-remote-image checks, with an explicit
shared-logo basename for Qoder China. The map also works with older
catalog type unions.
* fix(qoder): align China catalog entry with fallback order
---------
Co-authored-by: Orca Integration Recovery <orca-validation@invalid.example>
Port the synthetic fixture and replay coverage from Neil/nwparker original PR #19195 (8142d72ae0 and 14f6a387c3). Validate unknown fixture JSON with Zod before checking its SHA-256 and keep the existing 8 MiB workload and scheduler budgets.