mirror of
https://github.com/stablyai/orca.git
synced 2026-09-22 08:02:28 +00:00
5bd66bac8bfec4d9891846050114685aae1e257e
236
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
5bd66bac8b |
fix(cli): resolve a WSL worktree by the Linux path its own shell prints (#16628) (#17440)
On a Windows host the runtime stores a WSL worktree as the UNC path Windows sees, but a user inside the distro types the Linux spelling, so every `path:` selector missed: `worktree show`, `terminal list --worktree` and `worktree rm --worktree` all reported selector_not_found for a directory Orca manages. Translate once in the CLI, which is the only side that can prove which distro the typed path belongs to — from its own UNC cwd, never from WSL_DISTRO_NAME, which a Linux-native CLI also sets. The runtime's `path:` branch stays exact-spelling-only for the same reason: this resolver feeds delete, so a tail-only match would remove another distro's copy. |
||
|
|
c3aceacc7b |
Fix PR unlink for auto-detected reviews (#16898)
* fix: make PR unlink hide auto-detected reviews * Type the empty-content test double against the real model The literal narrowed suppressedGitHubPR to number and typed the callback as Mock, so neither direction was comparable and tsconfig.tc.web.json failed on TS2352. Keeping the 'as' cast preserves checking of the fields the double does supply. * Add localization keys for the unlinked checks-panel state The unlinked title, relink action, and the remote-runtime upgrade notice introduced untranslated keys that static analysis requires in en.json. * Advertise PR suppression capability in the transport test The client capability list is pinned by websocket-transport.test.ts, and adding WORKTREE_GITHUB_PR_SUPPRESSION left the expected list stale. * Fix stale PR suppression in Checks * fix: harden PR unlink suppression state * refactor: extract PR unlink state handling * fix: show PR relink recovery in source control * fix: add unlinked PR localization * Clarify workspace-scoped PR unlinking --------- Co-authored-by: Merge Sim <sim@local> |
||
|
|
3af2c665c0 |
fix(cli): name PowerShell when it strips quotes from JSON flags (#17351)
* fix(cli): name PowerShell when it strips quotes from JSON flags Windows PowerShell 5.1 does not escape inner quotes when building a native command line, so `--options '["a","b"]'` reaches orca.exe as `--options [a,b]`. The value is correct when printed and damaged by the time argv is parsed, so the resulting "invalid JSON" error blamed the user's input rather than the shell. #16743 recovered this for `--deps`, which is safe only because generated task IDs have a fixed 12-hex grammar. The same mangling hits `--options`, `--payload` and `--result`, and those are NOT safely recoverable: `["1","2"]` and `[1,2]` arrive at argv identically, so a general repair would silently turn strings into numbers. Detect instead. `getOptionalJsonFlag` rejects the damaged shape up front with an error that names the shell and shows the workaround. It fires only when the value is bracketed, quote-free, fails JSON.parse, AND consists entirely of bare tokens that quoting would rescue, so valid JSON is untouched. Also share the generated-id contract: `task-deps-flag` hardcoded /^task_[0-9a-f]{12}$/i, which silently diverges if `generateId`'s byte count changes. It now calls `isGeneratedId`, with a test pinning the two together. Verified on a Windows host. Measured argv, which the new test pins as a fixture: PS_VALUE=["task_b2a580db74d8","task_c3b691ec85e9"] ARGV=["--deps","[task_b2a580db74d8,task_c3b691ec85e9]"] Before: Invalid --options: must be a JSON array of strings After: --options arrived as [a,b], which is not valid JSON. Windows PowerShell 5.1 strips the inner quotes ... * fix(cli): scope JSON-flag detection to genuinely JSON flags Review found the detector wired to two flags that are not JSON: - `orchestration ask --options` is documented `<csv>` and the runtime splits it on commas, so `--options [a,b]` was a legitimate value being rejected. - `task-update --result` is stored verbatim and reused as dispatch failure text; existing tests pass free text, so a bracketed `[ok]` was being rejected. Both revert to `getOptionalStringFlag`. Only `gate-create --options` (`<json_array>`) and `send --payload` (`<json>`) are JSON-parsed and keep it. Three further review fixes: - Objects now require a `key:value` pair per entry. `{a,b}` and `{a:b,c}` were reported as quote-stripped although quoting them cannot produce valid JSON. - The raw value is no longer echoed. A `--payload` can carry secrets and this message reaches `--json` output; the flag name and guidance are enough. - The message hedges the shell attribution. Detection inspects only the value's shape, so it also fires when a macOS/Linux user forgets to quote, where PowerShell is not involved. Verified against a Windows host, all six cases: both JSON flags fire on the mangled shape and pass valid JSON through to the runtime; both non-JSON flags now reach the runtime again; and the secret in `{token:hunter2}` appears zero times in the error output. |
||
|
|
07df4bf0be | fix(orchestration): recover stripped task deps | ||
|
|
fd9125ea8c |
feat(native-chat): Codex structured native chat restructure (#16729)
* feat(native-chat): port structured Codex sessions from restructure-recovery Rebuilds the desktop structured native-chat implementation from brennanb2025/native-chat-restructure-recovery (tip 4e31c08db3) on top of current main as a single commit, scoped to the local Codex path. Ported: - Structured agent-session core: durable record store + single-writer lease, canonical journal, agent-session wire host/attach/eviction/subscribers, `agentSession.*` RPC surface (registered via ALL_RPC_METHODS; host-side mobile allowlist included for wire compat), pty write gate, transcript additions, and the Codex app-server adapter/launch resolution. - Renderer: NativeChatStructuredSession view/composer stack, structured launch path with the single-flight guard, local structured session tabs sync, activation gate + structured inventory (read-only `agentSession.handoffStatus` probe), agent-session tabs in the tab strip, AI-vault structured session activation, and the settings pane with the parent Experimental Chat UI toggle plus the nested "Use updated structured native chat" toggle. New sessions require both flags, agent codex, no prompt, and a local non-WSL, non-Windows-host execution host (structured-native-chat-availability). - Fixes 72c013cea6 (verified Codex launch recovery), 8ddbaf5e3d (defer native terminal view switching affordances), and 4e31c08db3 (release the launch gate after a visibility retry) with their regression tests, including the third-launch-after-retry guard case. - Cross-version agent-session wire test + CI lane, packaging entries (proper-lockfile, agent-tooling asar excludes), and the wire-compat doc section. Deliberately not ported: mobile/ changes, the Claude structured runtime (only the claude-transcript-branch-proof and claude-structured-owner-identity leaf modules remain, backing the kept TUI-recovery arms), the terminal↔chat adoption/handoff flow (`agentSession.adoptTerminal`/`requestHandoff`, the handoff request engine, TUI adoption machinery, orca-runtime adoption methods), renderer switching affordances and their dead leftovers, the hook/subagent-status refactor cluster, and unrelated branch changes. The crash-during-acquisition recovery path (restart handoff adjudication, restore/reverse re-acquire, lease schema handoff keys) is kept because every plain direct launch depends on it; a trimmed handoff coordinator exposes only status/restore/close. Branch edits that targeted files main has since split (ipc/pty.ts, worktrees.ts, rpc/methods/terminal.ts, useIpcEvents, pty-connection, store/slices/terminals.ts, runtime-types, web preload) were re-applied to the split modules, preserving main's newer logic (Windows CIM fallback, browser tab close rework, cold-restore resume flow, dispatcher threading). Known seam: the mobile clipboard image-provenance CONSUMER gate ships (agentSession.send refuses unproven mobile image refs with agent_session_image_untrusted) but the producer hunk in rpc/methods/clipboard.ts stays with the unported mobile cluster, so mobile image sends into structured chat fail closed until that side ports. * fix(native-chat): trust only authenticated local image uploads * fix(build): preserve Windows process-tree patch application * test(windows): include process creation time in addon fixture * fix(build): run windows-process-tree node-gyp from the physical package dir gyp expands the node-addon-api dependency by probing node, whose cwd resolves to the package's physical directory in the store, so the emitted target is a store-relative ../../../../node-addon-api@... hop. gyp then resolves that hop against the rebuild cwd; from the node_modules symlink/junction it escapes the store and configure fails with "node_addon_api.gyp not found" (run 32999886072). Rebuild from realpath(package dir) so both bases agree, matching how the package manager itself runs native install scripts. The regression test replays gyp's expansion+resolution against the planned cwd and fails without the fix. * fix(native-chat): keep chat tabs visible through terminal closes and empty-worktree launches Two proven blockers in the native Codex tab contract: closeTerminalTab pre-empted the canonical unified close. With one terminal left it deactivated the worktree on a terminal/editor/browser-only check, blanking a workspace that still held a renderable agent-session tab; with two or more it pre-picked a successor from terminal entities only, re-stamping the group active before closeUnifiedTab's MRU/neighbor repair could land on the chat tab. Successor choice now defers to the unified contract whenever the terminal has a unified row, and deactivation is gated on the unified renderable count (matching leaveWorktreeIfEmpty), with the legacy pre-pick kept only for terminals without a unified row. A structured session created on an empty worktree was published into the host's headless group while preserveLocalLayout froze the local layout, leaving the tab in store but permanently off screen. A preserveLocalLayout owner now always takes client-owned placement — repairing a rendered leaf whose group record is missing, or materializing a rendered group on a truly empty worktree — and applies the client-derived layout repair while still rejecting host-authored layout. Regression tests drive the real store through closeTerminalTab (git worktree and folder workspace) and the real snapshot applier for the empty-worktree adoption states; all fail without the fixes. * fix(native-chat): close stale turns and retry rejected sends * fix(native-chat): retire hosted rows on structured tab activation * fix(native-chat): preserve rpc defaults across main merge * chore: format remote wire compatibility guide * test(native-chat): cover retry after unconfirmed send * fix(native-chat): reload outbox on session switch * docs(settings): disclose structured chat platform limits * fix(native-chat): await Codex launch-home preparation * fix(codex): align child-process allowlist with async trust bridge * test(identity): update inventory for tab surface refactor * fix(windows): preserve process-tree CRLF patch sources * fix(native-chat): anchor an unmatched chat echo where it was sent (#16117) * fix(native-chat): anchor an unmatched chat echo where it was sent The reported symptom was old user messages replaying below every new turn, so the conversation read as scrambled. The cause was not that the echo failed to match a transcript row. Claude consumes a mid-turn send through a `queued_command` attachment and writes no `type:"user"` record for it, so some echoes can never match, and no amount of matching will change that. The cause was WHERE an unmatched echo rendered: buildMobileNativeChatTransientData appended every pending item after the entire transcript, so it re-read below each turn that landed afterwards. Render each echo directly after the transcript row it was sent against, using the baseline the send already captures. An unmatched echo is then at worst a duplicate in the right position rather than a scrambled one, and it stays visible. Echoes sharing an anchor keep send order; a send with no baseline, or one whose anchor folding dropped, still falls back to the tail. Deliberately NOT fixed by deleting the echo. Inferring from send ordering that an echo can never match, then removing it, loses the user's own text for a message the agent did receive, and it cannot fire in the common case anyway - measured drain groups are 1,017 of size 1 against 55 larger. It also escalates an existing gap: the count pass has no baseline-tail guard, unlike the glue pass, while `messages` is a 40-row window that head-trims, resets on reconnect and grows at the front on loadEarlier, so a false landing there would license deleting a DIFFERENT outstanding message. That count-pass gap is real and left for a separate change; anchoring makes its worst case a duplicate in place rather than a scrambled conversation. * fix(native-chat): preserve folded echo anchors * fix(native-chat): preserve forward-folded echo anchors * fix(native-chat): keep leading folded echoes in place * fix(workspace-cleanup): show git status for every row (#16690) * fix(native-chat): refuse structured chat on every Windows execution path canUseStructuredNativeChat only refused win32 when a project runtime resolved, so folder-workspace keys (and other keys with no project runtime) failed open into structured chat on Windows. Fail closed on win32 unconditionally after the host check, matching the settings copy: local macOS/Linux only; Windows/WSL/SSH stay on terminal chat. * fix(native-chat): restore runtime refusals behind the win32 gate |
||
|
|
2c86d2a3bd |
fix(agent-hooks): stop test runs and secondary profiles deleting the user's agent hooks (STA-5679) (#16980)
* fix(agent-hooks): stop startup from deleting another instance's managed hooks (STA-5679) Startup reconciliation removed the managed agent hooks whenever THIS profile had the agent-status-hooks off switch set. The hook files it removes are user-global (~/.claude/settings.json, ~/.cursor/hooks.json), so a second Orca profile with the switch off deleted the hooks every other running instance depends on. Cursor is the only agent with no title-derived status fallback: its native title is deliberately parsed as status-less, so a hookless Cursor pane is floored at 'idle' rather than showing a spinner. A global hook wipe therefore surfaces as "Cursor loading status missing from the sidebar" while Claude and Codex still paint status from their own titles, which is why this reads as a Cursor-only bug. Codex is unaffected either way because its hooks live in an Orca-owned runtime home. Honoring the off switch only requires skipping the install; removal stays on the explicit Settings toggle, which is the user-initiated path that should own it. Regression from #2778, which restored the destructive startup branch. * fix(cli-tests): stop the deferral suite deleting the developer's real agent hooks runtime-client-deferral.test.ts runs the REAL `main()` and feeds it `agent hooks off`. It mocks only ./runtime/environments and ./runtime-client, so the production handler ran end to end: updateEnabledOnDisk() wrote its state file and applyAgentStatusHooksEnabled(false) called removeManagedAgentHooks() against the developer's OWN ~/.claude/settings.json and ~/.cursor/hooks.json. A green test run therefore deleted every Orca-managed hook on the machine. Agent status then stopped reporting until the next Orca restart reinstalled them — silently, because the hook POSTs still return 204 and Cursor has no title-derived status fallback at all. The byte-for-byte equivalence twin already refuses these exact tokens, commented "MUTATING — writes outside ORCA_USER_DATA_PATH (`agent hooks off` parks the real ~/.claude hooks)". The vitest twin never got that guard. Stub the hook-controls module rather than dropping the row: `agent hooks off` is the only case in the table that reads ctx.client, so it carries the null-vs-undefined coverage the other four cannot. All 23 tests still pass, and a sandboxed HOME now keeps its hooks (5 -> 5) where it previously lost them (5 -> 0). * fix(cli-tests): ratchet agent hook deferral safety * fix(agent-hooks): keep startup reconciliation install-only |
||
|
|
2b0ee06205 |
docs(env-recipes): warn that snapshotting a started runtime bakes its identity (#17001)
Snapshotting a VM on which `orca serve` has already run captures the runtime's user-data dir into the image. Every VM booted from that image then shares one pairing identity and one agent-session-authority key, which defeats the per-device token design. Confirmed by booting two VMs from one such snapshot: both emitted identical deviceToken and pairedDeviceId. Adds the rule to the base-snapshot section and repeats it for the agent-auth layer, which is the likelier place to start the runtime by hand while smoke-testing. Says to delete the whole user-data dir rather than a named file list, since that list drifts as Orca adds state. |
||
|
|
c4b39295c1 |
style: format codebase (#16935)
* style: format codebase * style: format codebase * refactor: extract skill install dialog footer and content Extract footer and content sections from SkillInstallDialog and SkillInstallManagementDialog into separate components for improved maintainability and clarity of component responsibilities. |
||
|
|
7ee8b5e1a6 | Refactor lower max-lines modules (#16760) | ||
|
|
419e3b4496 |
Fix terminal reads that flatten composer drafts into output (#16711)
* fix(terminal): separate composer drafts from read output Rendered screen reads treated cursor-line suggestion overlays as PTY output. Detect composer-owned text from cell attributes and cursor context, remove it from tail, and expose it as structured draft metadata. * fix(terminal): handle wrapped composer overlays * fix(terminal): preserve draft wrapping and tail alignment * fix(terminal): preserve composer wrap boundaries * fix(terminal): preserve draft continuations with middle dots * fix(terminal): recognize configurable Codex status lines |
||
|
|
913509edeb |
fix(orchestration): prevent slow worker-start stalls (#16300)
* Extend orchestration agent submission timing budgets * fix(orchestration): preserve mutation recovery identity * fix(orchestration): preserve recovery executable identity * fix(orchestration): keep worker starts and recovery commands safe * test(orchestration): cover federated worker preflight * fix(orchestration): harden mutation recovery * fix(orchestration): redact dispatch recovery credentials * chore: preserve upstream skill dialog formatting * test(orchestration): stabilize agent prompt submit e2e * fix(orchestration): validate federated start receipts * perf(runtime): cache unchanged prompt verification tail * fix(orchestration): reject worker-start timer overflow * fix(orchestration): normalize worker-start timeout defaults * fix(orchestration): normalize worker-start readiness budgets * fix(orchestration): normalize federated readiness timeout * test(runtime): tolerate current-main degradation exports * chore: preserve current-main orcad formatting * chore: drop unrelated formatting carryover |
||
|
|
b241a68ae4 |
Fix worktree identity collisions across hosts (#16691)
* fix(workspaces): add collision-safe worktree identity * fix(workspaces): read worktree metadata per host and repair ambiguous identities The canonical identity store landed write-only: getWorktreeMetaForHost had no production callers while setWorktreeMetaForHost kept the legacy projection only for the first known owner, so a second host's edits persisted and were never read back. Wire the listing paths through host-qualified reads. An ambiguous alias was also unrecoverable — reads returned undefined and writes threw forever, and the throw escaped the detected-worktree loop, emptying the whole repo's sidebar. Fail open onto the most recently active instance instead. - collapse ambiguous aliases deterministically and persist the repair - reclaim identity rows in the metadata GC so they cannot outlive their locator or resurrect onto a worktree recreated at the same path - drop every host's rows when a locator is removed outright, not just the owner's - honour an explicit instanceId so the stale-lineage rotation guard still works - scope a rename to the moving host; other hosts keep their own locator - prefer the project host setup matching the repo's own execution host, so a repoId registered on two hosts no longer stamps the wrong one durably - reject an unencoded `|` in a host id, the invariant the alias delimiter needs - drop the never-populated hostGeneration from the canonical key * fix(workspaces): close remaining identity review gaps * fix(workspaces): close remaining review gaps * fix(workspaces): address review and CI regressions * test(workspaces): update host-qualified metadata expectations * fix(workspaces): preserve ambiguous identity records * fix(workspaces): snapshot metadata during listing * test(workspaces): mirror listing metadata snapshot in windows fixture * fix(workspaces): preserve identity routing for metadata writes * fix(workspaces): scope stale metadata cleanup by host * fix(workspaces): rekey identities on SSH readoption * fix(workspaces): fail closed for ambiguous board ids * perf(workspaces): snapshot metadata across catalog listing * fix(workspaces): retain neighboring manual order updates * test(workspaces): cover ambiguous board id index * fix(persistence): harden host-qualified worktree metadata * refactor(shared): split project host setup lookup * refactor(workspaces): simplify host-qualified metadata |
||
|
|
3558cf943f |
fix(codex): heal WSL hooks before typed launches (#16535)
* fix(codex): heal WSL hooks before typed launches * test(codex): keep launcher fixture type-safe on Windows * fix(build): list codex-home-wsl-env in the CLI typecheck project `managed-home-shell-preflight.ts` is already in the CLI project's include list and now imports `wslCodexRuntimeHomeForGuestHome` from `src/main/pty/codex-home-wsl-env.ts`, which the list did not cover — TS6307, so the CLI typecheck failed on every push. Added the single module rather than a `src/main/pty/**` glob: it is a 31-line leaf with no imports of its own, so it does not widen what the CLI bundle can reach. * fix(codex): converge the two WSL hook install lanes onto one writer Two independent readiness reviews agreed the Orca-terminal boundary holds, but Codex Sol found a P1 the other rated P2: the new just-in-time repair raced the existing relay installer and the two produced DIFFERENT hook and trust representations for the same managed home. Two unserialized writers emitting different formats is worse than the bug this PR fixes, because it fails intermittently rather than cleanly — a pane works or does not depending on which lane won. - Relay Codex installs now delegate to the runtime-home writer, so there is one canonical representation instead of two. Redirected scripts use the runtime path, the readable wrapper, and the prepended group. - `installForRuntimeHomeSerialized` puts every asynchronous WSL caller for a given home on one queue (`wslInstallQueues`), so concurrent panes cannot interleave writes. Also rewrites the stale pin test the new `-x` guard broke. It asserted the defect — "would run the impostor if the preflight carried an unqualified command name", expecting the hijack marker to exist. The guard is a security improvement, so the test now asserts the contract: an unqualified preflight is skipped and the marker is never written. Rewritten to the new behavior, not loosened or deleted. 818 tests pass across the affected suites; typecheck clean. The changed-file quality gate could not run locally — its pnpm engine-warning JSON parser fails under Node 26 — so CI covers it. The boundary both reviews verified is untouched: paired/relay/mobile clients stay hard-blocked from the RPC, params remain shape-locked to the managed home suffix with traversal rejection, nothing is written outside the managed home, and macOS/Linux stay inert. * fix(codex): serialize resolved WSL hook homes * fix(codex): recover managed WSL homes after restart * fix(wsl): translate Codex preflight through WSLENV * fix(cli): cover bounded WSL Codex repair * fix(codex): coalesce duplicate WSL hook repairs * fix(codex): verify reconstructed WSL homes |
||
|
|
0f522c35e5 | fix(remote): gate empty session inventory on host authority (#16546) | ||
|
|
26721bd632 |
fix(codex): stop blocking the main thread on trust grants (#16441) (#16594)
* fix(codex): stop blocking the main thread on trust grants (#16441) Codex hook trust was granted by blocking the Electron main thread on `spawnSync` of a bundled ELECTRON_RUN_AS_NODE entry for the whole app-server deadline: 15s native, 35s WSL, ~45s on the real-home path (rebase inspect + repair + grant). Cold start and every Codex pane launch showed "Not Responding"; the reported event-loop gap was 15,049 ms. The subprocess only ever existed to donate an event loop to a deliberately blocked parent — `runCodexHookTrustGrantSession` was already the real async implementation. Make the callers async and the fork is unnecessary, so the bridge, the forked entry and its envelope are deleted along with their build/knip/tsconfig registrations. The CLI `agent hooks prepare-codex` handler is already async, so it awaits the in-process session and saves a process spawn per managed-home shell. `resolveCodexTrustGrantHost` is async too; the WSL identity probe moves from `execFileSync` to `runProcess`, dropping that file from the child-process import allowlist. Status reads keep a synchronous native-only stamp path. Two invariants that held only because the lane blocked: - Overlapping capability probes were impossible by construction. `GitCapabilityCache`'s dedupe engine is extracted to a shared `CapabilityProbeCache` and `CodexAppServerCapabilityCache` now inherits it, so concurrent launches against a cold host share one app-server session instead of one each. - Two grants on one `config.toml` could not interleave capture and restore. A reentrant per-file lane now serializes the whole install sequence (managed, WSL runtime, real-home ensure, legacy sweep) and the grant and rebase inside it. Cold-start work moves off the critical path: retained-home reconciliation (N sequential sessions) is fire-and-forget behind the daemon provider, and the startup real-home ensure chains into managed hook reconciliation instead of blocking app init. Every preserved semantic is unchanged: never throws, the ORCA_DISABLE_CODEX_TRUST_RPC kill switch, ledger hits, backfill-pending and cooldown fallbacks, config rollback on every failure path, pre-grant self-computed trust removal, the verify-failure taxonomy, diagnostics and telemetry. * fix(codex): widen the trust-config lane to every config.toml writer Review follow-ups on #16441's async trust grant: - `markCodexProjectTrusted` now runs inside the runtime+system config.toml lanes, so a project-trust write can no longer land inside a hook grant's capture->restore window and be silently reverted. Its callers await it. - `install`/`refreshRuntimeUserHooks`/`remove` hold the system config.toml lane as well as the runtime one — they promote approvals into ~/.codex/config.toml and mirror it back. Lock order is runtime-before-system everywhere. - The real-home ensure chain resumes after a rejection instead of returning the same rejected promise to every later pane launch, and resolving the real home is now inside the module's never-throws boundary. - `buildSpawnEnv` awaits inside a cancelable pending-spawn registration, so shutdown during the (now long) env build stops the PTY from launching. `prepareLocalPtySpawn` generalizes into `awaitCancelableLocalPtySpawn`. - CapabilityProbeCache drops the test-only `nowMs` passthrough; its probe backstop comment now describes what it actually guards. - Preflight is a plain async function; the trust dispatch in orca-runtime collapses into one `markWorkspaceTrustedForAgent`. * test(codex): exercise the trust-config lane under real concurrency The async grant makes two pane launches overlap for the first time. These drive the real modules end to end on real files: a rollback swallowing a sibling's grant, a markCodexProjectTrusted write landing inside a capture -> restore window, shared capability-probe dedupe on a cold host, the host-scoped transient cooldown, and reentrancy from inside an installer. Each was verified to fail against a deliberately broken implementation (lane removed, dedupe disabled, cooldown made global, reentrancy pass- through disabled). * test(codex): stop hook-service suites spawning the developer's real codex The forked grant bundle never existed under vitest, so the RPC lane was unreachable in tests on main. Running it in-process makes these suites spawn a real `codex app-server` when one is installed: 38 spawns and two failures in hook-service-runtime-trust-repair on a machine with codex, green in CI where there is none. Stand in for the missing binary so both environments exercise the same fallback lane. * docs(codex): scope the trust-RPC kill switch comment to what it actually gates The comment read as though the flag forces the fallback lane everywhere. It gates the managed grant only: the real-home rebase still runs its own inspect/repair app-server sessions when Orca's insertion shifts a user's hook positions, and never reads the flag. Verified by exercise, not by reading — with the flag set, both inspect-user-hook-trust and repair-user-hook-trust still ran. Pre-existing: main has no check there either, it just blocked the main thread while doing it. Widening the flag to cover the rebase is a follow-up; this only stops the comment promising something the constant does not do. |
||
|
|
9135b6f004 |
feat(orchestration): surface nested worker depth and propagate it across hosts (#16669)
* feat(orchestration): surface nested worker depth and propagate it across hosts Builds on the depth enforcement in the previous commit, which shipped with the setting reachable only by editing settings.json and with workers never told they could nest. Adds the Settings -> Agents control (a 1/2/3 select rather than a free-form number, which bounds the value without inventing a numeric input primitive). The key stays absent from the SettingsUpdate RPC schema, matching agentSkillSharingEnabled: settings.update is reachable from the CLI, so an RPC-writable depth would let a worker raise its own cap. Adds a SUB-DISPATCH block to the dispatch preamble, emitted only when the worker actually has budget left. A worker told it "usually cannot" delegate still tries and then reports the refusal as a blocker, so the section is omitted entirely rather than softened. Propagates depth to federated worker hosts. Previously the home side computed and stored a depth the remote host never received, so a remote attachment always read as depth 1. That is correct at the default cap and wrong as soon as the cap is raised — precisely when someone starts relying on nesting. The field is optional, so an older Run home simply omits it and the attachment's NOT NULL DEFAULT 1 keeps the fail-closed behaviour. Enforcement still runs on the executing host against that host's own cap, consistent with the SSH execution boundary. * fix(orchestration): close nested depth readiness gaps * fix(settings): defer nested depth translations * fix(orchestration): drop federated depth keys that main already landed The enforcement PR's review pass added the same federated depth propagation before it merged, so replaying this branch onto main produced duplicate object keys. Keep main's versions -- its schema entry validates an integer >= 1 rather than any finite number. * fix(settings): label nested worker depth select * fix(settings): move nested depth to orchestration * fix(settings): refine nested depth placement |
||
|
|
64c992cd56 |
fix(memory): report the Windows number that predicts paging, not just resident pages (#16211) (#16589)
* fix(memory): report Windows commit charge, not just working set (#16211) On Windows the per-process figure was working set — resident pages only. An agent whose pages Windows has trimmed to the pagefile shrinks its working set while still holding the commit that pushes the host into paging, so Resource Manager and `orca diagnostics memory` understated an owned tree by 10-40x (9 codex.exe: 1.4 GB working set, 13.4 GB private) and could not warn before the host was already thrashing. Add committed private bytes as a second, separately-labelled quantity rather than redefining the existing one: - CIM sweep gains one property (PageFileUsage, UInt32 KB); the typeperf fallback gains one counter (\Process(*)\Private Bytes). Both ride the sweep that already runs. - MemorySnapshot gains optional `privateMemory` per app/worktree/session plus `processCommitMetric` and `totalPrivateMemory`. Rule 1 additive optional fields: old clients ignore them, and absence reads as "not measured", never as zero — Unix hosts and older hosts send nothing. - `totalMemory` and `processMemoryMetric` keep their exact meaning, so the "shared pages may repeat" copy stays true; the working-set copy now also says paged-out memory is not counted. - Resource Manager shows "Σ Private" beside "Σ WS", and tints the badge yellow/red once tracked commit passes 60/80% of physical RAM — the same thresholds `usageTextColorClass` already uses for host usage. Tint and tooltip only; no toast, and the badge number is unchanged. The parsers move to windows-process-sample-parsing.ts and the Windows sweep tests to their own file to stay under max-lines. Not migrating the collector to windows-process-table.ts: the native snapshot exposes no commit figure and no CPU times, and truncates WorkingSetSize through a DWORD. Documented in the enumeration reference. * fix(memory): derive the typeperf field cap from the counter list The fallback parser's 8192-field cap was sized for three `\Process(*)` counters. Adding `Private Bytes` cut the parsable process count from ~2730 to ~2047, and overrun is a blackout (`parseTypeperfCsvLine` returns `[]`, so the whole sweep reports nothing) rather than a truncation. The counter list now lives beside the decoder that reads those names back out of the PDH header, and the cap is derived from it. Also collapses the four spellings of "omit privateMemory when unmeasured" in collector.ts onto one `commitField` helper, drops the unread parameter and the never-rendered `columnLabel` from `getResourceCommitMetricCopy`, folds `getCommitPressurePercent` into the only function that called it, and reverts unrelated Prettier churn in the Windows enumeration doc. The commit tint's doc comment no longer claims to predict host paging: it measures Orca's own share of physical RAM. Host commit charge / commit limit stays a follow-up (#16211). |
||
|
|
0e10fc5925 | fix(browser): retire helpers with page owners (#16564) | ||
|
|
8a07bbd8cf |
fix(orchestration): enforce nested worker depth instead of an accidental fence (#16668)
* fix(orchestration): enforce nested worker depth instead of an accidental fence Orca documented that "dispatched workers cannot spawn their own sub-workers (worker-start is coordinator-fenced)". No such check existed. What existed was a single Run-binding check in the workerStart RPC: a worker's terminal is not bound to a Run, so worker-start happened to fail. The rule was emergent, asserted by no test, and written in no doc — and it leaked. A worker could run-create its own Run, task-create, and worker-start: now bound, the check passed. Replace it with a real, configurable depth cap. Depth is derived from the caller's own active Dispatch rather than from Run binding, which is what dissolves the run-create bypass: creating a Run does not stop you being a worker. Enforcement lives in a single dispatch-row writer that owns all three INSERTs that mint a live worker — the generic claim, the supervised worker-start path (including every retry), and the remote attachment. Two of those were missed by earlier drafts of this change, so `creator` and `maxDepth` are required parameters: a new spawn path cannot compile without deciding, and a boundary test refuses the SQL anywhere else. Schema v30 adds depth to dispatch_contexts and remote_dispatch_attachments, NOT NULL DEFAULT 1 and backfilled to 1 so an unstamped or pre-upgrade row fails closed rather than reading as a root coordinator. The attachment pane indexes widen to the five states in which a remote worker may still be running: loss of contact is not evidence of process death, so an unverifiable worker still counts as a nesting parent. Also adds the caller-evidence assertion that workerStart was the only Run-scoped verb to skip, so a declared --from cannot name another terminal's pane and inherit its depth. Default is 1, so behaviour is unchanged unless the new setting is raised. Two limitations are deliberate and documented rather than papered over: this is a guardrail and not a security boundary, since a caller whose launch evidence is unverifiable (any ordinary restored terminal) can declare another handle; and it is enforced at supervised dispatch creation, so a settled worker whose process is still alive counts as a root again. * fix(orchestration): share caller resolution and pin worker gaps * refactor(orchestration): make the caller resolver's pane contract explicit Overloads so requireStablePane callers get a non-null string instead of casting, and rename the attestation opt-out to say what it means: the caller asserts it itself. A flag called assertEvidence:false reads as "attestation optional", which is the hole this helper exists to close. * fix(orchestration): propagate dispatch depth to federated workers * chore(cli): refresh bundled orchestration guide |
||
|
|
cda2280d63 |
Show all automations (#16532)
* Add all-host automations with scoped ownership and multi-authority suppo
Enable automations to run on multiple hosts (SSH targets and local) with
owner-fenced mutations, scoped list queries per host, and conflict
resolution. Introduces desktop and runtime authorities as distinct
automation storage owners, with per-host caching, invalidation, and
retry scheduling on the renderer. Captures registration generations for
SSH hosts to survive re-adoption. Adds CLI support for destination
selection and conflict recovery.
* Filter automation create projects by destination host
Only offer projects available on the selected destination, preventing
the mismatches that would fail at submit time. Auto-adjust the project
selection if it becomes unavailable when the destination changes.
* Add runtime storage authority support for automations
- Support both runtime and desktop as automation storage authorities
- Make owner preconditions optional for legacy-client compatibility
- Cache automation list projections to improve performance
- Add per-row repo/worktree resolution for cross-authority collisions
- Extend automation.list RPC to always include owner metadata
* Replace child_process.execFile with runProcess for external automations
- Migrate external-manager to use cross-platform runProcess wrapper per child-process safety policy
- Abstract electron app/ipcMain APIs in orca-runtime via environment accessors
- Install fake app environment in automation tests for consistent setup
- Reorganize imports to use specific module paths (ssh-target-registry, agent-detection, browser-error)
- Remove external-manager from child-process import allowlists (no longer violates direct import)
* Unify desktop automation CRUD onto the local runtime RPC surface
The desktop authority now speaks the same automation.* RPC contract as
remote runtimes, via callRuntimeRpc({kind:'local'}) -> runtime:call ->
the shared RpcDispatcher. The automations:list/listRuns/create/update/
delete/runNow IPC arms, their preload members, and every renderer
desktop-vs-runtime transport fork are retired; the runtime methods are
the single implementation of scoped lists, owner fencing, and change
publication for both transports (mobile clients already exercised them).
The desktop probe scheduler's priority lease survives the move as an
AutomationService hook the IPC registration installs and the runtime
methods take, so Orca's own automation traffic still parks queued
external-manager probes.
External-manager scope arms and dispatch-loop plumbing stay on IPC by
design; automation change events keep their existing channels (renderer
ingestion already converges them by authority).
* Remove automation ghost SSH tombstone scanning
This functionality for synthesizing tombstones for automation-referenced SSH
targets is no longer needed as part of the automation system refactoring.
* Refuse orphan automations at dispatch time, not migration time
Remove migration-time disabling of orphan automations and the `enabledDecidedBy` field. Dispatch now refuses orphans at runtime instead, simplifying state management and UI. Orphans are left unstamped and enabled; dispatch refuses to run them via `resolveAutomationRunTarget`.
* Show all automations in flat table with unified filter menu
- Replace host picker component with comprehensive Filters menu supporting status, last run, agent, and host filters
- Flatten automation list layout to single table instead of host-grouped sections
- Add Host column to display execution host for each automation
- Display active filters as removable pills below toolbar
- Delete unused AutomationHostPicker* components
* Add automation owner fencing and destination validation
- New AUTOMATION_OWNER_FENCING_RUNTIME_CAPABILITY for owner preconditions; legacy clients get owner metadata snapshotted at RPC boundary for compatibility
- Editor captures and revalidates automation destination before save, preventing silent retargeting if SSH infrastructure changes mid-edit
- SSH target types now isolate renderer-authored fields; generation is server-owned and stripped by IPC handlers
* Route automation recovery actions to the origin host
When an automation action fails due to owner fencing, recovery verbs
("Update server", "Reconnect") must run on the host where the refusal
originated: the row's captured owner for row operations, or the
destination the create dialog captured, not the list's filtered host.
* Remove external manager scope limitation notices
Consolidate create destination eligibility checks with a unified predicate
and fix the bug where desktop repo IDs could be sent to runtime hosts where
they cannot resolve.
* Persist only store-derived automation contexts, not client-perspective o
Store contexts must never be based on client-provided runContext or sourceContext
values—clients speak a different perspective (e.g., 'runtime:<id>' for host IDs
they assign), and persisting those makes the store projection orphan automations
it actually owns. Derived contexts now take precedence in create and update paths,
with explicit null still honored to clear a value. Tests verify this by simulating
drift after storage and confirming that moves re-derive while toggles preserve.
|
||
|
|
a9781a4118 |
STA-4150: client-hosted remote browser (consolidated) (#15448)
Co-authored-by: Jinwoo-H <jinwoo@stably.ai> |
||
|
|
2b1b094aa8 |
fix(cli): pair every resolved CLI with its runtime, and ratchet it (#16383)
Follow-up to #16365, which paired 8 spawn sites by hand. Hand-pairing is how the class got introduced, so close it structurally instead. cliPath is now required on CodexAppServerInvocation, `null` only for the guest-side wsl.exe launcher where a host path pairs nothing. Optional let a native builder omit it and silently fall back to pairing against a cmd.exe wrapper with no type error. Every production site already passed it; only test fixtures needed updating, which is the type doing its job. Four more sites now pair. codex-state-db-backfill-recovery spawns the same `codex app-server` subcommand #16365 fixed elsewhere. cli/handlers/account was the worst case: addAgentNodePaths prepends the *newest* version-manager bin, which is not necessarily where the CLI being launched lives, so it actively created the mismatch — pairing now runs last so the CLI's own node wins. commit-message-text-generation and skills/skill-update-run spawn resolved binaries with inherited env. cli/handlers/skills had grown its own buildNpxPath: a weaker local copy that prepended unconditionally, ignored the Windows `Path` key, and special-cased a '.' dirname. Deleted in favor of the shared helper, which checks the sibling node actually exists — the behavior change one test had pinned. The ratchet is the point: any file that resolves a CLI and spawns must reference withCliRuntimeOnPath, with a shrink-only allowlist. It caught skill-update-run, which I had missed. Its first draft required a call paren and so let dependency-injected resolvers (`resolveCommand: resolveCodexCommand`) through — verified by removing a pairing and watching it stay green, then widened until it failed. A second assertion fails on a stale allowlist entry so an exemption cannot outlive its reason. external-editor-launch stays allowlisted: it launches a GUI editor, not a Node CLI whose ABI matters. |
||
|
|
09048c63d4 |
feat(orcad): add headless browser providers (#16193)
* feat(orcad): add headless browser providers * fix(orcad): merge the duplicate runtime-browser type import |
||
|
|
0b66daffcc | refactor(cli): split orchestration handlers (#16139) | ||
|
|
31562c5b27 |
fix(windows): attach interactive login children to console input
Verified on native Windows awin at the exact PR head with Electron CDP/Playwright: the Claude sign-in console is visible, cancellation after console launch restores Add Account state, and the login process/PID/temp cleanup completes. |
||
|
|
5bcbafff53 |
docs(cli): document worktree rm branch cleanup (#16167)
Document that Git worktree removal may also delete the checked-out local branch, while clarifying that --force does not force branch deletion and that Orca retains branches whose changes cannot be proven merged. |
||
|
|
445c390170 |
fix(cli): allow an empty --value for storage set commands (#15863)
Co-authored-by: 2sumtech <2sumtech@gmail.com> |
||
|
|
3fca1d1648 |
fix(linear): unbound list-issues by default, surface truncation, bind cursor workspace (#15824)
Fixes STA-5076. list-issues capped at 50 by default and hard-clamped at 250, with hasMore buried under result.meta and no stderr warning for --json, so a page that stopped early read as a complete answer. Omitting --limit now walks Linear's pages until they run out (meta.limit is null), and --limit <n> is the only cap, paging past Linear's 250-per-request maximum to reach it. result.truncated sits next to result.issues and is set only when a cap actually held results back; human output prints "truncated: showing N". The read still has to fit the CLI's 60s RPC budget, so a 20s wall-clock deadline and a 200-page ceiling stop the walk early and report truncated with a continuation cursor rather than failing the command. Also: - issued --cursor values bind the resolved workspace, so call -> nextCursor -> call works without --workspace; raw Linear cursors still need one and now carry nextSteps - issued cursors whose payload smuggles back `all` or an empty workspace are rejected at decode, since either would widen the read past the bound workspace - JSON issue rows carry priorityLabel (none/urgent/high/medium/low), matching orca linear priority set - truncated and priorityLabel are optional on the wire, so a host that predates either is not read as "complete"; readers fall back to meta.hasMore - the truncation line prints the rows actually rendered, so a remote result with no meta.returned cannot print "showing undefined" |
||
|
|
ef096d539d |
fix(terminal): refuse a cursor on a screen read, and correct the source docs (#15563)
Review follow-up on #15380. The RPC accepted `cursor` and `screen` together. The CLI refuses the pair, but terminal.read is reachable without it, and honoring both answered with rendered lines carrying the stream's pagination metadata — two frames of reference in one payload, which is the confusion `source` exists to remove. The guard beside it, withVisibleSnapshotFallback, already declines to substitute rendered lines when a cursor is present; the screen path now agrees, at the RPC boundary where every remote caller passes. Nothing could previously send both, since `screen` did not exist, so rejecting breaks no existing caller. The command notes and the runtime comment both still described the fallback as `source: stream`, left over from renaming that value to `screen-unavailable` during implementation. The spec text is surfaced through `orca help` and the agent-context schema, so a caller following it would test for a value the code never emits. Both now describe all four states, including that an absent source means the host predates the field. |
||
|
|
9d1dfc314f |
fix(cli): resolve host names across both kinds, and stop ssh: answering empty (#15449)
* fix(cli): resolve host names across both kinds, and stop ssh: answering empty `--host ssh:<id>` was never validated. An unknown target filtered to nothing and returned ok:true with an empty list — the same silent wrong-machine answer that unknown `runtime:` ids gave before they were rejected. And because SSH target ids are machine-generated (`ssh-<timestamp>-<random>`) while the name anyone actually knows is the label, this fired on the ordinary spelling rather than a rare typo: every human-typed SSH name missed. The two kinds of remote machine are also reached on different axes. A paired Orca server is a connection (`--environment <name>`); an SSH target is a machine the connected host reaches (`--host ssh:<id>`). A caller only knows "the machine called X", so naming X on the wrong axis was the common failure and produced either an empty answer or a dead-end "unknown environment". Now: `ssh:` resolves labels as well as ids and rejects an unknown target with the known ones listed; `runtime:` accepts the environment name as well as its id, matching --environment, and canonicalizes to the id so stored host ids still compare; and when a name misses on one axis but exists on the other, the error says which and gives the exact flag. Candidates ride along in error.data so an agent can recover without parsing prose. `orca host list` is the discovery surface that was missing entirely — nothing in the CLI listed SSH targets, so a caller told to use one had nowhere to look. It prints this machine, the SSH targets registered on the connected host, and the paired servers, each with the selector to use. * fix(cli): give --environment the same cross-kind hint, and validate the ssh host on setup-create Two gaps a follow-up survey found in the first pass. `--environment openclaw` still dead-ended with a bare "Unknown environment" while an SSH target by that name sat right there — the inverse of the case just fixed, and the direction the report actually hit. The store's own error cannot carry the hint: translateStoreError forwards code and message and drops data. So the selector is resolved before the client is built, where the payload survives. Only the explicit flag is asserted eagerly; an ambient ORCA_ENVIRONMENT stays lazy, because failing local-only commands over stale background config would be a regression. `project setup-create` records independent metadata and, unlike the other setup paths, is not covered by the runtime's ssh rejection — so an unknown target persisted a row pointing at a machine that does not exist. It now resolves the host. `local` and `runtime:` still pass through untouched: this is also the provisioning path, where a runtime host legitimately may not exist yet when its metadata is written. `setup-existing-folder` and `setup-clone` deliberately keep the unresolved id. The runtime rejects every ssh host for those operations regardless of whether it exists, so resolving first would answer "no such target" and imply the command would have worked with the right id. * fix(cli): refuse an ambiguous host name instead of resolving the first match Name lookup took the first match while the environment store itself refuses an ambiguous name rather than guessing. That put the guess back, in the selector whose entire purpose is to stop a command reaching a machine the caller did not choose — and it applied to both spellings: two SSH targets sharing a label, and two paired servers sharing a name. Both now resolve to nothing and report every candidate with its id, so the caller picks. An exact id still resolves past a colliding name, since an id is never ambiguous. Also pins the property that makes accepting a name safe at all: `runtime:<id>` is a persisted token that lands in ProjectHostSetup.hostId and is embedded in generated setup ids, so the name is canonicalized to the id before anything downstream sees it. A test now asserts a name never reaches the wire. * fix(cli): fall back to the older ssh listing so an old host is not read as having no targets Hosts predating ssh.listTargetSummaries still answer ssh.listTargets, and both are served by the same summariser. Swallowing the method_not_found made such a host indistinguishable from one with no SSH targets registered, which would reject a target id that is valid there — a new-client/old-host regression on a path that previously passed the id through unvalidated. |
||
|
|
a61b39a9a6 |
fix(runtime): stamp a runtime's own project setups as local, and report remote status about the remote (STA-4792) (#15376)
* fix(runtime): stamp a runtime's own project setups as local, and report remote status about the remote (STA-4792) Two independent frame-of-reference bugs, both from code describing one machine while labelled as another. #15366 — projectHostSetup.* persisted the caller's host id verbatim. Those `runtime:<environment-id>` ids are minted by the calling client's own pairing store, so they name a machine only relative to that client. A client sending one is addressing this runtime, and runtimes do not proxy these calls onward, so the host it names is us. Storing the client's spelling made one machine look like a different host to every other client, hid its rows from them, and defeated the (projectId, hostId) duplicate check — two laptops paired to one server each created their own setup for the same checkout. Re-spell it as `local` at the RPC boundary. Rows written earlier keep their old stamp; readers already project `local` back to `runtime:<their-id>`, so the client-visible model is unchanged and no ids are rewritten. STA-4792 defect 4 — `status --environment <name>` hardcoded app.running:false to mean "no desktop on THIS machine" while every other field in the same object described the target, including a desktopWindowStatus echoed straight from it. The result contradicted itself and read as "that run was headless" when the remote GUI was up. `app` now describes the target, keyed off the one window status that requires a live renderer, and the result names its own subject so the frame can't be misread again. The remote pid is not knowable, so it stays null. STA-4792 defect 2 gets a regression test rather than a fix: routing already made the client remote, which is what stops a Windows destination being joined to the local cwd. The test pins the exact reported invocation. * fix(status): share the remote app projection with the SSH host passthrough, and name the version gap on project host setup Two review follow-ups. The SSH host passthrough answered `app.running: true` unconditionally for the Orca host a caller reached over SSH, claiming a desktop app even for a headless `serve`. That is the same defect as the paired-server path, one transport over, so the projection moved to shared and both now answer the question the same way. `--host runtime:<id>` routes project commands to a paired server, which means a client can reach a server that predates project host setup without meaning to. That answered a raw `method_not_found`, which reads as an Orca bug rather than a version gap; the CLI now names it the way the desktop already does. Reverted a third change: making the persistence duplicate check treat `local` and `runtime:*` as one machine. That assumption holds at the RPC boundary, where a `runtime:` host means the runtime being addressed, but not in the store, which also records independent provisioning metadata for machines that are not itself. An existing test covers exactly that, and it was right. The duplicate convergence therefore stays bounded to rows written after the normalization. |
||
|
|
3ffab9a6b3 |
feat(terminal): read the rendered screen with terminal read --screen (STA-4792) (#15380)
* feat(terminal): read the rendered screen with `terminal read --screen` (STA-4792) `terminal read` returns accumulated pty output with escape sequences stripped. That is the right answer for "what happened over time" and the wrong one for "what is on screen": any program that repaints a line comes back as stacked fragments, so one `clear` typed key by key reads as `cclclecleaclear`, and a prompt that draws a space by moving the cursor loses it. Nothing in the output said which question had been answered, so it was used as rendering evidence and produced false conclusions. The runtime already knew how to render — it replays the byte stream through a headless emulator — but only as a fallback for blank reads, alternate screen, and never-attached ptys. A normal attached terminal never reached it. `--screen` asks for it directly. Every read now reports its source, which also surfaces the pre-existing snapshot fallback that until now swapped rendered lines into an ordinary read with no indication. `screen-unavailable` distinguishes "asked for a screen, none could be rendered, here is the stream" from a stream the caller asked for, and an absent source means the host predates the field. Because an older host strips the unknown param and answers with its ordinary read, `--screen` against one fails with that explanation rather than passing the stream off as a screen. `--screen` and `--cursor` are mutually exclusive: a screen is the current frame and has nothing behind it to page. * refactor(terminal): stamp the screen source where rendered lines enter the read Inferring it from tail array identity worked but made a load-bearing contract out of reference equality; any later path spreading the read would silently mislabel. Rendered lines only enter through one builder, so it stamps there and anything still unlabelled is the stream. |
||
|
|
9b5538d786 | fix(runtime): scope create-with-activate navigation to the requesting client (STA-2802) (#15407) | ||
|
|
79be5b7fde |
feat(orchestration): report a worker blocked on a human prompt (STA-4513, STA-3714) (#15261)
* feat(orchestration): report a worker blocked on a human prompt (STA-4513, STA-3714) A lane parked on an approval, trust, or permission prompt looked exactly like a lane that was thinking or inside a long tool call. On origin/main, driving a real cursor-agent through Orca: surface running `sleep 60` awaiting approval worktree ps agents[].state working working terminal show / list no such field no such field terminal wait --for tui-idle satisfied: true satisfied: true worker-show no agent state no agent state The runtime already fuses hook state, OSC title, and matched prompt text into a `permission` verdict inside getTerminalAgentStatus — it was reachable only from the renderer, and it was blind to cursor-agent approvals. Two gaps, one boundary. Exposure: getTerminalInteractiveWait publishes that same fusion, minus the async foreground probe, as `agentWait` on `terminal show` and on `worker-show`'s observation. It carries the evidence that proved the wait (hook, prompt-text, or title) so a coordinator can weigh it. Null means no proof; a missing field means the host predates it — absence is never read as "not waiting". Detection: cursor-agent's hook set has no approval event and beforeShellExecution fires identically for auto-allowed commands, so its rendered menu is the only authority. Matched on the key-bound choices rather than the prose, requiring two, and self-clearing when the follow-up input line returns. Its live spinner title is exempted from the staleness rule that clears startup modals, because cursor keeps spinning while it waits. Falls out of routing it through the shared verdict: `dispatch --inject` into a cursor pane on an approval now refuses with agent_prompt_blocked instead of typing the preamble into the dialog. Fixtures are captured verbatim from cursor-agent 2026.08.11-e8db854 driven through Orca; the same case matrix was replayed live against a built runtime. terminal list stays untouched: its rows would each need a full tail scan, and STA-4694 owns the one-call-per-run aggregate. * fix(orchestration): only call a Cursor approval live while it owns the screen Independent review found the approval detector trusted one dismissal string, so any later output that did not contain cursor's follow-up line left the menu reading as a live wait. Reproduced: a tail of the real menu followed by two lines of ordinary output returned agent-approval-prompt, which fails tui-idle and refuses prompt injection on a healthy lane. Replaced with the structural property the string was standing in for: a live dialog owns the bottom of the screen, so the last choice may sit at most one line above the end of the retained tail. That tolerates a status footer or a partial line mid-redraw without admitting scrollback, and it drops the vendor prose. Being bottom-of-screen is also the dating this reason needed, so it no longer requires waitBlockedAt. A tail restored from terminal history carries none, and a lane parked on a prompt emits no bytes — so before this, an Orca restart made exactly the lane both issues are about go quiet for good. The startup modals keep the timestamp rule: their text lingers in scrollback with nothing to say whether it was answered. Also from review: - worker-show and federationShow reuse the verdict showTerminal already computed rather than rescanning the tail, so the two can no longer disagree. - The worker-show test now drives a real runtime, real PTY tail, and the real detector; it previously mocked getTerminalInteractiveWait, so it would have passed with detection permanently returning null. - The guard claim is now asserted against the guard: a blocked pane rejects both assertTerminalAgentSendable and sendTerminalAgentPrompt, and a working pane still passes. - Added a non-local (connectionId) pane case, since the verdict is derived from retained tail and title state on every host. * fix(agent-status): stop a hook wait from outliving its agent A third reviewer caught that the hook branch proved agent ownership from the pane title alone, while the shared verdict it claimed to reuse also probes the foreground process. A shell that takes a pane back usually sets something like `user@host: ~/repo`, which no title rule recognizes, and a hook row stays fresh for AGENT_STATUS_STALE_AFTER_MS — so a dead agent could be reported as waiting on a human for half an hour. Hook evidence now goes through getTerminalAgentStatus, which is the only thing that can answer whether an agent still owns this PTY. The two prompt branches skip it: a matched prompt is on the pane's screen now, so it proves itself. That makes the probe cost fall exactly where correctness needs it, and getTerminalInteractiveWait async, which only showTerminal had to absorb. Also trims the comments the same reviewer flagged as longer than the repo's rule. * test(agent-status): pin that a dead pane stops reporting a human wait A fourth reviewer noted the approval menu sits at the bottom of a dead pane's tail forever, and that no test covered process exit with no trailing output. The snapshot already refuses an exited pane, and worker-show gates agentWait on proven identity — this pins both so neither can drift into reporting a worker that needs intervention as one that needs an answer. * fix(orchestration): never report an unchecked worker as not waiting Automated review caught that the three worker paths which return before the wait is ever evaluated — unattached, missing, and identity_changed — then had their undefined coerced to null by the emitters. A worker whose process was replaced was reported as `agentWait: null`, which reads as "Orca looked and nobody is waiting" when Orca never looked. That is the false negative this field exists to remove. The field is now emitted only when it was evaluated, so a present null is a claim about the pane and an absent one means nobody looked — because the host predates the field, or the worker's identity could not be verified. The CLI and the worker-show note say that rather than blaming an old host. Covered on the context-only path, where the regression test fails against the previous behavior; the supervised and federated emitters take the identical one-line change. Also trims the two test-file headers to one statement of purpose. * fix(agent-status): tighten the Cursor menu match and stop guessing on unknowns Fourth review round, three findings, each reproduced before acting. Matching each choice marker with an independent lastIndexOf let text outside the menu carry the anchor. An agent narrating "next time I'll suggest Run Everything" after the menu was answered pulled the match down to the bottom of the screen and revived it. The match is now confined to the last lines of the tail, and a choice is a line that ends in the key that picks it — prose writes the same words but not the same shape. The one line of slack under the dialog went with it. It was a guess; every capture of a live dialog ends on its last choice, and one line is exactly enough room for that narration. A redraw caught mid-flight now reads as no wait until the next poll, which is the safe way to be wrong. The hook branch awaited a foreground probe that reaches a PTY controller which may be a remote host, so a wedged probe stalled every caller of showTerminal — a path that never probed before. It is bounded now, and a timeout leaves the wait unevaluated rather than claiming there is none. Which is the same distinction the previous commit only fixed one level up: getTerminalInteractiveWait itself turned an unreadable pane into `null`, so showTerminal published "looked, nobody waiting" for a pane it could not read. It returns undefined there, showTerminal omits the key, and worker-show's text output prints unknown rather than rendering it the same as none. * fix(agent-status): bound the wedged probe's cost and stop matching prose keys Fifth review round. No correctness defects in the shipped behaviour this time; two robustness holes and the documentation of the contract. The bounded probe abandoned the wait but not the request, so a coordinator watching a wedged remote host added one live probe on every poll. It is single-flighted per PTY now, the way the leaf-absence probe already is. The trailing-key rule that separates a menu row from the agent narrating a choice was written as a character class, and any lowercase run up to twelve characters satisfied it — "…suggest Run Everything (as before)" passed. Spelled out as key names instead, which also lets the glyph forms of those keys through. The contract wording said an absent agentWait meant an old host or an unverifiable identity. It also covers an unreadable pane and a probe that did not answer, and a reader diagnosing an old peer from that would be wrong. Corrected on the type, the worker-show note, and in docs/reference/remote-wire-compatibility.md, which had no entry for a field whose absent and null states mean different things. Also strengthens the worker-show agreement test, which compared the terminal and observation payloads without asserting either held the expected wait, so it passed when both were absent. |
||
|
|
4cc7e7859a |
fix(cli): route --host runtime:<id> to that server instead of answering locally (#15364)
* fix(cli): route --host runtime:<id> to that server instead of answering locally `--host` was only ever a local filter over whatever runtime the CLI happened to connect to, so `--host runtime:<id>` silently answered for (and mutated) the local machine. A real environment id and a made-up one were indistinguishable: both returned ok:true with an empty list and the local runtimeId in _meta, and `project setup-clone --host runtime:<id>` cloned into the caller's own machine. Resolve the flag before the client is built: unparseable host ids and runtime ids that no paired environment owns are rejected, and a known runtime id selects that environment as the connection (conflicting with --pairing-code or a different --environment is an error). Once routed, a host filter also accepts the runtime's own `local`-stamped rows, since both spellings name the machine we are now talking to. * fix(cli): close --host routing gaps found in review - Conflict-check an ambient ORCA_ENVIRONMENT, not just the --environment flag. `ORCA_ENVIRONMENT=staging orca ... --host runtime:<prod-id>` silently routed to prod while the flag spelling errored. An ambient pairing code still loses to the explicit flag, because it cannot be resolved to an id to compare. - Attach the known environment ids to the unknown-id error as `error.data`, so a --json consumer can retry without parsing prose, and say outright that runtime:<id> matches ids only and never environment names. - Fix four command examples that documented `--host runtime:gpu`. `gpu` is an environment name, so every one of them would now be rejected; use an id. - Cover the routed connection on `worktree create` and `automations create` (the mutating paths), the `--environment X --host local` filter-only case, and assert error.code/error.data rather than only substrings. * test(cli): pin execution-host-flag to the deferred error-class import index.ts now loads execution-host-flag.ts on every invocation, making it the sixth module on the --help path. It imports RuntimeClientError from ./runtime/types today, but nothing enforced that; switching it to the barrel would silently drag zod/ws/tweetnacl back onto --help, which is exactly what this guard exists to prevent. Verified the assertion fails when the import is flipped to the barrel. |
||
|
|
2a760e310b |
fix(computer): report unasserted accessibility actions (#15028)
* fix(computer): report unasserted accessibility actions * fix(computer): fail closed on missing action metadata * Fix merged tab search test fixture |
||
|
|
fc8b92e507 |
docs(computer): explain screenshot file requirements (#15054)
* docs(computer): clarify screenshot output requirements * fix(cli): do not advertise an unshipped --probe flag The capabilities help line referenced --probe, which does not exist yet; it ships in a later change. Advertising it here would be false until then. * fix(cli): align computer-use screenshot guidance * docs(computer): document inline screenshot fallback * docs(computer): keep screenshot summary accurate * docs(computer): keep screenshot guidance general |
||
|
|
64de8dd637 |
fix(workspaces): delete on the confirmed host, and make both hosts' rows selectable (STA-4343) (#15013)
* fix(workspaces): host-qualified workspace deletion (STA-4343, STA-4448) Squashed integration of PR #14606 + the codex review-loop output, replayed onto current main. Granular history preserved on brennanb2025/sta-4343-review-full. Fixes the regression from #13413: a workspace id is repoId::path with no host component, so the same repo at the same path on two hosts published one id for two workspaces, and deletion routed by that id landed on whichever host routing preferred - usually the ACTIVE one, not the row the user confirmed. - removeWorktree takes a REQUIRED host-qualified WorktreeRemovalTarget; omitting the host is a type error. All destructive callers migrated. - Projections dedup on (host, id), so two hosts render as two selectable rows while the createWorktree/fetchWorktrees race duplicate still collapses. - Ephemeral VM cleanup is host-scoped. It matched on bare workspaceId, so the host-scoped delete path destroyed the SURVIVING host's VM and its unpushed filesystem - a leak fix that had become data destruction. - Selection, keyboard routing, lineage grouping and Space rows carry host identity end to end; fixing the executor dedupe alone would have turned one-row intent into deleting both hosts. Files split to stay under max-lines rather than raising any cap. * refactor: split files that crossed max-lines The review-loop commits used --no-verify, so the pre-commit hook never enforced the caps. Extracted cohesive units rather than raising any limit: renderer teardown, delete-with-toast, pinned-group rows, host-scope helpers, workspace-kind predicates, filter actions, kanban drag selection, the renderer removal result type, and the native-chat persistence tests. * refactor(workspaces): extract cleanup deletion-phase selector Clears the last max-lines violation and the import-type side effect the changed-code gate flagged. * refactor(sidebar): track the delete-dialog extraction modules * fix(workspaces): preserve host identity across remaining surfaces * fix(sidebar): re-carry host through the rewritten palette result model #15170 replaced PaletteSearchResult while this PR was open. Re-applied the host qualification on top of the new model instead of taking either side: results carry worktreeHostId again, and the board filter keys its matched set on host identity rather than the bare id. Known gap, documented in the board test rather than deleted: searchWorktrees resolves evidence through a `documents` map keyed by BARE worktree id, so two same-id host rows collapse before this code sees them. Closing that belongs with the palette work. * test(cmd-j): pin the palette collision gap instead of asserting the old model The palette collision test asserted two host-qualified rows, which #15170's rewrite made unreachable: item ids are bare again and worktreeMap is id-keyed. Rewritten to assert what holds — activation always names a host — and to pin the defect it exposes: two same-id rows render on ONE command value, so React sees duplicate keys and a click on the first row activates the second row's host. That reproduces on main, so it is pre-existing, not from this PR. Pinned rather than deleted so fixing it must update this test. --------- Co-authored-by: QA <qa@local> |
||
|
|
0bedeea642 | fix(orchestration): expose unsupervised dispatch lanes (#15105) | ||
|
|
be07b43a2b | fix(orchestration): enforce honest recipient routing (#14964) | ||
|
|
04e7f5c805 |
fix(cli): relativize absolute POSIX paths against UNC worktree roots in WSL (#11406)
* chore: ignore worktrees directory * fix(cli): relativize absolute POSIX paths against UNC worktree roots in WSL * fix(cli): prevent double-prefixing UNC paths in WSL path normalization * fix(cli): gate the WSL path rewrite on a UNC worktree root WSL_DISTRO_NAME is also set for a plain Linux CLI inside the distro, where worktree roots are POSIX; prefixing there stranded every absolute path. Rewrite only when the root really is a WSL UNC path, and cover the legacy wsl$ alias, cross-distro paths, and the non-WSL case. * test(cli): pin WSL_DISTRO_NAME absent for the whole file-path suite Contributors run this suite inside WSL, where the inherited distro name would flip the rewrite on for every POSIX-root case. * fix(cli): never rewrite a Linux path that contains a backslash Backslash is a legal Linux filename character but a separator once the path reads as UNC, so `a\b.ts` relativized to `a/b.ts` — a different file. Such a path has no UNC spelling; let it fail the match instead. * test(cli): pin the WSL rewrite's negative space Sibling-prefix roots, Linux-tail case sensitivity, and Windows drive-letter workspaces all passed only by construction. * test(cli): pin the distro-case fold from the CLI side The negative-only case passed identically with the fold broken. * fix(cli): name the WSL distro from the invocation cwd when the env is absent WSL_DISTRO_NAME only reaches the CLI if interop forwards it across the PowerShell bridge, which nothing in the launcher guarantees. ORCA_CLI_CWD is set explicitly and its UNC form already names the distro. * test(cli): match the launcher's real cwd spelling and fix an over-claim wslpath -w emits a backslash UNC path; the fallback test now uses that shape. The aliasing test's comment described a state the || guard makes unreachable. * refactor(cli): spell the WSL rewrite with the shared toWindowsWslPath helper src/shared/wsl-paths.ts already owns "absolute Linux path in a known distro -> its Windows form" and has five production callers; the handler hand-rolled a fourth copy of the UNC template. Behavior is identical under the UNC-root guard, and passing distro as a real argument makes the null check a compile error rather than an untested branch. * test(cli): drop a WSL case that pins the guard shape and kills no mutant Deleting the distro null check left the case green — it asserts the same passthrough as 'does not rewrite when the CLI is not running under WSL'. The check is now enforced by the compiler instead. * chore: keep WSL path fix scoped --------- Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com> |
||
|
|
7afce2ea41 |
fix(ssh): stop reporting a confirmed kill when the SSH provider is gone (#14977)
* fix(ssh): stop reporting a confirmed kill when the SSH provider is gone A detached relay PTY is designed to outlive the provider that addressed it (it ignores SIGHUP and ships with an unlimited grace), so "the SSH provider is no longer registered" is lost contact, never evidence the remote process stopped. Both stop primitives in the PTY controller returned `true` from that branch, and every caller downstream reported the fabricated success: the CLI printed "PTY killed.", worker-stop settled the dispatch as stopped, and — because the stop "succeeded" — the unstopped-PTY gate never ran, so worktree removal walked straight past a live remote agent. `kill`/`stopAndWait` now still tombstone the local lease but report an unconfirmed stop and record why, using the three-verdict vocabulary the worktree teardown gate already spoke (`live` / `unverifiable` / `exited`), promoted out of that module into `src/shared/pty-liveness-verdict.ts`. The close receipt, the CLI wording, worker-stop and the removal gate all read that verdict instead of inferring an exit from silence. The same rule fixes the mirror-image defect: the aggregate inventory only enumerates registered providers, so a dropped relay clears `connected` for every remote PTY at once. The sweep now separates the provider answering "absent" (an exit) from no provider being able to answer (lost contact), so worker-stop stops claiming `exited` from a disconnect. The `connected` wire field is unchanged in meaning and shape. * fix(orchestration): apply the same honesty to the federation stop path The federation host runs its own copy of the worker observation and stop logic, with the same two defects: `inspectRemoteAttachment` read a dropped relay's `connected: false` as `exited`, and `federationStop` settled the dispatch as stopped from a close it never confirmed — relaying a fabricated success all the way home to the coordinator. Two guards also had to move so the honest verdict does not become a new refusal. `federationRead` gated on `status !== 'running'`, which would have rejected a connected terminal the moment a stop lost contact with it; it now gates on `status === 'exited'`, which is equivalent for every pre-existing status given the two guards beside it. Local `workerStop` likewise still attempts the close when the verdict is `unverifiable` — losing contact is a reason to report the outcome honestly, never a reason to stop trying. The show observations now carry the reason alongside the status, so a bare `unverifiable` is actionable. Both are new optional fields. * fix(ssh): preserve unconfirmed stop verdicts across consumers * fix(ssh): use canonical live verdict wording * fix(ssh): refuse wrong-host teardown verification * test(orchestration): confirm worker release teardown * fix(orchestration): negotiate honest worker stop receipts * fix(agent-teams): fence uncertain teammate respawns * fix(ssh): avoid duplicate missing-provider teardown * fix(orchestration): preserve archives across release retries * fix(ssh): preserve verdicts across synthetic kill exits * fix(ssh): preserve liveness evidence across teardown * fix(agent-teams): replace panes only after confirmed stop * fix(ssh): distinguish host exits from relay loss * fix(ssh): narrow concurrent inventory verdicts * fix(orchestration): serve archives after uncertain release * fix(orchestration): expose unverifiable read liveness * test(ssh): align liveness assertions with verdicts * fix(ssh): preserve host scope across inventory failures |
||
|
|
8ca4ed945e |
feat(terminal): report execution host and listing scope in terminal list (#14973)
* feat(terminal): report execution host and listing scope in terminal list `orca terminal list` returned rows with no host identity and no statement of what the listing covered, so a scoped listing that saw nothing read as "nothing exists anywhere" — an agent reported a live remote worker dead. Each row now carries an optional `executionHostId` derived from the PTY id (SSH and paired-runtime ids embed their owner), and the result carries an optional `hostScope` naming the hosts covered and the known hosts skipped. Both are surfaced in `--json` and in the human-readable CLI output, where an absent field renders as `unknown` rather than `local`. Both row builders route through one resolver, so the rule lives in one place. * fix(terminal): preserve unverifiable host scope * fix(terminal): fail closed on unverifiable hosts * test(terminal): name unverifiable scope explicitly * perf(terminal): keep graph hydration host scans narrow * fix(terminal): reject blank foreign host owners * fix(terminal): validate inferred inventory hosts * fix(terminal): preserve paired folder host scope * fix(terminal): keep inventory host inference typed * fix(terminal): disclose paired folder hosts |
||
|
|
fa9b20cb41 | feat(skills): reland private bundle sharing safely (#14934) | ||
|
|
763b1febeb |
Revert "feat(skills): add private bundle sharing (#14401)" (#14913)
This reverts commit
|
||
|
|
757fae28d7 |
feat(skills): add private bundle sharing (#14401)
Co-authored-by: E2E Test <e2e@test.local> |
||
|
|
d2ffe1f362 | fix(terminal): settle CLI prompts for Claude and Codex (#14608) | ||
|
|
9367169888 |
refactor(tests): split every oversized test file off the max-lines suppression list (#14728)
* refactor(tests): split oversized test files off the max-lines suppression list Every `*.test.ts`/`*.spec.ts` that carried an `eslint/oxlint-disable max-lines` directive is now split into focused, behavior-scoped suites that fit the 800-line test budget, with shared setup extracted into co-located `*-test-harness.ts` / `*-test-fixtures.ts` modules (300-line budget). 83 files became ~930; the largest output is 797 effective lines. `orca-runtime.test.ts` is intentionally untouched. Test bodies were moved by scripted line-range slicing rather than retyped, so assertions are byte-identical. The only permitted body edits were mechanical rebinding where a shared value moved into a harness (e.g. `tmpHome` -> `homes.tmpHome`). Registries that enumerate test files were updated in lockstep: - config/max-lines-baseline.txt: pruned 341 -> 258 entries (all 83 removed). - config/reliability-gates.jsonc: 33 gates repointed at the split files, with assertionRefs split per file where a gate's coverage now spans several. - .github/workflows/pr.yml: the real-zsh lane now lists the 4 split files that actually exercise zsh, so they keep running in the dedicated shell lane. Also renamed agent-hooks `server-test-fixtures.ts` to `server.test-fixtures.ts` so the global-fetch call-site audit keeps skipping it, and added `.js` extensions to the CLI suites' dynamic harness imports (node16 resolution) to unbreak `build:cli`. Verification: full suite 52,449 passing vs 52,448 at baseline with zero assertions lost; `pnpm lint`, `pnpm typecheck`, and `pnpm build:cli` all exit 0; the terminal-pane e2e spec runs 31/31 headless. * refactor(tests): split hook-idle arbitration suite that oxfmt pushed over budget The pre-commit oxfmt pass reflowed pty-connection-hook-idle-arbitration.test.ts to 811 effective lines, 11 over the test budget. Split the hook-completion side effect and replacement-agent veto cases into their own suite; both files now sit well under the cap and the 15 tests are unchanged. * test: port upstream test changes into the split files after rebase Rebasing onto main surfaced 27 tests that main had added to files this branch deleted, plus edits to tests that had already moved. Taking the deletion side of those modify/delete conflicts would have dropped that coverage silently, so each upstream change is ported into the split file that now owns the behavior — for example main's six orchestration mailbox tests land across orchestration-runs, -send, and -check. Also repoints `orchestration.notification-mailbox-consistency`, a gate main added after this branch's gate remap, at those same three split files, and re-prunes the max-lines baseline against main's (257 entries). Verified: all 27 upstream test titles present; full suite 52,761 passing with the only diff vs baseline being 12 tests main itself removed and 3 that moved from skipped to passing; lint and typecheck exit 0. * fix(test): flush pending continuations before tearing down terminal test globals CI shard 5/16 failed on both Node 24 and 26 with `ReferenceError: window is not defined` from pty-connection.ts, surfacing through pty-connection-daemon-snapshot-replay.test.ts. The reattach/settle chains `await` a real promise and then touch `window.api`. Under fake timers those continuations cannot run, so they only become schedulable once restoreTerminalTestGlobals() switches back to real timers — which previously happened immediately before `delete globalThis.window`, so a late continuation threw and failed the whole file. Flush async ticks in that window instead. This is latent in the source rather than new: the pre-split 25k-line file kept running other tests after these, which gave the chains time to settle before teardown. Splitting the file moved teardown directly behind them. * fix(test): keep an inert window after terminal test teardown instead of deleting it The async-tick flush was not enough: the reattach/settle chain can resolve after teardown regardless of how long we drain, so CI shard 5/16 still failed with `ReferenceError: window is not defined` from pty-connection.ts. A real renderer never loses `window`, so deleting it was the artificial part. Swap in an inert proxy whose properties resolve to callables and whose calls resolve to undefined, making a late `window.api.pty.*` call a harmless no-op. The next test replaces it wholesale via installTerminalTestGlobals(), and no test asserts that `window` is absent. |
||
|
|
66dfdc456f |
feat(computer-use): support macOS middle click and stop the silent left-click fallback (#14721)
* feat(computer-use): support macOS middle click and gate the AX click path `--mouse-button middle` already validated end-to-end through the CLI, the zod schema, and the provider validator, and both the Windows and Linux providers honored it. Only the macOS provider rejected it outright with "middle-click is not yet supported", so the flag was a dead end on the one platform that has no fallback. Two changes: - Add `.middle` to the macOS button mapping. macOS has no dedicated middle event family, so it rides `otherMouseDown`/`otherMouseUp` with the button number carried by `mouseButton: .center`; that constructor argument is honored for exactly the `otherMouse*` types, so no extra field write is needed. - Validate the requested button before the accessibility fast path, and skip that path for buttons it cannot express. Previously the raw string was read unvalidated, and `performClickAction` only special-cased `right`, so `click --mouse-button middle --element-index N` (no modifiers, count 1) fell through to `AXPress` — a left click — and reported success with `path: "accessibility"`. Any unrecognized button string did the same. This matches guards the Windows and Linux providers already had. The button enum moves into `OrcaComputerUseMacOSCore` so it is unit-testable; `main.swift` keeps only the CoreGraphics mapping. Also documents `--mouse-button` in the computer-use skill guide, which never mentioned the flag, so agents on Windows and Linux had no way to discover it. * test(computer-use): cover macOS middle click in the real-desktop e2e suite * test(computer-use): prove macOS middle-click delivery |
||
|
|
78d5920446 |
fix(orchestration-cli): point dropped mutations at --retry-request (#14586)
* fix(orchestration-cli): guide dropped mutations to idempotent retry * test(orchestration-cli): preserve read-only drop message * fix(orchestration): harden mutation replay identity * fix(orchestration): preserve replay across remints * fix(orchestration): defer local mutation identity |