mirror of
https://github.com/stablyai/orca.git
synced 2026-11-01 01:27:35 +00:00
5e094889116de9a695009bcbd2cd348b9191dcfb
2914
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
5e09488911 |
fix(terminal-daemon): status says live, exited or unverifiable, and shows the process ceiling (#28239)
* fix(terminal-daemon): status reports live, exited or unverifiable, and the process ceiling `orca terminal-daemon status` printed 'Terminal daemon: OK.' whenever no severed daemon was recorded, including the ~60 s after a kill -9 before the next terminal respawned it. The host now probes the daemon (health check, then its pid record with Linux start-time identity) and returns an additive `daemon` report; the CLI prints the verdict and, for a live daemon, the terminal process ceiling, which no CLI command showed under Electron serve. Fixes BUG-5 and BUG-7 from the real-host Linux test run. * fix(terminal-daemon): load the ceiling description from shared so the packaged CLI can resolve it The packaged CLI cannot load out/main/daemon modules: electron-vite replaces out/main after the CLI build. The ceiling state and its description move to src/shared, which the CLI already ships. * fix(terminal-daemon): a pid the signal proved live is never exited because procfs hides it * fix(terminal-daemon): load the liveness probe lazily so the RPC table stays light Importing the probe eagerly pulled the daemon provider graph into every module that loads the runtime RPC method table, which broke the cross-version terminal wire harness. --------- Co-authored-by: m4air <m4air@Mac.localdomain> |
||
|
|
b724e3a24d |
fix(linux): a supervised AppImage serve follows a replaced AppImage (#28244)
* fix(linux): a supervised AppImage serve restarts from a replaced AppImage Swapping the AppImage file never reached a supervised serve: it runs the copy it unpacked, and even a full service restart went back through the stable launcher to that same copy. The extraction cache now records which AppImage each namespace came from. Before each start the supervisor compares that file's identity with the build it runs; once a settled new file is there and runs on this host, it execs the new AppImage with its own arguments (same pid), whose serve unpacks the new build and re-points the stable launcher. Fixes BUG-4 from the real-host Linux test run. * fix(linux): try a replaced AppImage once, and let it past an old build's give-up - A give-up recorded against the old build no longer stops the supervisor before it checks for a replaced AppImage. - The supervisor records the AppImage identity it is about to exec in the profile. If that build dies before its own crash brake, later starts of the old build keep running it instead of exec'ing the same broken file until systemd's start limit disables the service. A supervisor running that build, a further replacement, or --force clears the record. * test(cli): compare the namespace listing order-independently --------- Co-authored-by: m4air <m4air@Mac.localdomain> |
||
|
|
e09d732d17 |
perf(wsl): pooled wsl.exe command loops and orcad compile cache on the WSL wake path (C4 follow-up) (#27923)
* perf(wsl): run stdin-free probes over pooled wsl.exe command loops; cache orcad's compiled bundle The WSL wake path made ~37 sequential wsl.exe starts at ~180 ms each. Stdin-free execCommand calls now go through a few long-lived wsl.exe sh loops (one command at a time per loop, own session, stdin at EOF, base64-framed output). SSH has no such method, so its commands and options are unchanged. orcad enables Node's compile cache inside a client-managed slot, and hands it to the profile preflight child, which compiles the same bundle. * fix(orcad): share the slot compile cache with the preflight only in a managed slot An unmanaged orcad started with its own NODE_COMPILE_CACHE kept that location for its preflight before; never redirect it into the install dir. |
||
|
|
9e52645e6c |
Show Codex model capacity failures as red agent status (#28231)
* Show Codex model capacity failures in agent status * Reduce Codex capacity detection work on terminal output * Preserve Codex capacity notices across partial terminal frames |
||
|
|
4559c250b3 |
fix(windows): restrict secure files in-process instead of spawning icacls (#28135)
* fix(windows): restrict secure files in-process instead of spawning icacls Every secure write on Windows spawned icacls.exe five times on the calling thread (the Electron main thread for most stores): verify, /reset, /inheritance:r /grant:r, verify, verify. Process creation under endpoint security costs ~0.6 s each, so each write froze the UI for seconds (#26027). The /reset + /inheritance:r pair is also what behavioural EDR scores as ransomware tradecraft (#25583). Add native/windows-security (@orca/windows-security): a small N-API addon over advapi32 that reads and writes DACLs in-process. It exposes the token user SID, a DACL read as SDDL, a DACL write (sync and on the thread pool), a create-with-DACL file write, and an additive grant. Policy and verification stay in TypeScript. - writeSecureFile creates each temp file already carrying the protected user/SYSTEM/Administrators DACL (CreateFileW + SECURITY_ATTRIBUTES), reads it back once, and renames it. No ACL is modified on the write path and no process starts. Before: 5-7 spawns, 94-138 ms per write. After: 0 spawns, ~0.5 ms. - A secure directory is verified once per process. It is rewritten only when verification fails, on the thread pool, because the write walks every descendant (~2 s on a 29k-entry userData). - The verifier follows OpenSSH's rule: only the user, SYSTEM and Administrators may be granted anything, and the path must be protected (or, for a file, inherit from a secure parent). DACLs written by the icacls-era hardening pass unchanged, so there is no migration. - If the addon cannot load, the write still happens and reports unrestricted, logged once. There is no icacls or PowerShell fallback. - grantDirAcl, the startup userData grant, and the install-dir probe and package repair use the same addon, so whoami.exe and icacls.exe are no longer spawned anywhere at runtime. - orcad's Windows slots ship the addon beside their bundles (built per arch, PE-machine checked). Its data-root privacy check fails closed, so without the addon a Windows orcad would not start. Fixes #26027 Fixes #25583 * refactor(secure-file): harden existing Windows files through the sync lane The read path called restrictWindowsPathSync directly and recorded success itself; the existing sync lane already does both. One call site fewer. * refactor(windows-security): one load, one rule, no fallthrough (review #28135) - The descriptor parser is anchored, so a conditional ACE (its body holds parentheses) or anything else it cannot read fails closed instead of verifying the ACEs before it. - findSecureDaclViolation is one flat rule: protected, allow rules only for the user, SYSTEM and Administrators (OpenSSH's ACE rule), and full control for the user. The inherits-from-a-secure-parent exception is gone: it accepted exactly what loosening the parent reaches. - The addon and the user SID load once, behind one wrapper that adds the extended-length prefix to every path and runs async DACL writes to one path in order, so a startup grant cannot land after a re-lock and restore the broader DACL. - With the addon loaded, a failed born-restricted create throws with the matching Node error code instead of falling back to a plain write. - A grant leaves a NULL DACL untouched instead of turning 'everyone' into 'only this SID'. WriteFile runs once and fails on a short write. - The artifact create-intent store writes through writeSecureFile and drops its own temp writer, directory LRU and synchronous directory re-lock. - Test seams the global fake makes redundant are gone. * fix(packaging): fail a Windows build whose workspace addon binary is missing or foreign-arch The @orca/windows-* workspace links exist on every host, so their presence proved nothing: a missing or wrong-architecture .node only surfaced at runtime, as unrestricted secure files or an orcad that refuses to start. beforePack now reads each addon's PE machine against the target arch. * refactor(windows-security): prune what the native ACL left dead (review #28135) - Sync-spawn ratchet: hardenSecurePath and the sync-flag exemption it alone used are gone, and the advice and baseline header no longer say secure writers run icacls. - The install-dir probe reads ACEs through parseSddlDacl, so an ACE it cannot parse fails the whole reading closed instead of being skipped; AC is a resolved alias there now. - The packaging check reads the addon binary through node_modules, the copy that ships. - No winCode-to-errno table: nothing reads .code off a failed secure write. - The load memo is not mutated; secure-path keeps its own log-once flag. - grantInstallDirPackageAcl is a plain function, and secure-file restricts a Windows directory and a POSIX path directly instead of through one two-platform helper. * fix(windows-security): resolve LA in real-ACL tests; tear a file whose close fails - The GitHub Windows runner is the built-in Administrator (RID 500), whose SID reads back as LA. Production already resolves it through localDomainSidOf; the win32 test helpers parsed without it. One shared readDaclOf resolves aliases for every real-ACL suite, and the verifier's unit test covers an LA-aliased directory and LA as a stranger. - createFileWithDacl treats a failed CloseHandle like a failed write (lost data on a network volume) and deletes the torn file. - Drop a duplicate test reset and a typo. * fix(windows-security): load only from inside the app; read DACLs as structure (review #28135) - Loader: orcad's staged binary first, then the workspace package only if its real path is inside the app (process.resourcesPath, or this checkout). A bare specifier otherwise walks every parent up to C:\node_modules, which any local user may create, and orcad holds credentials. - The addon reads a DACL as { protected, aces: [{ type, flags, mask, sid }] } via GetAce, with a SID only for plain allow/deny ACEs and null for a NULL DACL. findSecureDaclViolation and the install-dir probe check that structure, so conditional, callback and object ACEs fail closed. The SDDL parser, its alias table, LA/localDomainSid resolution and getDaclSddl are gone; SDDL is only the write format. - Install-dir tree pass: files flagless as before, but a directory only when its own DACL is protected (inheritably). Re-granting inheriting directories re-propagated each subtree (3.2 s for 1,093 directories). The root is no longer granted, or counted, twice. The two passes are plain functions behind a { root, tree } seam. - One namespacing (path.win32.toNamespacedPath) at the addon boundary, also the per-path ordering key; the comment states the real sync-grant invariant. - restrictWindowsDirectory(dir): Promise<boolean> replaces the callback lane; the reporter is guarded once, so the 'settle' stage is gone. - Sync-spawn ratchet: the secure writers no longer spawn or block, so they leave the gate (109 -> 61 uses). - Errors name the Win32 call and code only; a failed GetSecurityDescriptorControl fails the write instead of defaulting to unprotected. * test(secure-file): assert the durable POSIX write's outcome, not the old chmod order writeSecureFile now chmods the temp file to 0o600 before fsyncing it, so the fsync also covers the mode change and the file is owner-writable when opened. Assert what matters under umask 0o200: the write succeeds, the temp file is fsynced read-only, and the published file is exactly 0o600. * fix(windows-security): namespace paths with the host's toNamespacedPath path.win32.toNamespacedPath rewrote POSIX temp paths (/tmp/x -> \tmp\x) in the Linux runs of the Windows-logic unit tests, so the fake addon wrote to the wrong place. The addon only runs on Windows, where the host function is the win32 one; elsewhere it is the identity. The long-path assertion runs on Windows only. * test(windows-security): assert the install-dir long-path prefix on Windows only The prefix comes from the host's toNamespacedPath, which is the identity off Windows; the Linux run of this suite expected the win32 escape. * fix(windows-user-data-acl): grant the root even when its children cannot be listed (review #28135) A readdir failure on userData aborted the whole grant, so the root grant that could repair a DACL refusing the listing was never attempted. The listing failure is now recorded, the root is still granted, and the marker stays withheld so the next launch retries. * test(windows-security): wait for relock propagation, accept Entra ID SIDs (review #28135) - The large-relock test waited only for the parent to turn protected, which happens before propagation reaches the children, then read a child. It now waits on the parent and both ends of the child range. - The token-user test rejected S-1-12-1 (Entra ID) accounts, failing the suite on Entra-joined dev machines. --------- Co-authored-by: Orca Worker <orca-worker@localhost> |
||
|
|
7246a0e269 |
Fix delayed agent launches such as sleep 30 && claude (#28205)
* fix(terminal): preserve preparation and discovery for delayed agent launches * refactor(agents): centralize launch intent recognition * refactor(agents): keep launch intent in one module apart from process recognition Move recognizeAgentLaunchCommand next to the sleep-prefix scanner so the strict process-table matcher is untouched, name the module away from the agent-launch funnel files, share one executable-name normalizer, and colocate the preparation tests with their modules. |
||
|
|
aceebea0a4 |
fix(terminal): name a new tab's first pane so its saved layout is never empty (#28168)
* fix(terminal): name a new tab's first pane so its saved layout is never empty createTab now always mints the first leaf id, so the window never saves a terminal tab with an empty layout root. With no empty roots coming from the window, main's save-time swap that kept its own root over an empty window root (from #1909) has nothing left to guard, and is deleted. * test(persistence): an empty-root layout from an old session still loads and gets its pane * fix(persistence): name an old session's empty layout root once at load A session saved before tabs named their first pane can hold an unmounted tab with an empty root. If main bound a terminal there, the window's next save of that tab dropped the binding once the save-time swap was gone. Name the pane at load instead (the same pane the window would mount), so no save carries an empty root. The rootless-pane resolver moves to shared so main and the window pick the same pane. * test(persistence): name the bind-then-stale-write order the load repair covers * fix(persistence): name an old empty tab's pane with the stable headless leaf id The load repair fell back to a random id, so an unsaved headless serve gave a phone a new tab::leaf id on every restart. Use deriveHeadlessLegacyTerminalLeafId, which phones already saw for these tabs. Also import the rootless resolver from shared directly and return the loaded state unchanged when nothing needs repair. |
||
|
|
5929350944 |
fix(linux): direct AppImage serve honours the chosen profile and --supervise (#28227)
* fix(linux): run a direct AppImage serve through the CLI so it keeps its profile and supervisor A direct `Orca.AppImage serve` stayed in the Electron process and skipped the CLI's serve front end, so it ignored ORCA_USER_DATA_PATH and silently dropped --supervise. It now execs into the bundled CLI (same pid, so service managers and signals still reach it), translating a leading --user-data-dir into ORCA_USER_DATA_PATH. Also lists terminal-daemon and reference as CLI commands so a direct binary launch of them reaches the CLI. Fixes BUG-1, BUG-3 and BUG-10 from the real-host Linux test run. * fix(linux): drop desktop switches anywhere in a direct serve launch * fix(linux): keep a direct serve's --no-sandbox for the Electron server the CLI starts --------- Co-authored-by: m4air <m4air@Mac.localdomain> |
||
|
|
8cd5bb2a74 |
feat(wsl): deploy and launch orcad inside a WSL distro (WSL-via-orcad C4) (#27819)
* feat(wsl): deploy and launch orcad inside a WSL distro (WSL-via-orcad C4) * fix(wsl): keep the install-dir fallback on the PATH detection searched, so a /mnt/c shim cannot re-enter * fix(wsl): call the install-dir resolver exactly as before when no PATH is given * fix(wsl): import orcad artifacts from their new orcad/ home * fix(wsl): adopt an orcad serving in the distro that the activation record does not name * fix(wsl): filter /mnt/<drive> PATH entries only in an orcad launched as a WSL host SSH-managed orcad inside a distro and Orca under WSLg kept detecting drive-mounted CLIs before this stack; the setsid launch now marks itself with ORCA_WSL_ORCAD_HOST=1 and only that process filters. * fix(wsl): keep the install-dir fallback on the process PATH outside a WSL host orcad On Windows the preflight env merges the persisted registry Path, which the fallback never searched before; only the marked WSL host runtime now passes a filtered PATH. |
||
|
|
49a1129aa6 |
build(linux): carry the pinned Node inside the AppImage so serve's slot works offline (#28204)
* build(linux): carry the pinned Node inside the AppImage so orca serve's slot works offline Linux packaging places the pinned glibc Node at resources/node-runtime/bin/node before the glibc-floor gate, proves it serves one PTY with the packaged daemon entry, and deb/rpm exclude it through fpm. orca serve's orcad slot copies that Node (hash-checked) instead of downloading. * test(serve): type the bundled Node fixture --------- Co-authored-by: m4air <m4air@Mac.localdomain> |
||
|
|
e2751d4318 |
After a restart, an agent tab asks the terminal host what is running, never "Couldn't confirm the agent started" (#28043)
* fix(agent-launch): after a restart a launch pane is an ordinary pane, never "couldn't confirm" A launch pane's spawn read the launch record (and the tab's saved outcome) when no launch was running for it, so a row left pending by a quit showed "Couldn't confirm the agent started." The record never observes the process; the terminal host does. With no launch running in this process the pane now spawns exactly like any tab: a live agent reattaches, an exited or absent one gives a fresh shell, and a gone daemon shows the normal unavailable state. - the running launch tells its pane how it ended in memory (finish carries the verdict); only a definite failure says "couldn't start", an unknown outcome leaves an ordinary pane - deleted: agentLaunchPaneVerdictFromRecord, listAgentSessionOperationRowsOwningPane, the `unconfirmed` verdict and its notice string, the tab's persisted launch pane, ownedPane writes - rows written by an older build that carry ownedPane are ignored; they expire with the row * test(agent-launch): the restart pane test joins the attach suite it shares mocks with * fix(agent-launch): a launch pane whose agent couldn't start keeps its notice for the session and never spawns a shell under it; a reload saves only a pending launch * test(agent-launch): drop assertions on code that is gone, and the taking-back case the IPC suite already pins * fix(agent-launch): narrow the finished-pane check, and test the saved session's launch pane with a typed tab * fix(agent-launch): a launch whose outcome is unknown still says "Couldn't confirm the agent started" for the session, and attaches its agent if it turns up * fix(agent-launch): main keeps a failed or unknown launch's answer for its pane until it quits, so the pane never spawns a shell under its notice; the window's spawn gate goes * fix(agent-launch): a failure kept for a pane goes when the pane is closed during its launch, moved to another tab, or its workspace is removed; comments say what an unknown outcome and a reload now do |
||
|
|
8e4e7be869 |
feat(daemon): pace automatic terminal restores on the desktop too (#28202)
* feat(daemon): pace automatic terminal restores on the desktop too The desktop daemon now enforces the restore budget that orca serve and orcad already enforce: the visible pane first, at most 3 automatic restores at once (1/s), at most 8 live per machine, and the rest stay sleeping until opened. ORCA_TERMINAL_SPAWN_ADMISSION=observe rolls newly launched daemons back to count-only. * fix(daemon): never put a user's own resume back to sleep; keep every refused old-client restore A user-resume past the per-client allowance is still paced as automatic work but is no longer deferred for a pause, the live cap or its deadline, so a visible pane is never put back to sleep in front of the user. The legacy restore record holds as many refusals as the host could run. --------- Co-authored-by: m4air <m4air@Mac.localdomain> |
||
|
|
687190e9f1 |
fix(ssh): let existing profiles migrate without losing client history (#28141)
* fix(ssh): migrate released profiles with retained client state * fix(ssh): reuse the shared adoption rule when the local and host copies overlap The host copy replaces the older local one only where it holds tabs, the catalog places the workspace on that host, no other host claims it, no worker fence or close record disagrees, and local drafts are kept. Elsewhere both copies export and must agree, as on main. --------- Co-authored-by: m4air <m4air@Mac.localdomain> |
||
|
|
f0ccfcc0d3 |
fix(native-chat): seed the chat permission default from Claude's and Codex's own arguments (#28100)
* fix(native-chat): seed the chat permission default from the Yolo/Manual toggle as it shows The one-time copy into Settings -> Chat -> Permissions gave Full access only when every agent's arguments were exactly the bypass flag, so a profile the toggle shows as Yolo (agents set differently, or an extra flag) got Ask. Follow the toggle: Manual seeds Ask, anything it shows as Yolo seeds Full access. * fix(native-chat): seed the chat permission default from Claude's and Codex's own arguments The Yolo/Manual toggle has no saved value: it summarises every agent's arguments and shows Yolo for any mix, including after Manual when another agent keeps custom arguments. Seeding from it would grant Full access to people who chose Manual. Read what Claude and Codex chats checked before they had modes instead: Full access when both launch with their bypass flag, otherwise Ask. |
||
|
|
8be17ddf3a |
feat(chat): host writes one saved row per subagent (#27947)
* feat(chat): read one saved row per subagent Adds the singular `subagent` journal block (one row per child, revised in place, with a plain-text twin for older clients) and teaches every reader to draw it: desktop and phone fold a turn's sibling rows into the existing "Ran N subagents" row, the client and off-page rosters name children from their own rows, the reopen pass settles a row a dead host left working, and the phone and worker-transcript wires bound it. No producer writes the block yet; that switch lands in the follow-up. * feat(chat): host writes one saved row per subagent Claude, Codex and Grok now only report child-work evidence. The host writes each subagent's row from its child record, so the chat row, strip and sidebar read one lifecycle. Deletes the shared subagent tracker, the per-agent rosters and their caps. An accepted Stop settles only the run it was sent to, provisionally, until that run reports its own ending. Work the host store has no room to record keeps the running-tasks strip and its Stop instead of reading as idle. * fix(chat): open subagent background flag, separate nested group ids, page window skips subagent rows - background is an open boolean so a later build's value never fails a chat load - a helper's own helpers fold under a groupId of their own, so the phone opens them apart - each subagent's own row rides along with the page window instead of counting as conversation * fix(chat): subagent row writer reads the live turn once, stop acks survive a failed batch - the row writer reads the journal's live turn at most once per change and only for a subagent with no saved, queued or in-flight row; a revision keeps its first scope - a background stop names each accepted task as its answer lands, so a stop-all batch that times out or fails part way still ends the runs it was answered for (Claude and Grok) - the strip's untargeted Stop rule lives in the shared view (offersStopAll); both strips inline it again; incomplete tracking no longer holds commands or rewind - clients admit a frame whose only change is incomplete tracking - drop the Claude stop's dead publish; the real-CLI stop test asserts the run's own ending - list the writer test with the real SQLite tests * fix(chat): a late Grok report never revives an ended helper; Codex keeps announced helpers - Grok: once a helper's run ended, a later progress or repeated spawn sends no live evidence, so its record and saved row stay ended after the next send retires the record; the test rig now retires finished records on each turn open, as the host does - Codex: past the bound, only a child that ran and ended is evicted, so an announced helper that has not started keeps its record, row, Stop and parent - cut: Codex parentTurnId (written, never read), the Claude identities' turn-end forwarding (call the corrections directly), the duplicate Grok outcome map - update stale comments that described the deleted producers * test(chat): Claude frames to saved row, kept Codex path and usage rules, agent ingestion exemption - a captured background Claude helper keeps its saved row working past its spawn result and the parent turn's end, through the real adapter, host and child records, until its own end - restore focused tests for the Codex root path, label trim and placeholder, usage before announcement and parent usage - the per-session live limit test uses tasks: subagents are exempt from it * fix(chat): incomplete tracking follows the refused children, and the strip's Stop reaches them - the host remembers which children the store refused for room, by handle; each leaves when a later report of it is admitted or it reports its own end, so incomplete clears on a truthful signal; the provider session's end still clears all, and past 512 a flag holds until then - a parent row the store refused counts every live child it reports as refused - with work no record lists, an untargeted strip Stop while no turn runs ends the agent through the idle sweep's stop; under a running turn it leaves the agent alone - the background Stop moves into its own module * fix(chat): the incomplete Stop is offered for every agent and decides inside its step - the strip offers the untargeted Stop for work no row lists whenever no turn runs, whatever untargeted stop the provider has (ACP agents, Codex before it proves terminal stops), since the host reaches it by ending the agent; never under a running turn - the Stop reads whether work is unlisted and whether a turn runs inside its own step, after the listed stops land, so a wake-up turn opened meanwhile is never ended - Codex executions past the bound evict the oldest ended child, else the oldest never started - test the writer's unnamed-subagent label; qualify the writer's retry comment * refactor(chat): the subagent row writer joins the views with their records Drops the shared host-only row projection and its hook-server read: the writer's wiring reads the views every surface reads and the records they come from, and takes residency and the ending's basis from the record. * refactor(chat): Grok subagent evidence reads its ending through the one outcome map * refactor(chat): a subagent's group entry as one object * fix(chat): a background Stop with nothing listed asks the agent for no task stops So an agent with no stop route (an ACP agent the probe found none for) never refuses before the Stop ends the work no row lists. Tests: the unlisted case is untargeted and reads unlisted work inside the step; the repeat-press rig lists the task its Stops reach; reflow the writer header. * test(chat): a nested helper's saved parent; describe how incomplete tracking clears * fix(chat): a closed session claims no Stop; a helper's row follows its own running work - a session close Orca proves is loss of contact, not a Stop the CLI accepted: Claude no longer ends what still ran as stopped, so the host settles it unknown and its row reads unverifiable, as before this stack; stop-acknowledged comes only from a Stop the CLI accepted - a helper that finished while work it started still runs is written working, by the strip's own rule; once that work ends, or the session ends, it reads its own ending again - the CLI starting a completed helper again under the same spawn call is the helper waking when its work ends: the decoder reports it as a restart, so its one row revises in place and ends with its real duration; after any other ending such a start is still read as late - tests from a captured 2.1.280 frame order; item 4 Stops pin their stop-acknowledged basis * refactor(chat): drop the unused Stop basis on a child's own ending Only the acknowledged-Stop edge writes that basis now; reflow the decoder header. * fix(chat): an older history page keeps a turn's subagent ordinals and row A page now reaches back to the first sibling of each subagent group it holds part of, at most one more page of conversation, so loading the older page no longer renumbers same-named helpers or moves (and closes) their folded row. |
||
|
|
bcad291f35 |
fix(orcad): identify instances by kernel start ticks, not drifting wall-clock start time (#28125)
* fix(orcad): identify instances by kernel start ticks, not drifting wall-clock start time On Linux the start time is derived from /proc/stat btime, which drifts (WSL2, NTP), so a live orcad was read as a reused PID and its managed stop reported exited without ever writing the request. The lock and stop request now carry optional linuxStartTicks + bootId; readers prefer them, and a wall-clock mismatch on Linux for records without them is unverifiable, never proof of exit. Co-Authored-By: Claude <noreply@anthropic.com> * fix(orcad): match stop receipts without the incarnation an older client drops A request recovered by an older client loses linuxStartTicks/bootId, which made the newer host reject its own receipts and decision records as mismatched. Also exercise the incarnation reader against a real Linux /proc. --------- Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
1104402f5e |
fix(browser): annotation card and outline follow the picked element (#27875)
* fix(browser): annotation card and outline follow the selected element The grab overlay keeps re-measuring the picked element after the click (scroll in any box, resize, element size changes), and the isolated viewport bridge reports that live box so the pending card follows it, including page zoom. The card's side pick uses its measured height. * fix(browser): bind annotation card to selected element * fix(browser): keep document annotation card on selection * fix(ci): point the main sync-spawn ratchet at the shared ratchet library (#28054) #27756's script imported baseline helpers from check-focus-read-ratchet.mjs after #27788 had moved them into ratchet-baseline.mjs, so it crashed on start and failed every PR that merges main. Use the library's helpers, and record the two daemon-process launches from #27769 that the broken check never saw. Co-authored-by: Orca Worker <orca-worker@localhost> * fix(browser): follow selected element outer-box size * refactor(chat): give the start-refusal resumable table its own module (#28085) #27946 and #26684 together put agent-session-failure-words.ts at 304 lines, over max-lines, which fails lint and the pre-commit hook on every merge of main. The refusal-code table is standalone with one reader, so it moves out. Co-authored-by: Orca Worker <orca-worker@localhost> --------- Co-authored-by: OrcaWin <alpha-eng@stably.ai> Co-authored-by: Orca Worker <orca-worker@localhost> |
||
|
|
a1f8e6482b |
fix(layout): save terminal rows in the tab groups' order (#28066)
* fix(layout): save terminal rows in the tab groups' order Co-Authored-By: Claude <noreply@anthropic.com> * test(e2e): read the paired retention heap after two collections Co-Authored-By: Claude <noreply@anthropic.com> * test(e2e): fail the paired retention check when the heap cannot be read Co-Authored-By: Claude <noreply@anthropic.com> * fix(layout): name the headless flow behind the row-id group fallback Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
2233fd3669 |
feat(native-chat): each Claude/Codex chat gets its own permission mode (#26116)
Each native Claude and Codex chat now has its own permission mode (Ask for approval, Accept edits for Claude, Approve for me, Full access), shown and changed from a button right after "+" in the composer. A change takes effect before the next message: the agent restarts quietly and resumes the same conversation, never interrupting a reply or background work. New chats start from Settings → Chat → Permissions, which is seeded once from Agent Permissions. The mode is stored as an ordinary chat option, and the restart reuses the existing "replace the agent before the next message" step. |
||
|
|
6440d3c520 |
fix(mobile): keep a not-sent message in place on the phone, using the desktop's logic (#27936)
* fix(native-chat): list not-sent rows at their journal places The shared projection appended rows the host recorded but never delivered after every other row, leaving the reader to sort them. A reader that draws the list as given showed an old not-sent message under newer turns. Merge them in at their journal places, after the stop rows are built, so the list is already in order. The desktop sorts by the same order, so what it draws is unchanged. * refactor(native-chat): move the recorded-rejection line into shared words The desktop's words for a message the host recorded and then rejected now live beside the other rejection words, returning notice parts each client renders its own way, so the phone can say the same thing. The desktop calls it with the same inputs; its words are unchanged. * refactor(native-chat): one projection behaviour for rejected rows Every reader now draws a message the host recorded and then rejected where it was sent, marked not sent, so the rejectedInPlace option goes. The phone was its last off caller apart from the host's conversation outline, which now leaves not-sent rows out itself; a test proves the outline is the same as if the rejected message had never been sent. * fix(mobile): show a not-sent message in place with the reason under it The phone now draws a message the host recorded and then rejected where it was sent, as the desktop does, with one small line under the bubble in the desktop's words. The line is built from the journal the view already receives; when a start-failure or sign-in row already gives the reason, it says only that the message was not sent. The load-earlier header moves to its own file to keep the view under its line limit. * fix(mobile): a send the host recorded is never handed back When the send's own reply said the host recorded the message and then rejected it, the phone put the text back in the composer with a banner, though the host and the desktop keep it in the chat. A recorded send now settles like any other: its row draws it in place as not sent (or its kept card holds it), and the composer stays empty. The banner's failure-fact plumbing, fed only by that branch, goes with it; the transcript states the sign-in guidance once. * fix(mobile): keep each structured send on the shared optimistic list Once a not-sent row stays in the chat, matching a pending bubble by its text breaks: resending the same words after a not-sent message never reached the count the bubble waited for, so a stray copy stayed, and an acknowledgement lost on that resend showed a false "Delivery unconfirmed". A structured send is now one record, kept by the structured session under the id it is sent with. Three parts match the desktop's sender: the id, the drawing (the shared projection draws it at the tail until the host's row under that id lands, in the same pass) and the settle rule (the host's own record of it, a submission, a card, or a card's hand-off, drops it by the one rule the desktop now shares). The rest stays the phone's own: the bubble shows once the host accepts the send, not from the tap; a send whose answer was lost is held undrawn and says "Delivery unconfirmed" only if its deadline passes first, instead of going back to the composer; records are kept whether or not a chat view is open; and one send in flight is the composer's rule, not the sender's. Its photo is kept under the row id it shares with the host's row, so it shows on the bubble, the landed row and a not-sent row. The drafts' text matching, with its older queued-card matching, now serves only terminal-backed chats, whose transcripts carry no ids. * fix(native-chat): merge not-sent rows by the transcript order alone The merge kept a rule for rows with no journal place that no caller reaches and that the desktop's sort does not have; drop it, and say what merging after the stop rows actually keeps. A test pins that the list the phone draws as given is already in the desktop's sorted order, with a moved stop row, a retry run, a cut-turn notice, a held card and two not-sent rows. * fix(mobile): draw the not-sent line as the desktop does Red and capped to the bubble's width, like the desktop's line and the phone's own returned-card caption, and only when there is a notice: the row had a second, unreachable source of words. A send a Stop took back is no failure, so it no longer rebuilds the notices on every streaming frame. |
||
|
|
e83e40e258 |
Session History shows OpenCode, Grok, OMP and Pi chats as chats and refuses a second terminal writer (#27183)
* refactor(chat): answer transcript adoption and Session History from the agent registration Claude and Codex's transcript importers and Session History ownership (row id, resume-command grammar) move onto their runtime registrations as required-nullable fields; every other agent writes null. agentSession.agents publishes the derived transcriptAdoption/sessionHistory flags, and a client reads a host that omits them (or lists nothing) as the old build's answer. Strict parity: only Claude and Codex answer yes anywhere. * refactor(chat): one Session History ownership index, binaries from row agents, fork offer from fork argv Review round 1: the duplicate-adoption check and Session History ownership read one index keyed by the registration's row id (an importer now requires sessionHistory); resume-command binaries derive from rowAgents through the TUI agent config; the Resume in New CLI offer asks getAgentForkArgv instead of naming Claude and Codex. Strict parity: only Claude and Codex answer yes. * feat(chat): Session History owns OpenCode, Grok, OMP and Pi native chats An OpenCode, Grok, OMP or Pi native chat's conversation listed in Session History as a plain terminal row, and resuming it ran the agent's CLI as a second writer to the conversation the chat was still using. Each agent's registration now fills `sessionHistory`, so those rows show as the chat (its name, opening the chat) and Orca refuses a terminal resume of an owned conversation, as it already did for Claude and Codex. - OpenCode lists under `opencode` and `opencode2` (2.x) rows by its ses_ id; `-s/--session`, `-c/--continue` resume, `--fork` does not. - Grok: `-r/--resume/--load` and `-c`; a non-UUID value names a title, so it is read as target-less; `--fork-session` does not resume. - OMP: `-r/--resume/--session` take an id, any prefix of it, or the session file's path; bare `--resume` and `-c` are target-less. - Pi: rows are listed by the session file name's id while the chat holds the file's path; `--session <id|prefix|path>`, `-c`, `-r` (picker). - An optional `resumeTargetMatches` on the registration covers prefix and path targets; it defaults to exact equality. - Each agent now reads every token naming its own binary, so another agent's name earlier in a command (`claude --model pi`, `cd ~/src/codex && claude --resume X`) no longer hides a resume. For Claude and Codex this only ever refuses more than before. - A renamed OpenCode chat tab names its `opencode2` row too. * fix(chat): show owned rows only to clients that open the chat, without a resume command - The list and search projection now asks, per owning agent, whether the requesting client opens that agent's chat. Current mobile renders only Claude and Codex chats, so it no longer receives OpenCode/Grok/OMP/Pi rows owned by a chat (a tap there opened an empty terminal and showed agent_session_conflict). Claude and Codex keep their rule: any client that reads structured sessions. - An owned search hit opens its chat, so it no longer carries a terminal resume command (all agents; `orca` CLI search stopped printing one for a chat). - OpenCode/Grok/OMP/Pi flags match case-sensitively, as their CLIs read them, so `rg foo src/pi -C 3` is not `pi -c`. Claude and Codex still fold case. - A test feeds every resume command Orca builds for an owned row of each agent (Session History, live-tab argv; posix, PowerShell, cmd) into the guard. * fix(chat): keep the resume guard linear and skip chat-tab lookups with no move - Each agent binary mention in a command now reads its arguments only up to that agent's next mention, which reads the rest, so every token is parsed once per agent. A 200 KB text naming agents throughout took 9.4 s on the host's main process; it now takes about 90 ms. Every refusal before still holds: a later mention still reads its own resume. - Right-clicking a native chat tab looks up its Session History row only when the agent's CLI can fork a conversation, the one move a chat tab offers, so OpenCode, Grok, OMP and Pi chat tabs run no list or rescan for nothing. Claude and Codex are unchanged. * fix(chat): read each shell command once in the resume guard The guard tokenizes the text once and splits it into shell commands at &&, ||, ;, |, & and newlines (a newline after a line-continuation mark stays inside its command). In each command, each agent reads its arguments from its first binary token to the command's end; a later token naming it is an argument. A `claude` path inside a Claude command no longer ends that command, so `--session-id <owned>` still cancels a fork after it, and a binary-named path between a resume and its fork flag keeps the fork a fork. Binary names are looked up once per token, which keeps a 16 MB send to about 2 s. Against main's parser, a Claude/Codex command is now allowed only when main refused it for a flag in a later shell command (`claude ; echo --resume X`). * fix(chat): end a resume-guard command at a bare carriage return A PTY's Enter, and many orca terminal send texts, end lines with \r. The guard now treats \r and \r\n as line breaks like \n, including after a line continuation mark, so a later line's resume is read as its own command. * refactor(chat): drop the transcript-adoption and Session History wire flags agentSession.agents no longer publishes transcriptAdoption or sessionHistory, so there is no new field for older and newer peers to reconcile. Their only consumers were renderer gates that could answer from what they already have: "Resume in New Native Chat" eligibility goes back to main's rule (adopting a CLI conversation is Claude/Codex-only, and the host still refuses any other agent from its registration's transcriptImport). Removes the AgentSessionDeclaredCapabilities split, the older-host decoder shim and the generic host-flag reader. * refactor(chat): keep main's Claude/Codex rule for the CLI fork offer "Resume in New CLI" and the chat-tab menu read the agent name again instead of building a synthetic provider session to ask the fork builder, and the chat-tab lookup loses its host Session History flag gate. Only Claude and Codex CLIs fork, so only their chat tabs look up a row, exactly as on main: no new list requests. * refactor(chat): one registration-aware ownership index, no registration union - listStructuredProviderSessionOwnership reads each record's registration for its Session History row id; the callback parameter and its one-function wrapper module are gone (no import cycle: checked value and type imports). - StructuredAgentCliConversations is a plain type: transcriptImport is nullable and sessionHistory is required, since every registered agent now owns its rows. The "importer needs history" union and its null checks go. - A test pins that no two registrations claim the same Session History row agent, since the owner lookup takes the first match. * fix(chat): read resume commands into words as a shell does Separators glued to a word (`opencode -s id; echo`, `cd /x&&claude -r id`) now end the command, and quotes are removed inside a word, so `--session="id"` reads as the id. Applies to every agent, Claude and Codex included. The acp-and-pi allowed() helper now asserts. * fix(chat): count an agent's name only where a shell would run it terminal.send checks agent prompts too, and agentPrompt does not keep the text from a shell (it falls back to a plain PTY write), so prompt text like "Fix the Pi --resume flag" was refused. The resume check now reads each command's program: its first word past VAR=x prefixes, a path, and env/exec/command/sudo/npx/bunx/pnpm dlx. Every command Orca's builders produce, Claude and Codex included and with a user's command override, is still refused. * refactor(chat): compare resume ids in any case; Claude uses the shared option reader The default target match now ignores case (ids are UUIDs or ses_ ids), so Grok's override, which only repeated it, is gone. Claude folds its flags and reads them with resumeInvocationFromOptions instead of repeating the `=` handling. * perf(chat): read terminal text in one linear pass A repeated regex group overflowed the stack on a 16 MB word. A sticky-regex scanner reads words, quoted runs and separators in one pass; an unclosed quote is searched for once per quote kind. * refactor(chat): read a command's resume only when its program is an agent * fix(chat): read an agent run under nohup, time or wsl as the command's program * chore: restore pnpm-lock.yaml * fix(chat): keep the CLI's own title on OpenCode, Grok, OMP and Pi chat rows Session History shows these agents' chat conversations under the title their CLI saved, as it does for their terminal conversations; the chat tab's name and renames stay on the tab. Claude and Codex rows keep the chat's name, as on main. * feat(chat): Resume in New CLI for Grok and Pi chats A Grok or Pi chat's Session History row and tab menu now offer "Resume in New CLI", like Claude and Codex: the CLI opens a copy of the conversation (grok --resume <id> --fork-session, pi --fork <file>) and the chat keeps its own. OpenCode's interactive CLI and OMP have no way to open a copy, so their chats keep only Resume. * fix(chat): offer Resume in New CLI only when the copy command can be built A Pi chat found by search on a remote Orca server has no session file path, so its copy command can't be built; the row no longer offers the button there. * fix(chat): refuse pi --session-id of a conversation a chat owns `pi --session-id <id>` reopens that session when it exists, so it is a second writer like --session. |
||
|
|
9c1d5d2fb7 |
perf(pty): check WSL working directories off the main thread (#27705)
* fix(wsl): start host-side wsl.exe probes through process-host so they never open a console wsl --list, wsl --status, the WSL home and directory probes, and WSL UNC deletes were started with node:child_process directly. The synchronous ones omitted windowsHide, so each spawn from the GUI-subsystem main process flashed a conhost window that could take keyboard focus. Route them through @orca/process-host, which pins windowsHide, and keep the execFile-shaped exit errors the availability cache classifies. Refs #27390 * build(tc): list wsl-exe-process.ts in the web typecheck project wsl-availability.ts is in the explicit web file list and now imports it. * refactor(wsl): drop the unused sync availability probe; classify ProcessResult directly No production code calls the sync isWslAvailable (the IPC and RPC paths already assert they never do), so delete it and its sync guest-probe twin instead of porting them. The async probe now reads the --status ProcessResult itself: an exit code is definitive, a timeout or signal kill is retryable, ENOENT on spawn is definitive. The WSL2-missing-kernel exit codes live in one named set. Availability tests now drive the async probe. * refactor(wsl): shrink wsl-exe-process to a spec builder and stdout reader With the availability cache classifying ProcessResult itself, nothing reads the execFile-shaped code/status error any more. wslExeFailure returns a plain reason string; wslExeStdout throws it; the UNC delete folds it into its own error. The home probe builds its argv with buildWslExecArgs. * perf(pty): probe WSL working directories off the main thread The in-process terminal launch path validated a \wsl.localhost cwd with a synchronous wsl.exe probe, blocking the main process ~70 ms per launch (300-530 ms when the distro is stopped, up to the 5 s timeout). The launch now awaits the existing async probe and the synchronous one is removed. Refs #20497 * fix(pty): let a canceled spawn abandon its pending WSL cwd probe The local spawn awaited the WSL directory probe without its cancellation signal, so a pane closed mid-probe waited up to the 5s wsl.exe timeout before noticing. Pass the signal through validateWorkingDirectoryAsync and report an abandoned probe as the spawn's own cancellation, as the daemon spawn preflight does. * test(wsl): mock process-host in the empty running-list case from main * docs(wsl): the availability probe no longer blocks; drop the stale word * build(ratchet): prune the wsl.exe sync launches this branch removed * build(ratchet): prune the sync WSL cwd probe this branch moved off the main thread --------- Co-authored-by: Orca Worker <orca-worker@localhost> |
||
|
|
50cc50d33e |
refactor(chat): give the start-refusal resumable table its own module (#28085)
#27946 and #26684 together put agent-session-failure-words.ts at 304 lines, over max-lines, which fails lint and the pre-commit hook on every merge of main. The refusal-code table is standalone with one reader, so it moves out. Co-authored-by: Orca Worker <orca-worker@localhost> |
||
|
|
c0e2b73395 |
feat(native-chat): require Codex 0.136.0 for chats (#26684)
* Require Codex 0.136.0 for native chats
* Add host-owned Codex install and update actions
* Bind Codex checks and maintenance to the selected installation
* Resolve Windows updater cleanup through system helpers
* Use one child environment for Codex checks and launch
* Expire Codex readiness and reconcile current host jobs
* Resolve Codex checks against the child command search path
* Restrict global npm repair to the selected Codex install layout
* Pin the host updater directory through Windows supervision
* Synchronize Codex maintenance fixtures on readiness
* Unify Codex notices and recover version-refused chats
* Route Codex recovery to the operation that remains
* Preserve Codex setup execution errors in the job log
* Preserve UTF-8 in Codex setup logs
* Remove unused Codex host installation detector
* Require host administration for Codex maintenance
* Show a missing Codex explanation once
* test(native-chat): give scripted Codex runtimes their installation fact; keep the registry out of the relay
Runtime tests that drive a scripted Codex app-server still ran the new
version check against whatever codex was on the machine's PATH: refused
on CI (no codex), and on a dev machine it ran the real codex --version.
A scripted transport now must state the Codex it stands for
(readCodexInstallation), asserted at install, and the tests get it from
one scriptedCodexTransport fixture.
The relay's Codex maintenance path reached @orca/windows-registry through
resolvePathEnvKey's re-export in windows-environment-path; import it from
its registry-free module instead.
Also update the catalog-probe test for the shared child environment.
* refactor(native-chat): drop the Update Codex action; keep Install for a missing Codex
A too-old Codex now gets only the plain message ("Codex X is too old for
chats. Update to 0.136.0 or newer.") with no button and no job. The
existing rechecks (window focus, the <=30 s expiry of a saved result)
retry the refused chat once the user has updated Codex themselves.
A missing Codex keeps the Install Codex button: `npm install -g
@openai/codex` with the npm on the chat's PATH, as a host-run job with
its log, then a recheck and recovery. A configured Command that is
missing gets the message only, since a global npm install would not
land at that path.
Removed: the `codex update` command and its 0.126 floor, manual
instructions, npm layout and launcher identity detection, the action
descriptor (install|update) and its titles, the legacy capability, and
the SSH relay maintenance path (structured chat never runs over SSH or
WSL), so the relay preflight handler is back to main's.
* refactor(native-chat): keep Install Codex in Settings only
When Codex isn't installed, main already hides Codex from the New tab menu,
so the in-chat Install button was almost never reachable. Settings → Agents
already shows "Codex isn't installed." with Install Codex.
- Chats and the new-workspace composer now only state "Codex isn't
installed." (no button, no install job from the chat).
- The Install action, its busy spinner and the host-busy subscription move
into the Settings Codex row, the only place that starts the job.
- The store no longer re-checks every other workspace context when a job
finishes. A refused chat recovers through the same re-check a too-old
Codex uses (window focus, or the <=30 s expiry of a saved result).
- The chat recovery key no longer includes the install job id.
- Tests: Settings drives the install; a new test proves a chat refused for
a missing Codex recovers through focus after a Settings install, not
through the job; the expiry recovery test covers missing and too old.
* refactor(codex): check chat compatibility when acquiring the child
* Restore workspace submit guard and simplify Codex maintenance
* Keep only the Codex chat minimum version
|
||
|
|
6af34a4d99 |
fix(chat): name the agent when it refuses a message (#27946)
* refactor: move provider-retry pieces into a sibling words file Pure move of the six provider-retry translation pieces out of agent-session-failure-words-text.ts, which sits at the 300-line limit. No key or behaviour change. Co-Authored-By: Claude <noreply@anthropic.com> * fix(chat): name the agent when it refuses a message "The provider did not accept this message" becomes "{{agent}} didn't accept this message" ("The agent" when the name is unknown), in English and every locale. The providerRejected failure kind is unchanged. Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
fe1f0223af |
Native chat: read one saved row per subagent (part 1 of 2) (#27914)
* feat(chat): read one saved row per subagent Adds the singular `subagent` journal block (one row per child, revised in place, with a plain-text twin for older clients) and teaches every reader to draw it: desktop and phone fold a turn's sibling rows into the existing "Ran N subagents" row, the client and off-page rosters name children from their own rows, the reopen pass settles a row a dead host left working, and the phone and worker-transcript wires bound it. No producer writes the block yet; that switch lands in the follow-up. * fix(chat): open subagent background flag, separate nested group ids, page window skips subagent rows - background is an open boolean so a later build's value never fails a chat load - a helper's own helpers fold under a groupId of their own, so the phone opens them apart - each subagent's own row rides along with the page window instead of counting as conversation * refactor(chat): a subagent's group entry as one object |
||
|
|
fde0fe35a7 |
fix(daemon): stop giving new terminals to a daemon whose AppImage files are gone (#28011)
* fix(daemon): stop giving new terminals to a daemon whose AppImage files are gone On Linux AppImage, a terminal daemon can outlive the mount it runs from and keep answering health checks until it SIGBUSes. Orca now reads the daemon's /proc exe link and mount table at adoption: an idle severed daemon is replaced; a busy one is kept for its sessions but quarantined, so fresh terminals use the local provider and a healthy RPC never re-admits them. Status and a verified restart are available over RPC and as `orca terminal-daemon status|restart`. Co-Authored-By: Claude <noreply@anthropic.com> * fix(daemon): lift the severed quarantine once a new daemon owns the endpoint Co-Authored-By: Claude <noreply@anthropic.com> * fix(daemon): a severed-daemon restart counts fallback terminals and never stops a replacement The restart re-checks the authenticated endpoint owner before any teardown and again at shutdown, and never falls back to a pid kill when targeted. Status counts the local fallback terminals the restart also ends. Co-Authored-By: Claude <noreply@anthropic.com> * chore(daemon): merge duplicate import Co-Authored-By: Claude <noreply@anthropic.com> * fix(runtime): classify the terminalDaemon RPC namespace as host-owned Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: m4air <m4air@Mac.localdomain> Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
dedea9c76c |
fix(updater): clear a cancelled macOS install request before staging the next update (#28010)
* fix(updater): clear a cancelled macOS install request before staging the next update * fix(updater): match only Orca's ShipIt and recheck owners right before clearing --------- Co-authored-by: m4air <m4air@Mac.localdomain> |
||
|
|
7c13365f03 |
refactor(build): relay survivors named for what they are (#27962)
Co-authored-by: m4air <m4air@Mac.localdomain> |
||
|
|
b368ebc7a6 |
refactor(ssh): delete relay-only GC, takeover and install remnants (#27770)
Co-authored-by: m4air <m4air@Mac.localdomain> |
||
|
|
cfbf3610d5 |
fix(linux): keep Wayland clipboard text current (#27772)
Publish Linux Wayland text copies with an independent clipboard owner, then clear Chromium's stale source only after publication succeeds. Use data control where available and GTK's clipboard bridge on GNOME. Keep macOS, Windows and X11 on their existing Electron writers. Temporary workaround for https://github.com/electron/electron/issues/54645, with removal checks documented in the native README. Verified full build, macOS arm64 packaging, Linux x64/arm64 library floors, clipboard/platform tests, real Wayland copy/paste and CI packaging. Fixes #26917 Fixes #23229 |
||
|
|
822bd9f972 |
fix(ssh): keep 1.4.224's running terminals working after the upgrade (legacy relay bridge) (#27787)
* wip: legacy relay bridge client
* Revert "fix(ssh): end an older Orca's relay terminals from the connect refusal (STA-10166) (#27748)"
This reverts commit
|
||
|
|
627716fdc1 |
perf(ports): stop leaving stuck lsof processes behind on a dead network mount (#27729)
* perf(ports): run lsof without blocking on dead network mounts Without -b, lsof stat()s every mounted file system before answering. A hard-mounted network share that stops responding blocks that call in the kernel, so each port scan left an unkillable lsof behind. Pass -b -w to the lsof queries that name no file (port list, process cwd, executable path); their output is unchanged. The -U and +D queries keep stat because -b makes lsof reject named-file arguments. Linear: STA-9017, STA-9716 * perf(ports): name the stuck lsof helper in the comment; plain readiness test name * test: query a known child executable in lsof readiness smoke test |
||
|
|
e3ef95dc04 | feat(cli): launch agents in existing workspaces (#27794) | ||
|
|
8a1c63b381 |
refactor(shared): dedupe the local-default connection-to-host conversion and the trim helpers (#27797)
Co-authored-by: m4air <m4air@Mac.localdomain> |
||
|
|
d3182d4bf1 |
Native chat: every agent launches through one composed launch path (#27204)
* test(chat): snapshot every native-chat agent's spawned child as a parity oracle Captures the exact child each native-chat agent spawns (Claude, Codex, Grok, OpenCode, OMP, Pi), worktree and floating, fresh and resumed, plus a registered worker with visuals off, at the real spawn boundary (the process host's spawn) and through production's own host dependency wiring. The inherited environment holds every pane, hook, caller, terminal-handle and visuals key a parent Orca pane could hand down, so the snapshot records today's per-agent differences as they are. Each case also records the launch's permission posture, which for Codex (thread policy) and the ACP agents (full access) acts only after spawn. Each agent also runs fresh and resumed with every launch setting moved off its default: a Command setting naming another binary, the permission posture flipped, an environment overlay over inherited and shell keys, and only named login-shell variables inherited (the fake login shell carries its own PATH and a variable no case names). A second test fails when a registered agent has no snapshot, since the oracle lists its agents by hand. Test only: the launch-path refactor that follows must leave both tests and the snapshot file unchanged. * refactor(chat): one composed launch for every native-chat agent Every native-chat agent (Claude, Codex, Grok, OpenCode, OMP, Pi) now builds its child through one composer instead of six hand-written resolvers. Each agent contributes only its own part (command, args, agent env, keys it must strip); the shared composer admits the session record, reads the launch directory, base environment, launch overrides and visuals once each (memoized, in the agent's original order), sets cwd, and seals the child. The seal (sealStructuredSessionChild) is the one place that names a child: session id, structured marker, worker terminal handle, the Orca CLI env, the spawn token under one shared constant, and the per-launch list of inherited keys to delete, minus any key the seal itself sets. Claude's delete list now reaches its SDK spawn after buildClaudeChildProcessEnv's merge. Behaviour is unchanged: the parity snapshot from the previous commit passes byte for byte, including each agent's existing differences (Claude and Codex keep inherited pane and hook env, Pi has no visuals and always full access, the ACP history read stays unsealed). Admission refusals keep their reasons; only log-only wording moved to the shared check. Drops launch dependencies no production caller wires (resolveLaunchEnv, resolveCodexOverrides) and folds the per-agent env overlay hooks into the existing resolveAgentLaunchEnv. * refactor(chat): only the seal names a native-chat child Follow-ups from review of the composed launch: - The spawn token is required on every resolveLaunch. The one read that starts no child, ACP's restart-recovery history read, now has its own path (composeStructuredLaunchRead, deps.readLaunch) that returns the agent's own env unsealed, as before. Agent parts receive the session identity only, never the token. - structuredSessionChildIdentityEnv is gone; the seal is the only way to build a child's identity env, and tests go through it. - A launch's environment is the runtime's resolveAgentEnvironment, so the base-plus-overlay rule exists once. - Claude's connection test now covers a non-empty envToDelete applied after the inherited env is merged. No change to any spawned child: the parity snapshot is unchanged. * ci: run the login-shell contract job for the renamed child-env seal The seal moved from structured-session-child-identity-env to structured-session-child-env, but the PR job classifier still listed the old path, so a change to only the seal would skip the live login-shell contract job. The prefix now covers the seal, its test and its test support (which the login-shell harness imports). |
||
|
|
5035baac50 |
Edit agent launch settings on the workspace host (#26479)
* Add host-owned agent launch settings mutations * Edit agent launch defaults on the selected execution host * Await durable writes for host agent launch settings * Fence agent detection by execution-host pairing * Acknowledge host settings before hook reconciliation * Preserve Reset after pending agent launch saves * Simplify agent detection re-pair handling to the clear and the transport fence A re-pair already clears every detected-agent list, flag and pending probe for the environment, so the per-record pairing owner keys, the owner-keyed pending probes, the settle-time pairing checks and the hook's ownership gating were never reached. Detection now captures the pairing before the capability wait and fences its RPCs with it; the refresh fallback still refuses after a re-pair. Pins the fence across a capability wait, the needs-update clear, and the agent-launch RPC permission levels. * Open launch settings on the Active Server without the focus-routing helper Main's new owner-routing ratchet counts getActiveRuntimeTarget calls, and this pane's no-workspace default added one. It now reads the Active Server field the way the Agents pane did before host selection, so the default is unchanged. * Re-probe mounted launch surfaces after a re-pair; rely on the transport fence for the refresh fallback A re-pair clears an environment's detected-agent lists, but a mounted tab bar, quick launch or composer that was still waiting for its first list (or whose first probe had failed) never asked again, leaving "Can't reach this workspace's host" until another surface mounted. The hook now re-runs its effect when the target environment's pairing revision changes. The refresh fallback no longer re-checks the pairing in the renderer: it already carries the pairing captured before the wait, and main refuses any mismatch. Pins the Settings host form's remount key on re-pair. |
||
|
|
d4a55f583e |
fix(ssh): end an older Orca's relay terminals from the connect refusal (STA-10166) (#27748)
* fix(ssh): end an older Orca's relay terminals from the refusal, and name the command * test: type confirmation mocks * fix(ssh): match old relay daemons by argv position; owner-check the Windows command * fix(ssh): select Windows relay daemons by argv position in the manual command * fix(ssh): keep the relay daemon marker local so census mocks stay intact --------- Co-authored-by: m4air <m4air@Mac.localdomain> |
||
|
|
30660086f2 |
test(launch): pin what the "+" launchers produce on main today (#27691)
* test(launch): pin what the "+" launchers and the AI buttons' window route produce on main
Row 1 ("+": menu, shortcut, dashboard) joins the window-tab table on both halves;
saved-arg tokenize failures before routing, the restart of a never-mounted "+" tab
and the AI-button host gate are pinned at the window planner.
* test(launch): cut "+" pins main already holds and check outcomes, not internals
Drop the AI-button and restart tests and the duplicate \\wsl$ folder and macOS
unclosed-quote rows; the Q5 pair now checks that no tab or startup was made.
|
||
|
|
6c3ce444bc |
feat(updater): auto-park registered serve supervisors during a desktop install (#27722)
* feat(updater): auto-park registered serve supervisors during a desktop install * test(serve): drop a type assertion in the bundle park test * fix(updater): stop any server started during an install hold and re-list blockers right before commit --------- Co-authored-by: m4air <m4air@Mac.localdomain> |
||
|
|
78144e91cc |
feat(serve): supervised mode for orca serve --supervise (#27716)
* feat(serve): supervised mode for orca serve --supervise Restart the server with jittered backoff instead of exiting when it dies, with a crash-loop brake whose exhaustion is durable per profile and build, an install hold while the executable is missing or ShipIt replaces the bundle, and standby while another Orca serves the profile. Only an explicit --supervise turns it on; Windows refuses it for now. * fix(serve): let a supervised orcad serve wait for a live peer instead of counting it * fix(serve): gate update replacements on the install hold, honor stop during the bundle wait, keep a give-up while the executable is missing * fix(serve): hand a removed executable back to the service manager and key the brake on the app archive --------- Co-authored-by: m4air <m4air@Mac.localdomain> |
||
|
|
1a1a2a165e |
perf(zod): use Zod 4.6 validation and compile ACP updates (#27735)
* perf(zod): compile ACP updates and use Zod 4.6 boolean validation * test(zod): expand regression coverage and move benchmark to PR body |
||
|
|
3a52598cdb |
refactor(ssh): one pinned-runtime host target (#27709)
The relay runtime ladder (rungs A/B/C/legacy/D, its self-test, refusal cache, per-host decision store and its telemetry) lost its only caller when the relay was deleted. Two pieces were still live: picking the Node runtime a host can run by glibc (managed orcad deploy, WSL guest runtime) and the runtime-store pins orcad version GC keeps. Those move to pinned-runtime-host-target.ts; pick/durationBucket move into ssh-host-server-telemetry.ts, their only remaining user. Co-authored-by: m4air <m4air@Mac.localdomain> |
||
|
|
be4cea1584 |
perf(wsl): search Windows-drive projects with host ripgrep (#27690)
* perf(wsl): run ripgrep on the Windows host for drive-path projects A project stored on a Windows drive (C:\...) with the WSL runtime selected ran quick open, file-path search, content search and Markdown listing through wsl.exe and the Linux rg reading /mnt/c over drvfs. rg only reads files, so it has no git index whose stat cache two writers could thrash; the host binary reads NTFS directly. Measured on a 21.6k-file clone on C: (Windows 11, Ubuntu-24.04): content search WSL rg 3.5 s -> host rg.exe 0.19 s file listing WSL rg 0.14 s + ~65 ms wsl.exe spawn -> host 0.03 s Git routing is intentionally unchanged: alternating Windows git and WSL git on the same index forced a full ~250 s rehash on the next WSL git status. WSL UNC roots and non-drive roots keep their existing routing. * fix(search): keep host rg results whole when files only WSL can open exist Windows rg cannot open a 'nul' file, Win32-trimmed names, or WSL-created symlinks; treat those per-file errors as skipped rather than failing or truncating the search, and keep symlink-following scans in WSL. |
||
|
|
d9a3c84b00 |
fix(terminal): don't duplicate terminals a newer Orca still runs after a downgrade (#27577)
* refactor(daemon): share the cold-restore payload builder Lets a reader outside the adapter build the same saved-screen payload from history without spawning. * feat(daemon): hold restored terminals a newer build's daemon may still run (STA-9257) After a downgrade, an older build cannot adopt a newer-protocol daemon, so it cold-restored that daemon's terminals under the same ids and resumed their agents beside the originals. Startup now classifies newer-protocol pid records in this profile's runtime dir as live, unverifiable or exited, and pty:spawn refuses a restore no adopted daemon owns while one is live or unverifiable. Reopen stops the newer daemon by verified signal only and reports success only once its pid is confirmed gone. * feat(terminal): show held restored panes read-only with a Reopen banner (STA-9257) A pane refused for a newer daemon shows its saved screen read-only under a pane-top warning. Reopen terminal stops the newer daemon and releases every held pane through the existing reattach path, then toasts what actually reopened and resumed; an unconfirmed stop reopens nothing and offers Try again. * refactor(daemon): hold newer-daemon restores at the router, one classifier (STA-9257) The hold moves from the pty:spawn IPC handler into DaemonPtyRouter.spawn so every spawn source (IPC, runtime controller, mobile refresh, CLI) refuses a cold restore of an id no adopted daemon owns while a newer daemon is held. Discovery and stop share one classifier; an unidentifiable owner settles by whether its endpoint accepts a connection, so a dead leftover never holds a tab forever. Main announces a confirmed stop to the renderer. * fix(terminal): per-pane failed state, one release path, no repeated screen (STA-9257) A pane held after a failed Reopen shows the held state. Held panes also release on main's stop announcement, with one toast only from the clicking window. A pane whose saved buffer the layout restore already painted skips the history screen and is not cleared on release. * refactor(terminal): drop the release event, await the router hold, one phase per pane (STA-9257) The release announcement only reached the window that clicked, which already releases on its reply, so it is removed. The router hold check is always awaited; three router tests released the adapter spawn before the router had reached it and now wait for it instead. Each held pane carries its own phase. * refactor(terminal): trim the newer-daemon hold, keep paired panes out of it (STA-9257) A paired host's refusal is now an ordinary error: holding it locally looped, since this window cannot stop that host's daemon. The history-screen read is gone (the layout restore already paints saved output), discovery folds into the hold module, the hold store uses zustand, the reattach returns its outcome so a fresh-session fallback counts as reopened, and the toast uses plural keys. * feat(terminal): show a held local pane's saved screen read-only (STA-9257) Local daemon-backed panes keep no layout buffer, so a held pane was blank. Main reads the session's saved screen from history without spawning and the pane paints it read-only, unless the layout restore already painted the pane; it is cleared before the normal reconnect. * fix(daemon): re-probe a timed-out newer-daemon endpoint before holding (STA-9257) * feat(terminal): count held panes in the reopen label, add Dismiss (STA-9257) |
||
|
|
d8ebe75efe |
fix(wsl): restore Windows interop after another distro wipes it (#27682)
* fix(wsl): detect and restore a wiped WSLInterop entry in running distros On WSL < 3.0.1 a graceful stop of any systemd distro deletes every binfmt_misc entry VM-wide, so Windows .exe launches (code ., explorer.exe, the orca WSL CLI bridge) fail with Exec format error in every other distro. Check, never boot: 5 s after a WSL terminal env build (gated on the distro being in the confirmed running list, at most once a minute per distro), and whenever a confirmed running-distro probe shows another distro stopped. Repair is automatic and idempotent as root, skipped when the distro's wsl.conf sets [interop] enabled=false, single-flight per distro, with a 10 minute backoff after a failed repair. * fix(wsl): repair interop with a single register write matching WSL's flags Busybox printf writes a trailing newline separately; the kernel registers the entry, then rejects the lone newline, so the repair reported failure after succeeding. Write without the newline and let the re-check decide. Use WSL's own P flags rather than PF, and treat an entry restored between probe and repair as intact. Add a spawn-budget test. |
||
|
|
a3e97184f4 |
fix(runtime): keep remote servers usable when one saved record can't be read (#27685)
* fix(runtime-environments): tolerate unreadable server records without dropping them One corrupt or newer-format record in orca-environments.json or the environment sidecar made every server unlistable. Each file now parses per record: readable records load, unreadable ones are rewritten verbatim on every write. A server whose sidecar entry cannot be read is hidden and refused for edits, so it never connects or is rewritten without its SSH access or deployment link. * test(runtime-environments): fuzz interleaved writers against unreadable records |
||
|
|
83056e5edb |
fix(skills): judge official skill updates by ownership, not snapshot matching (#27603)
* fix(skills): judge official skill updates by ownership, not snapshot matching Orca decided whether an installed official skill was pristine by matching its bytes against a shipped list of past snapshots, falling back to the skills CLI's lock hash. Copies installed between releases, CRLF copies, and installs whose lock recorded a sha256 hash all read "doesn't match the official version" and could not be updated (#26841), and older content could be labelled newer and never offered. Now an official skill is Orca's when the skills.sh global lock says it came from stablyai/orca, and it is current only when its listed files equal the bundled manifest (line endings and sidecars ignored); anything else is outdated. Install and update are one pinned `skills add .../tree/v<appVersion>` command, used by the in-app runner, `orca skills update`, and the Settings rails. The update dialog preselects Orca-owned rows and lists same-named skills from elsewhere unchecked. Removes the snapshot registry, release mapping, git-tree identity, lock trust and convergence logic, and the release-cut provenance step. * ci(skills): run the pinned-add round trip on a skills CLI that can fetch a commit * fix(skills): let orca skills update target the shared folder on a bare host Update became the same pinned add as install, so it inherited install's refusal when no agent is detected. That broke the bare-host update the old command guaranteed, and the built-CLI check (update --dry-run, no --agent) on CI runners. The in-app runner already fell back to universal; both now share one detectSkillsCliUpdateAgentKeys rule. Also brings the CLI skills docs off `npx skills update`. * refactor(skills): derive the update selection from the grouped rows The selection hook took the live and the retained inventory, re-derived Orca ownership from installations beside the grouping's own orcaOwned, and kept a separate display count. Groups already carry both eligibility and ownership, so the hook reads them once; the button's existing live-scan gate is the only authorization check. The unselected line is inlined into the headline. * refactor(skills): drop helpers the ownership design made redundant - isOwnerManagedSkillScope only negated into the participation check once plugin caches stopped participating; fold it in. - Eligibility re-checked resolvedPath/physicalIdentity, which observation already turns into 'inaccessible'. - The manifest verifier probed access() before a readFile that throws anyway. - orchestration-install-command.ts only forwarded one constant from the lib module its three importers already import. * i18n(skills): stop naming npx skills update in four skipped-copy strings The es/ja/ko/zh cantUpdateReason and tipDuplicate strings still named `npx skills update`, which Orca no longer runs for its skills. Say 'the update command' instead. * refactor(skills): inline the single-use pinned source URL helper orcaSkillsSourceUrl was exported but only called by the builder beside it. * fix(skills): let an update choice lapse when the install source changes The selection froze every checked name, Orca's preselection included, the moment the user ticked anything. A re-check that then attributed a name to another source kept it checked, replacing it without consent. Store only the user's explicit choices, keyed by name and lock source, and default to ownership otherwise. * fix(skills): label the empty update action 'Update skills', not 'Update 0 skills' The button stays whenever a row is updatable, so unticking every row (or a dialog with only non-Orca copies outdated) doesn't reflow the footer. At zero selected it stays disabled and reads 'Update skills' instead of a zero count. The label lives beside the dialog's other count copy. * fix(skills): let keyboard focus open the Not from Orca tooltip The source of a non-Orca copy is only in the tooltip, and its trigger was a plain span. Make it focusable like Orca's other tooltip-on-text triggers (tabIndex 0, cursor-help, the row primitive's focus ring), as in the agreed mock. * test(skills): hold the round-trip add equal to the shared install builder The CI round trip hardcoded its skills add argv, so it could drift from what Orca runs. A .mjs script can't import the TS builder (extensionless imports), so the argv moves into an exported roundTripAddArgs and a unit test asserts it is the builder's unattended add with the tag swapped for the PR commit. The script runs under the same main guard as the manifest generator. * i18n(skills): translate the skill row's location count and duplicate reason es, ko and zh still carried English for SkillUpdateRow.oneLocation and manyLocations, and es and ko for SkillFreshnessRow.skippedReasonDuplicate. * fix(skills): pin official skill installs to the build commit and skills CLI `skills add .../tree/v<version>` only works for stable and RC builds: hourly, daily and adhoc builds publish elsewhere, so stablyai/orca has no tag for their version and both install and update failed there. Every build is made from a commit that exists in stablyai/orca, so the build now stamps `git rev-parse HEAD` and the one shared builder pins to `tree/<commit>`, which equals the bundled skills by construction. Main, renderer, web and vitest get it as ORCA_BUILD_COMMIT (replacing ORCA_APP_VERSION); the tsc-built CLI reads it from the compiled out/package.json boundary. A missing or malformed commit fails the build or the command with a clear error instead of building a bad URL. The builder also pins the skills CLI (`npx skills@1.7.2`): 1.5.x cannot fetch a commit, and a bare `npx skills` can reuse a stale cache or resolve to another binary (#18687). The CI round-trip script reuses the same constant. * ci(skills): run the round trip on the shared pinned skills CLI version * docs(skills): document the commit-pinned skills add * refactor(skills): keep the pinned skills CLI version in the install builder skills-cli-version.ts existed only so the plain-Node round-trip script could strip-load a .ts file, which needed --disable-warning in CI. The version now lives beside the builder that uses it; the script keeps a literal, and the existing round-trip test already asserts its argv, skills@<version> included, equals the builder's. * refactor(skills): validate the build commit once, where it is resolved resolveBuildSourceCommit already refuses anything but a full hash, and both the define and the CLI's stamped package.json come from it. The builder's second check could only fire as a module-load throw in the renderer's command constants. Also drops the resolver's unused cwd parameter. * test: stamp a fixed build commit instead of running git in the vitest config Tests only interpolate ORCA_BUILD_COMMIT, so they need not read HEAD (or fail outside a git checkout) on every run. * refactor(settings): match only skills add in the Windows npx preflight Every official skill command is now the builder's skills@<version> add; no rail emits skills update any more. * fix(skills): fail an update that leaves a broken skill link behind Eligibility already skips names with a broken link before the run, so one afterwards is the run's own damage; report it instead of claiming success. * ci(release): refuse to tag when the skill manifest is stale The removed --release step implicitly checked the committed manifest against skills/. Keep that guarantee with a verify-only run; it writes nothing. --------- Co-authored-by: Orca Worker <orca-worker@localhost> |
||
|
|
4526d8bab4 |
native chat: queued-message actions and question answers accept through their own receipt (#26988)
* feat(native-chat): add a chat-owned command receipt store Adds the agent_session_command_receipts table and an insert-if-absent receipt write that runs inside a command's own journal transaction. A receipt records the command's identity, canonical hash, status and a pointer to the row holding its result; it dies with its chat record. Nothing uses it yet: the existing operation ledger stays authoritative. * fix(native-chat): key command receipts like today's ledger ids Rows are keyed by caller and operation id; a send looks across callers and other methods look up only their caller's row, matching the existing ledger. Bad rows and newer table shapes read as unreadable instead of throwing. * fix(native-chat): order receipt lookups by insertion and key by operation id Global lookups now take rows in insertion order, so a read and an insert agree on which receipt answers an id regardless of the host clock. The primary key leads with the operation id, so its own index serves both lookups. * fix(native-chat): accept sends through their own receipt Send (direct, queued, steer) and /compact no longer commit a separate ledger reservation before the message is saved. Their command receipt is inserted-if-absent in the same transaction as the submission or queued draft; a repeated id replays from it, a changed request conflicts. Operation ids are checked for syntax only, so age, clock skew, pruning and the reservation's own storage can no longer refuse a send. The ledger success row they already co-committed stays as a temporary insert-if-absent write so families not yet switched keep seeing these ids; it is never read on this path. * fix(native-chat): answer a send's receipt one way, share /compact's id space with Send, wake delivery once - After its commit, a send whose publication fails answers through the same receipt replay a retry takes, so one id gets one answer; the in-memory copy and journal reload are gone. - /compact ids are global like Send's: a retry under a new connection replays instead of needing a second acknowledgment write. - The receipt route calls its runner directly; the runner wakes delivery when a new message row commits. - The compatibility ledger row expires from host time; each temporary site is labelled. - Receipt read errors and post-commit failures are logged. * test(native-chat): expect receipt answers where tests assumed the send ledger Restart-continuation tests now model the case Send cannot vouch for (saved but not loaded into memory) and keep it unconfirmed; a later throw after the message is in the chat answers continued, as a resend would. Drop /compact's unreachable legacy replay branch and fix stale comments. * test: run the queued tests that open the journal database in the Node runtime project * fix(native-chat): queued-message actions and prompt answers accept through their own receipt Send Now, Delete, Resume and prompt answers move onto the command-receipt route Send uses: the receipt is inserted with the command's own write. A no-op answers only once its receipt commits (otherwise outcome unknown), and a prompt answer's replay reads the exact revision it was accepted at. * fix(native-chat): replay a queued action from what its receipt recorded Send Now's acknowledgement points at the submission it handed off; a question answer's receipt keeps the accepted resolution; a no-op that cannot save its receipt answers outcome unknown and holds nothing in memory; classified refusals pass through. * docs(native-chat): receipt comments say what a replay answers from * fix(native-chat): refuse a prompt answer on a newer Orca's journal with the update words A prompt answer now runs through the same newer-Orca refusal the queued-message actions use, so a read-only journal answers agent_session_journal_unreadable instead of throwing journal_read_only. |
||
|
|
c5224f70d8 |
refactor(providers): delete the SSH git and filesystem providers and relay-only wiring (#27644)
* refactor(providers): delete the SSH git and filesystem providers and relay-only wiring * refactor(ssh): drop relay-protocol exports only the deleted stream readers used --------- Co-authored-by: m4air <m4air@Mac.localdomain> |