mirror of
https://github.com/stablyai/orca.git
synced 2026-09-23 00:02:29 +00:00
8a07bbd8cf58dcaca62868101627eddc6feeb0ac
92
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
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. |
||
|
|
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. |
||
|
|
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. |
||
|
|
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. |
||
|
|
0bedeea642 | fix(orchestration): expose unsupervised dispatch lanes (#15105) | ||
|
|
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> |
||
|
|
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 |
||
|
|
537864a248 |
Fix Codex hook trust before manual shell launches (#14326)
* fix codex hook trust before shell launch * fix packaged cli preflight dependency * fix codex shell preflight safety * fix Codex shell preflight settings and startup safety |
||
|
|
45c1cb979a |
fix(orchestration): release context-only dispatches (#13376)
* fix(orchestration): release context-only dispatches Refs #13005 * test(orchestration): align PTY readiness timeout --------- Co-authored-by: Jinwoo-H <Jinwoo-H@users.noreply.github.com> |
||
|
|
c991bb27d3 | Add account-backed artifact sharing (#13012) | ||
|
|
b0ba51831c | Add per-worker model and effort overrides (#12851) | ||
|
|
0ce108d935 |
fix(browser): add native-UA session profiles (#12608)
* fix(browser): add native-UA session profiles * test(browser): add Google sign-in UA probe * fix(browser): preserve native profile UA identity |
||
|
|
39c3c58d55 |
perf(runtime): gate terminal.list visual layouts (#12450)
* perf(runtime): gate terminal.list visual layouts and stop the false writable claim visualLayouts is ~31% of a large terminal.list payload (44,208 B of 137,412 B on a live 134-terminal remote runtime) and has exactly one consumer: the human-readable CLI formatter. Gate it behind an includeVisualLayouts request param that defaults to included, so pre-flag clients are unaffected, and have every --json/internal caller opt out. Also drop the record-backed builder's writable, which was a verbatim copy of connected. terminal.show now states writability explicitly as exactly what terminal.send's PTY gate enforces. * test(runtime): type the payload-size fixture arrays for tsc * fix(runtime): preserve terminal list compatibility * test(runtime): guard terminal list optimization * fix(cli): preserve agent access to terminal layouts |
||
|
|
f4b2b782b5 |
feat(orchestration): coordinator-driven release of settled worker terminals (STA-905) (#12355)
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com> |
||
|
|
1c8908b791 |
Fix orchestration gate authorization to scope by Run binding (#11802)
* fix(orchestration): gate methods route calls to the caller's Run with `f Gates are Run-scoped state; every gate command now resolves the caller's active Run (via pane binding or explicit --from flag) and authorizes within that Run's scope. Settled adopted work no longer requires --takeover-legacy, and the legacy coordinator fence respects both binding-based and attestation-based proof of authority. * fix(orchestration): gate methods route calls to the caller's Run with at Gate and run methods now verify that declared terminal handles match the caller's attested identity, preventing spoofing of other coordinators. Extracted shared `resolveRunScope` to enforce one authorization rule across all orchestration mutations. Added comprehensive regression tests for #11745. |
||
|
|
650dd48ec9 |
feat(cli): add orca account add / account list for headless hosts (Claude + Codex) (#9177)
* feat(cli): add `orca account add` / `account list` for headless hosts The desktop "Add account" UI is disabled when the renderer drives a remote runtime (isRemoteAccountScope === kind:'environment'), so a headless server reached from a remote desktop/web client has no way to register managed Claude accounts. Add a host-local CLI path that reuses the existing capture logic: - ClaudeAccountService.addAccountFromConfigDir(): register a managed account by capturing credentials from an already-authenticated CLAUDE_CONFIG_DIR instead of spawning the interactive browser login (extracted persist/rollback helpers shared with the existing add flow) - RPC accounts.addClaudeFromConfigDir, bridged via OrcaRuntime; rejected for mobile device tokens (host-local only) - `orca account add` runs `claude login` in the user's own terminal into a temp CLAUDE_CONFIG_DIR, then registers it via the local runtime; `orca account list` lists managed accounts Switching (select) already works from a remote client; only adding was blocked. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(cli): support Codex in `orca account add` / `account list` Mirror the Claude headless-account CLI for Codex: - CodexAccountService.addAccountFromHome(): register a managed Codex account by importing auth.json from an already-authenticated CODEX_HOME, reusing a shared persist helper extracted from doAddAccount (no interactive login spawned here) - RPC accounts.addCodexFromHome + OrcaRuntime.addCodexAccountFromHome bridge, rejected for mobile device tokens (host-local only) - `orca account add --agent claude|codex` (default claude); `orca account list` now renders both Claude and Codex managed-account blocks Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * test: cover headless account-add capture paths (Claude + Codex) - ClaudeAccountService.addAccountFromConfigDir: registers a managed account by capturing an authenticated CLAUDE_CONFIG_DIR; rejects and rolls back when the dir has no .credentials.json - CodexAccountService.addAccountFromHome: imports auth.json from an authenticated CODEX_HOME into a managed account; rejects when auth.json is missing Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix: address CodeRabbit review on headless account-add flows - CLI login spawn uses a shell on Windows so `.cmd` agent shims resolve without ENOENT (args are fixed literals, no injection risk) - Claude capture skips the `.credentials.json` precheck on macOS, where creds live in the Keychain and captureAuthFromConfigDir reads them - Claude add rollback is best-effort: a failed rematerialization no longer skips managed-auth cleanup or masks the original add error - Codex persist restores the prior account/selection if a post-write sync or rate-limit refresh fails, so a failure can't leave a dangling managed account - Codex sync passes the account's selection target (correct runtime for WSL) - Add JSDoc to the new public service methods and CLI functions Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(cli): harden headless account capture * fix(cli): correct account command flag surface and interrupt cleanup - `account` commands no longer accept or advertise the browser `--page` flag; `supportsBrowserPageFlag` allow-listed them by omission, so `orca account list --page x` was silently accepted and `--help` rendered a browser-only option - account specs declare GLOBAL_FLAGS, so `--help`/`--json` render in the Options block like every other command - `--agent` on `account add` documents the account provider instead of the terminal TUI-agent meaning inherited from the shared flag table - a SIGINT/SIGTERM during the interactive login now removes the temp login dir (and restores the macOS Keychain item) before exiting 130; Node terminates without unwinding `finally`, which stranded live OAuth credentials on disk * perf(cli): stop `account list` forcing a provider usage refresh `accounts.list` awaited refreshAccountsForMobile(), which runs fetchAll({ force: true }) — bypassing both the poll throttle and the per-provider Retry-After gate — then O(N) serial per-account round trips. `orca account list` renders only emails and the active ids, so all of that work was discarded. The RPC now takes `refreshUsage` (default true, so mobile and web keep the forced lane) and the CLI opts out. Older hosts declare `params: null` and ignore the field, so a newer CLI degrades to the previous behavior rather than failing. Also documents on `account list` that `--environment` does not retarget it, matching the host-local behavior of shouldIgnoreRemoteSelection. * fix(cli): survive repeated and hangup signals during account add withInterruptCleanup latched cleanup behind a boolean, so a second signal got an already-resolved promise and its process.exit fired while the first cleanup was still inside a Keychain call (3s each) — the temp dir's OAuth credentials and the swapped macOS Keychain item both survived. Memoize the cleanup promise so every signal awaits the same run, and register with `on` instead of `once` so a second Ctrl-C cannot fall through to Node's terminate-immediately default mid-cleanup. Handle SIGHUP too. This flow exists for headless/SSH hosts, where the most likely interrupt is the connection dropping, which hangs up the login's terminal and previously ran no cleanup at all. Warn when the interrupt lands after sign-in completed: the runtime finishes the add independently of this process, so exiting 130 silently would tell the user it was cancelled when the account may exist. Reject a valueless `--agent`; the parser turns it into boolean true, which silently ran a full OAuth login for Claude when the user asked for another provider. Also lock two behaviors the refactor changed but left uncovered: a WSL Codex add must sync the WSL runtime lane rather than the default host lane, and rename the account-spec help test to describe the Options block it actually asserts rather than the usage string it never reads. * fix(build): bundle the main modules the account CLI imports electron-vite cleans out/main and emits only its declared entries, and `build:desktop` runs it after `build:cli`, so the tsc-emitted copies of `claude-accounts/keychain`, `codex-cli/command` and `win32-utils` were deleted before packaging. Both `orca account add` and `orca account list` then died at require time with "Cannot find module '../../main/claude-accounts/keychain'" — reproduced against a real `--serve` host. `agent-hooks/managed-agent-hook-controls` already carried an entry for exactly this reason; these three were missing. Adds a parity test so any future CLI import of a `src/main` module fails in CI rather than at a user's shell after packaging. * test: cover the desktop add-path behavior this PR changes Both changes ride in the persist/rollback helpers the existing GUI add flow shares with the new headless path, and neither had coverage: - Claude: rollbackAddAccount now guards forceMaterializeCurrentSelection- ForRollback, so a rejecting rematerialization no longer replaces the real add error nor skips safeRemoveManagedAuth. Asserts the original error surfaces and the throwaway auth dir is gone. - Codex: the desktop add now passes the account's selection target to syncForCurrentSelection, matching reauthenticate and select. Asserts the host target alongside the existing WSL assertion. Both fail when the corresponding change is reverted. * fix(cli): close the remaining account-add interrupt and preflight gaps The round-1 interrupt fix detached the signal handlers before running the finally-path cleanup, so the very window it was meant to protect — the two serial 3s `security` calls plus rmSync on the success/error path — was still covered only by Node's terminate-immediately default. Both review lanes reproduced it independently. Await cleanup first, detach in a nested finally, and stop a cleanup failure from replacing the error that actually explains why the add failed. Do not burn the interactive login when the runtime is unreachable. The RuntimeClient is lazily constructed and the first call was the registration RPC itself, so "Requires the Orca runtime to be running" was discovered only after the user completed a full OAuth round trip. Preflight with the now-cheap `accounts.list { refreshUsage: false }`. Reject `--environment` / `--pairing-code` on `account add`. shouldIgnoreRemoteSelection pins account commands to the local runtime, so `orca account add --environment homelab` silently registered the account on the laptop instead of the headless host it names. Survive a daemon that cannot spawn `claude`. `allowFailure` is honored in onClose but not onError, and unlike the GUI flow nothing has run `claude` in the daemon before this point — so a launchd/systemd daemon with a minimal PATH hard-failed an add the user had already signed in for, even though identity resolves fine from the config dir's oauthAccount. Also align the `--agent` help description with the global flag column. * fix(cli): reject runtime selectors on `account list` too `orca account list --environment homelab` was accepted and silently listed the LOCAL machine's accounts, because shouldIgnoreRemoteSelection pins account commands to the local runtime. Documenting that in --help does not reach someone who already typed the flag, and answering with the wrong host's accounts is the specific wrong answer they would act on. `account add` already errors; this makes the new command group internally consistent. The other groups in shouldIgnoreRemoteSelection keep their existing silent-ignore behavior — changing those is not this PR's job. * test: harden account-add signal tests and cover cleanup failure - Identify the handler under test by set difference instead of `process.listeners(sig).at(-1)`. Vitest installs its own once-wrapped SIGINT teardown, so the positional lookup could grab the wrong listener; the helper also asserts exactly one new listener was added. - Mock rmSync while keeping the real implementation by default, so the temp-dir assertions elsewhere stay honest. - Cover that a cleanup failure in the `finally` does not replace the error explaining why the add failed. Fails when that guard is removed. Completes the review loop's final round; the loop died on an API error before it could commit this, and its `import()` type annotation would have failed oxlint. * fix(cli): harden interactive account add * test(cli): make account cancellation coverage portable * fix(cli): preserve merged skills runtime modules --------- Co-authored-by: Dominik <marketing@gavaplast.sk> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com> |
||
|
|
676ef7fab8 |
feat(cli): add orca skills install and orca skills update for headless skill setup (#9201)
Adds `orca skills install` and `orca skills update` so skills can be set up without the GUI — SSH hosts, containers, CI. Previously `orca skills` had only `list` and `get`, so there was no headless path. **Agent targeting is scoped explicitly rather than delegated to detection.** The `skills` CLI decides which agents to install into, and with `-y` and zero detected agents it takes `targetAgents = validAgents` — all ~75. That is not a corner case for a headless CLI: a fresh SSH box or container with no agent installed is the normal starting state. Measured on a bare host, the unscoped command created **52 top-level agent directories and 54 junctions** (one real payload in `~/.agents/skills`, the rest links) on Windows, and 52/53 on macOS. The CLI now passes `--agent` derived from Orca's own detection, mapped to the `skills` key namespace, plus `universal`. Supplying `--agent` makes `runAdd` use it directly and never call `detectInstalledAgents()`, so the fan-out branch is unreachable. On a bare host it now refuses with `No coding agent detected on this host` and exit 1, creating nothing. Same command with scoping: **1 directory, 0 junctions.** `universal` alone would under-install — Claude Code is not in that set, and 19 of 28 mapped keys write agent-private homes `universal` never touches. `--agent '*'` is the bug itself. The mapping is hedged three ways: `null` for any agent whose key could not be confirmed, `satisfies Record<TuiAgent, …>` so a new Orca agent is a compile error, and a test pinning every mapped key against the CLI's own valid list. Fixed during review — two holes that each restored the full fan-out through a different door: - `--agent ','` trimmed to nothing, which skipped the refusal *and* emitted no `--agent`. - `--agent -y` passed an emptiness check, and the vendor CLI silently drops `-`-leading values, re-emptying its list. The real invariant is argument *shape*, not emptiness, and it is now enforced at the choke point in `buildAgentFeatureSkillInstallArgs`, so no caller can emit `-y` without a usable target. `*` remains allowed — asking for every agent explicitly is a choice, not an accident. Verified with 51 hostile inputs through the built binary, each recorded argv replayed through the vendor's own parser. Also fixed: the `ORCA_CLI_CWD` refusal now runs before target resolution (it was quoting the wrong host's agent list), and `--dry-run` is refused in a forwarded shell rather than printing a command naming the wrong machine. Validated on a real Windows host across PowerShell 7, PowerShell 5.1, cmd.exe and Git Bash: `.cmd` shims route through `cmd.exe` and `.exe` shims spawn directly (proved with instrumented shims, not inferred), the ENOENT path produces an actionable error rather than a silent failure, and `skills update` genuinely restores a corrupted skill byte-for-byte. Known, not addressed here — both upstream behaviours this only forwards: a partial install failure exits 0, and "no installed skills found" exits 0. Both are invisible to the headless callers this feature exists for. Co-authored-by: scastanoh21 <scastanoh21@gmail.com> |
||
|
|
64a1269409 |
perf(orchestration): bound mutation ledger and run pages (#11432)
* perf(orchestration): bound mutation ledger and run pages Co-authored-by: Orca <help@stably.ai> * fix(orchestration): close retention pagination gaps * fix(orchestration): preserve unpaginated run listing Co-authored-by: Orca <help@stably.ai> * fix(orchestration): reject malformed run cursors --------- Co-authored-by: Orca <help@stably.ai> Co-authored-by: Jinjing <6427696+AmethystLiang@users.noreply.github.com> |
||
|
|
d0f341ad69 |
fix(computer-use): make modifier clicks interruption-safe (#11451)
* fix(computer-use): make modifier clicks interruption-safe * fix(computer-use): pace modified Windows multiclicks * fix(computer-use): address modifier safety review |
||
|
|
363e478909 |
fix(orchestration): preserve active workers across updates (#11271)
* fix(orchestration): preserve active workers across updates * test(ssh): model absent legacy adoption * test(orchestration): align compatibility contracts * fix(windows): escape updater PowerShell booleans * fix(windows): restore stock uninstall process check * fix(orchestration): keep recovery off renderer startup barrier * fix(orchestration): harden legacy recovery migration * fix(orchestration): close recovery review gaps * fix(orchestration): complete legacy worker cutover recovery * fix(orchestration): preserve legacy workers across updates --------- Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com> |
||
|
|
3baffb49ff |
fix(runtime): refuse SSH hosts in project setup instead of acting locally (#10799)
* fix(runtime): refuse SSH hosts in project setup instead of acting locally projectHostSetup.clone and .setupExistingFolder threaded executionHostId all the way down but never used it for routing: cloneRepo runs a local mkdir plus a local gitSpawn, and addRepo probes the path with existsSync/statSync. An `ssh:` host therefore cloned and validated on the *local* machine and then registered the result as living on the SSH host. It only failed loudly here because the remote path did not exist locally. With a plausible destination the clone succeeds and writes a setup record pointing at the wrong machine. Nothing legitimate sends `ssh:` to these RPCs: the renderer maps every ssh host (including ephemeral-VM `ssh:runtime-ssh-*`) to the desktop IPC path, which dispatches to addRemoteRepoFromPath/cloneRemoteRepo, and the IPC handler symmetrically rejects `runtime:`. Only the CLI can reach here with `ssh:`. Fail closed until the RPC learns to route through the SSH providers. * test(runtime): make the SSH guard test observe the corruption it names The test asserted `gitSpawn` was never called and no repo was registered, but neither assertion could fail. `/home/brennan` is unwritable on macOS, so the pre-guard clone died at `mkdir` before reaching `gitSpawn`, and `/home/brennan/orca` failed `isGitRepo` before reaching `addRepo` — the exact side effects under test were unreachable either way. `rejects.toThrow` also aborted the test before those lines ran. Use a real temp destination and a real temp git repo, await both calls via `.catch`, and assert the side effects before the wording. With the guard disabled the test now fails on `gitSpawn` being called once with a real `git clone`, and on a repo registered stamped `executionHostId: 'ssh:openclaw'` — the silent local-clone-recorded-as-remote defect itself. `gitSpawn` is stubbed so a regression records the call instead of hitting the network. Also document the SSH restriction on `project setup-existing-folder`, which the guard now rejects. `setup-clone` already carried that note; its sibling did not. |
||
|
|
cd05f2ff93 | Implement robust orchestration primitives and connected-server workers (#9925) | ||
|
|
108a2ad41b | fix(cli): relativize absolute --path for file open and file diff before the runtime RPC (#9429) (#9824) | ||
|
|
4a9affd6e5 |
fix(emulator): iOS ax via plain-JSON serve-sim helper (supersedes #10007) (#10029)
* Revert "Enable accessibility tree (`ax`) command on iOS emulator sessions (#10007)"
This reverts commit
|
||
|
|
43ae014a64 |
Enable accessibility tree (ax) command on iOS emulator sessions (#10007)
* Enable accessibility tree (`ax`) command on iOS emulator sessions Fetch the accessibility tree from serve-sim's /ax endpoint, which requires an active session but provides the same UI snapshot capability as Android's uiautomator output. Derive the endpoint from the stream URL when not explicitly provided by the helper, and route through the bridge to pass session context to the backend. * Add ax command routing and backend integration tests Tests verify accessibility tree routes through EmulatorBridge, Android backend ignores iOS-specific ax URLs, and ax endpoints are derived from serve-sim stream URLs. |
||
|
|
34c160442f | Fix headless Linux serve pairing readiness (#9785) | ||
|
|
f1c84d3858 | refactor(cli): split oversized command modules (#9775) | ||
|
|
a10a2ba53c |
feat(linear): add MCP-style save issue (#9670)
* feat(linear): add MCP-style save issue * fix(linear): harden save issue parity * fix(linear): close save issue contract gaps * docs(linear): bundle project discovery with save issue |
||
|
|
87af1c8673 |
feat(linear): add complete issue relations (#9674)
* feat(linear): add complete issue relations * fix(linear): harden relation reads and writes * fix(linear): classify ambiguous relation writes |
||
|
|
42a4f017b4 | feat(linear): add MCP-compatible issue listing (#9672) | ||
|
|
be066fe8e9 | feat(linear): expose issue activity history (#9667) | ||
|
|
319ae4e9ea |
fix(terminal): make whole-tab close durable (#8958)
Co-authored-by: Orca <help@stably.ai> |
||
|
|
31f643ca42 |
Add version-matched skill guides to the CLI (#8624)
* Add version-matched bundled skill guides * Clarify skill freshness rollout PRs * Add canonical skills show alias * fix(skills): address guide review feedback * fix(skills): make guide commands cross-platform * fix(skills): apply the ORCA convention to the emulator guides Review follow-up: the emulator guides still instructed literal `orca emulator ...` in sh fences with no Linux disambiguation, so on unmanaged Linux they could launch the GNOME screen reader — the exact failure the executable-selection preamble prevents. Both emulator guides now carry the preamble and ORCA placeholder across fences, tables, and prose, and the cross-platform safety test covers all four converted guides. Also replaces computer-use's "unless a block names a shell" carve-out, which contradicted its own POSIX example, with the unconditional placeholder rule. |
||
|
|
52388c1ab7 |
Fix worker done pane identity (#8473)
* fix(orchestration): bind lifecycle sends to dispatched pane * fix(orchestration): bind injected worker messaging * Fix worker_done pane identity gating and rename SSH linear CLI files - Lifecycle reconciliation now returns an explicit `rejected` action (instead of `ignored`) for persisted sender_not_assignee markers, and only treats a payload's `_orcaLifecycleRejection` as trusted state when it exactly matches that reserved shape, preventing caller- supplied markers from spoofing a rejected send as success. - SSH legacy fallback CLI now fails closed with `no_active_sender_terminal` when a worker_done/heartbeat send has no resolvable sender identity, rejects mixed raw/structured payload flags, and reconstructs structured lifecycle payload fields (task/dispatch id, files-modified, report-path, phase) instead of dropping them — extracted into ssh-remote-orchestration-send.ts. - Renamed ssh-remote-linear-argument-error.ts to ssh-remote-cli-argument-error.ts since RemoteCliArgumentError is now shared beyond the Linear CLI. * Add re-read logging for already-converted lifecycle rejections Send-path reconcile converts worker_done/heartbeat rejections with a no-op logger, so the coordinator's later re-read is the only chance to surface the rejection message. Log it there instead of dropping it. |
||
|
|
26934b11bf |
fix(orchestration): complete tasks on worker_done + coordinator UX fixes (#8030)
* fix(orchestration): complete worker tasks and improve coordinator UX
* Fix orchestration lifecycle sender resolution and peek/check compat hand
- Lifecycle sends (worker_done/heartbeat) now use ORCA_TERMINAL_HANDLE
verbatim, skipping the liveness probe and pane remint that could
block delivery during restarts or mismatch stale-runtime assignee
handles.
- --peek now round-trips as {peek:true, unread:false} so older runtimes
that strip unknown params degrade to non-destructive "all" instead of
mark-read, with client-side filtering to restore peek semantics and a
clear error when --peek --wait can't be honored.
- Reject combined read-mode flags (--unread/--peek/--all) before calling
the runtime.
- Distinguish suppressed (already-consumed) lifecycle messages from
ignored ones so send doesn't wake --wait waiters for stale heartbeats.
- Fix task summary truncation to avoid splitting UTF-16 surrogate pairs
and to not misreport whitespace normalization as truncation.
* Add shared helper to abbreviate orchestration task specs for brief listi
- Normalizes whitespace and caps spec length at 160 chars, flagging
truncation separately from whitespace-only changes
- Truncates on UTF-16 code point boundaries to avoid splitting
surrogate pairs and emitting malformed strings
* Add pane-key identity to worker_done/heartbeat reconciliation and server
- Records the sender's pane key on messages and dispatch contexts so
worker_done/heartbeat ownership can be verified by the remint-stable
pane leaf instead of the terminal handle, which is reissued across
restarts.
- Rejects lifecycle messages from a genuinely foreign pane while still
tolerating handle remints, tab break-outs, and older CLIs that lack
pane identity.
- Moves task-spec abbreviation server-side (orchestration.taskList
--brief) so full specs no longer cross SSH/relay transports, with a
client-side fallback for older runtimes; consolidates the shared
abbreviation helper under src/shared.
- Adds a stderr warning when a pre-peek runtime's --peek response hits
the 100-row cap, since older unread messages may be missing.
* Isolate ORCA_PANE_KEY in CLI test beforeEach to fix leaked senderPaneKey
Co-authored-by: Orca <help@stably.ai>
* Fix pane-key remint bypassing dispatch mutual-exclusion lock
- Dispatch locking only matched on assignee_handle, so a reminted
terminal handle (tab break-out) could open a second concurrent
dispatch on the same pane.
- Add leaf-UUID-based pane key comparison (parsePaneKey) as a
secondary lock, falling back to exact handle match for legacy
rows without pane keys.
* Update orchestration skill docs for lifecycle authority and CLI flag add
- Clarify that dispatch lifecycle is tied to taskId+dispatchId verified against
the dispatched pane, not the terminal handle, since handles can be reminted
after restart
- Document new `check --peek`/`--all` and `task-list --brief` flags, with
fallback guidance for older CLIs that reject them
- Note that a valid worker_done auto-completes the task/dispatch, so workers
shouldn't also call task-update manually
---------
Co-authored-by: Orca <help@stably.ai>
|
||
|
|
3090ff0edb |
fix(runtime): explain full worktree id selectors (#7432) (#7892)
* fix(runtime): explain full worktree id selectors (#7432) * Fix full worktree id selectors for bare repo ids and doc guidance - Reject bare repo-id selectors up front via a shared validator instead of relying on worktree-list scanning, so RPC callers no longer trigger an unnecessary rescan just to detect the mistake - Propagate the structured worktree_id_requires_full_path code through RPC error mapping so callers get a typed error, not just a message - Update orca-cli, orca-emulator, and orchestration skill docs to show the full `<repo-id>::<path>` id shape and stop implying a bare repo id is a valid worktree selector --------- Co-authored-by: Jinjing <6427696+AmethystLiang@users.noreply.github.com> |
||
|
|
9de1fb8d16 |
Cli destructive suggest (#8352)
* fix(cli): don't recover benign typos into destructive commands CLI did-you-mean ranked purely by Levenshtein, so `orca worktree move` sole-suggested `orca worktree remove` (distance 2) — an alias of the destructive `worktree rm`. Suggestions also flow into --json error.data.nextSteps, the agent recovery channel, so a blind retry could delete a clean worktree. Make destructiveness a declared property of the command instead of a verb heuristic: add `destructive?: true` to CommandSpec and mark the irreversible commands (worktree rm, environment rm, automations remove, project setup-delete, tab profile delete, cookie delete, storage local/session clear). The suggestion ranker excludes destructive candidates unless the input token is itself a near-miss (distance <=1) of a destructive verb, so `worktree remov` still recovers `rm`/`remove` while `worktree move` no longer does. The guard tracks the registry, so it also covers destructive verbs outside the delete family (e.g. kill). Fixes #6303 Co-authored-by: Orca <help@stably.ai> * fix(cli): use Array.at(-1) to satisfy oxlint prefer-at Co-authored-by: Orca <help@stably.ai> --------- Co-authored-by: Orca <help@stably.ai> |
||
|
|
e2b4bc2c2c |
feat(cli): make the CLI self-correcting and self-describing for agents (#6303)
* feat(cli): make the CLI self-correcting and self-describing for agents Agents build a generalized model of how CLIs work and apply it to every tool. When orca diverged — `rm` where git uses `remove` — a reasonable first guess (`orca worktree remove`) dead-ended on a bare "Unknown command" with no path forward. This makes the CLI degrade gracefully when the orca-cli skill isn't loaded in context. - First-class CommandSpec.aliases, resolved to the canonical path before dispatch (no new handler registrations). `worktree remove`/`delete` now resolve to `rm`; the ad-hoc `terminal focus` duplicate spec/handler is migrated onto the mechanism. - Did-you-mean suggestions on unknown commands and unknown flags, ranked by edit distance over the live registry, surfaced in both stderr and --json error.data (reusing the existing nextSteps channel). - `orca agent-context [--json]`: a versioned, machine-readable dump of the command schema. Pure local read (no RPC), so it works over SSH and when the app isn't running. - CI guards: specs<->handlers parity, and a vocabulary policy that fails on new off-policy deletion/read verbs (existing ones grandfathered). * Address PR review feedback (#6303) - agent-context now emits each command's effective flag set (globals + conditional --page), not just allowedFlags, so the schema no longer under-reports --json/--help. Shared as effectiveAllowedFlags() between validation and the schema. - Collision check now covers alias paths too, so a duplicate alias that would silently shadow a real command fails the build. * fix(cli): harden agent recovery and introspection Co-authored-by: Orca <help@stably.ai> --------- Co-authored-by: Jinwoo-H <jinwoo0825@gmail.com> Co-authored-by: Orca <help@stably.ai> |
||
|
|
39964149c8 |
Per-Workspace Environments (on-demand disposable runtimes) + Add Project remote host setup (#6320)
Co-authored-by: Orca <help@stably.ai> |
||
|
|
42b2ecc5c6 |
feat(emulator): Android emulation via scrcpy (cross-platform, iOS parity) (#6434)
* docs: add Android emulation design spec Adds the design for first-class Android emulator support as a cross-platform peer of the iOS simulator feature: an extracted EmulatorBackend interface (iOS + Android), full AVD lifecycle management via the Android SDK, a live scrcpy H.264 pane decoded in-renderer with WebCodecs, the full control surface (tap/gesture/type/buttons/rotate), accessibility tree, app install/launch, runtime permissions, logcat, and a dedicated orca-emulator-android skill. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(emulator): add EmulatorBackend interface + backend/codec session tags First step of multi-backend emulator support: introduce the EmulatorBackend type and tag each session with its backend kind + stream codec, defaulting to ios/mjpeg so existing serve-sim behavior is unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(emulator): extract IosEmulatorBackend and make the bridge a router Move the serve-sim/simctl device + helper + input mechanics out of EmulatorBridge into IosEmulatorBackend (implementing EmulatorBackend). The bridge now owns the session registry and lifecycle orchestration and routes each command to the backend that owns the target device. iOS behavior is unchanged; the existing bridge tests pass untouched and the backend gains its own input-op coverage. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(emulator): add pure Android leaf modules (sdk/adb/avd/scrcpy/input/ax) Dependency-injected building blocks for the Android emulator backend, each unit- tested in isolation: SDK + tool discovery, adb device/output parsing, AVD list + boot arg building, scrcpy control-socket byte encoders, normalized<->pixel + keycode mapping, and a uiautomator XML accessibility-tree parser. Not yet wired; AndroidEmulatorBackend composes these in the next phase. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(emulator): gate availability on the iOS backend + lock it with tests inspectEmulatorAvailability now decides iOS host support via the registered iOS backend instead of a bare platform literal, routing the decision through the multi-backend seam. Output shape and all messages are unchanged (the settings pane still reads simctl/serveSim). Adds the previously-missing regression tests covering the unsupported, ready, no-devices, and tool-failure paths. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(emulator): add Android app/permissions/logcat arg builders Pure adb arg-builders + a logcat line parser for app install/launch, runtime permission grant/revoke/reset, and logcat capture. Unit-tested in isolation; wired into AndroidEmulatorBackend's capability verbs in a later phase. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(emulator): AndroidEmulatorBackend device management + unified device list Adds the Android backend (registered alongside iOS in the bridge): SDK-gated host support, device/AVD discovery and merge, AVD boot + boot-completion wait, shutdown, and tap/swipe/type/button/rotate/exec via `adb shell input` so control works without the scrcpy server (the live H.264 stream lands in the streaming phase). Surfaces everything through a new cross-platform `orca emulator devices` command (RPC emulator.listDevices -> bridge.listAllDevices) with a platform column. Device inventory is split into its own module to keep files focused. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(emulator): Android capability verbs (install/launch/permissions/ax/logcat) Wires the Android capability operations into AndroidEmulatorBackend and exposes them through a capability-gated bridge router (runCapability), RPC, and CLI: - orca emulator install/launch/permissions/ax/logcat Capabilities are advertised per backend; calling one on a backend that lacks it (e.g. iOS) fails with emulator_unsupported instead of a silent no-op. Input ops and capability ops are split into focused modules to keep files under the line cap; the runtime shares one target-param type. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(skill): add orca-emulator-android skill + cross-ref from iOS skill Documents the cross-platform Android emulator control surface (devices, input, hardware buttons, rotate, install/launch, permissions, ax, logcat) driveable via the orca CLI today, and notes the live visual pane is in development. Points the iOS skill's "when not to use" at the new Android skill. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(emulator): Android live-pane streaming scaffolding (scrcpy + WebCodecs) Builds the H.264 video path as scaffolding: scrcpy frame/codec-meta parsing, server-deploy arg builders, control-protocol encoders (committed earlier), the stream session (server + sockets), a video pub/sub registry, the emulator:videoStream* IPC channel, and a renderer WebCodecs->canvas hook. Pure framing/deploy/registry are unit-tested; the socket/WebCodecs/jar integration is clearly flagged UNVERIFIED and the remaining wiring (startSession, preload, pane codec branch, packaging the jar) is documented in docs/android-emulation-streaming.md. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs: fix streaming notes doc path in video-stream hook comment Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore(emulator): add diagnostic probes for Android testing Adds an emulator-probe logger (console + temp file at os.tmpdir()/orca-android-emu-probe.log) and wires probes at four layers so errors surface during manual testing: every emulator.* RPC call + error (RPC dispatcher), every adb/emulator command + non-zero exit (command runner), and the scrcpy session + video-stream IPC lifecycle. Temporary diagnostics; remove or gate behind a flag once the Android pane is validated. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(emulator): show Mobile Emulator settings cross-platform + aggregate Android availability The Mobile Emulator settings section is no longer macOS-gated (Android works on Windows/Linux), and inspectEmulatorAvailability now aggregates the iOS and Android backends: Android devices/AVDs appear in the device list and a host without iOS gets the Android setup message instead of "requires macOS". Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(emulator): show Mobile Emulator sidebar nav entry on non-mac desktops The settings sidebar nav registered the Mobile Emulator entry behind isMac, so it stayed hidden on Windows/Linux even after the section content was ungated. Widen it to showDesktopOnlySettings to match the section. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(emulator): wire Android startSession to scrcpy + client-downloaded jar AndroidEmulatorBackend.startSession now boots the device, ensures the scrcpy server jar (downloaded by the client into the per-user cache on first use, not bundled), starts a ScrcpyStreamSession, and feeds its H.264 frames to the video registry; stopHelperForDevice tears it down. Sessions carry their backend kind so worktree-active routing picks the right backend. Boot, host SDK discovery, and the stream starter are split into focused modules to stay under the line cap. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(emulator): stop the iOS backend from claiming Android devices off-mac iOS ownsDevice now returns false unless the host supports it, so on Windows an Android serial routes to the Android backend instead of erroring with "requires macOS". Backend-for-device fallback prefers a host-supported backend. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(emulator): scrcpy scid 31-bit + retry video socket until server delivers Two fixes validated against a real emulator: scrcpy parses scid as a signed 32-bit hex int, so mask to 31 bits + pad to 8 digits (8-byte values overflowed and the server exited). And adb accepts the forwarded TCP connection before the server's abstract socket exists then resets it, so retry the video socket until it actually delivers the dummy byte before connecting control. H.264 meta now arrives (576x1280). Adds socket/server-exit diagnostics probes. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(emulator): render the Android H.264 pane via WebCodecs Wires the live Android pane end-to-end: preload exposes emulator video stream APIs; the pane's device list uses the unified emulator.listDevices (Android + iOS); and emulator-screen-stream-content renders a WebCodecs <canvas> for scrcpy:// sessions (H.264, SPS/PPS prepended to the first keyframe) instead of the MJPEG <img>. The video hook reports the stream size for the device frame. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(emulator): buffer the current GOP for late video subscribers The renderer subscribes after attach already started the scrcpy stream, so the registry now caches the current GOP (keyframe + following deltas) alongside the codec meta and config, and replays it on subscribe. A pane opened mid-stream decodes from the keyframe immediately instead of showing black until scrcpy's next periodic keyframe (~10s). Refreshes the now-validated session doc comment. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(emulator): show New Mobile Emulator tab action off macOS The tab create menu and its dropdown item gated the New Mobile Emulator action on isMacOs, hiding it on Windows/Linux where Android emulation is now supported. Gate on mobileEmulatorEnabled + onNewSimulatorTab (already cross-platform) so the action appears wherever a mobile emulator backend is available. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(emulator): open the Mobile Emulator tab off macOS openMobileEmulatorTab and ensureSimulatorTab both returned null unless the host was macOS, so the New Mobile Emulator action no-opped on Windows/Linux even though the menu entry showed. Drop the isMacOsHost early-returns; the mobileEmulatorEnabled setting and backend availability already gate the feature. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(emulator): resolve a default attach device across backends emulatorAttach with no device fell back only to the iOS listSimulators picker (empty on Windows/Linux), so the pane's no-device launch flow errored. Extract resolveDefaultAttachDevice: iOS default first, else the first booted (else first) device across host backends, so Android attaches without an explicit device. Split into its own module to stay under the line cap. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(emulator): render the pane off macOS instead of an unavailable wall EmulatorPane short-circuited to the "macOS only" EmulatorUnavailablePane on any non-Mac host, blocking the now-working Android pane. Always render the pane content; its device discovery and error surface handle a missing backend. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(emulator): let attach boot a shut-down AVD with a stale active session getReusableActiveForWorktree called resolveDeviceId on the requested device, which throws for a not-yet-booted Android AVD, aborting the attach. Guard it so a resolve failure means "not the active device" and the attach falls through to a fresh boot — so picking a shut-down AVD in the pane and hitting Connect boots it via ensureBooted instead of erroring. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(emulator): launch the AVD detached instead of via execFile bootAndroidDevice started the emulator through the command runner (execFile with a timeout + 1MB stdout maxBuffer), which kills the long-running, verbose emulator process — so booting an AVD from the pane never actually came up. Spawn it detached with no stdio and unref it so it outlives the call, mirroring how the scrcpy server is launched. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(emulator): dedupe concurrent attaches into one scrcpy stream Extract AndroidStreamController to own the per-serial scrcpy lifecycle and dedupe starts: concurrent attaches (e.g. the pane's auto-attach racing the tab launch) now share one in-flight start and reuse the live stream instead of spawning a second scrcpy server that fights for the port and kills the first. Also initialize the registry GOP buffer in register() (latent type error). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(emulator): boot the AVD headless without a console window The detached spawn opened a Windows console (showing the emulator's verbose qemu/netsim logs) and a redundant native emulator window. Pass windowsHide and run the emulator with -no-window so it boots headless — the scrcpy pane is the view, matching how iOS hides Simulator.app. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(emulator): boot the AVD with a hidden console, not detached detached: true sets DETACHED_PROCESS, which gives the console-subsystem emulator no console — so it and its qemu/netsim children pop their own visible cmd window that windowsHide can't suppress. Drop detached and rely on windowsHide (CREATE_NO_WINDOW = hidden console) + unref; spawn already keeps it alive past the launch call, and managed emulators are shut down on app quit. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(emulator): keep Android emulators alive when switching devices Attaching a different device shut down the active one (shutdownDevice: true), which for Android meant killing the running emulator and cold-booting the target (~60s) on every switch — and switching back. Add bridge.stopActiveForSwitch: Android emulators stay running for instant switch-back, while iOS simulators are still replaced. Switching to an already-running emulator is now immediate. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * perf(emulator): only resize the video canvas when dimensions change The decoder output handler set canvas.width/height on every frame, which reallocates the canvas backing store and forces an object-contain reflow each frame — a needless per-frame cost. Resize only when the frame dimensions actually change. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(emulator): genericize copy + add Android Studio setup link Replace iOS-only wording (Xcode/Simulator/iPhone) in the pane and settings with backend-neutral copy so Android reads correctly on every platform. When no emulator is available, the Mobile Emulator settings now show a "Download Android Studio" link plus setup guidance (ANDROID_HOME / default install path). Removes the now-unused, macOS-only EmulatorUnavailablePane. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(emulator): show emulator SDK status in settings The backend availability now reports the resolved Android SDK path, aggregated into emulator.availability as an `android` block. The Mobile Emulator settings render an "Emulator SDKs" card showing Android SDK (detected at <path> / not found, with a Download Android Studio link) and, on macOS, iOS Simulator (Xcode) status — mirroring the agent-control card. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(emulator): locate a custom Android SDK folder from settings Add an androidSdkPath setting and a "Locate SDK folder…" / Clear action in the emulator SDK status card. The path is applied as the highest-priority discovery candidate (falls back if invalid), and the backend's SDK is re-resolved on use via a new AndroidSdkState — so locating or installing the SDK takes effect on Refresh without restarting Orca. Guards the status card against older runtimes. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(emulator): settle the scrcpy video socket once to stop retry storms A failed TCP connect emits both 'error' and 'close', so retry ran twice and scheduled openVideoSocket(attempt+1) twice — fanning out into an exponential connection storm while waiting for the server to start listening. A runaway chain could then hit attempt 100 and fail/close a stream that had already connected. Replace the delivered flag with a single settled latch so each socket retries (or delivers) exactly once. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(emulator): address CodeRabbit review findings - avd-boot: handle spawn 'error' (an unhandled ChildProcess error crashed the main process); validate the target is a known AVD before launching. - capability-ops: propagate adb non-zero exits for launch/permission/logcat and check the uiautomator dump before reading (avoids stale XML). - scrcpy-video-registry: actually replay the buffered GOP on subscribe so late subscribers decode immediately. - android-sdk-state: re-resolve host discovery every call so a changed SDK path takes effect live (no restart). - android-sdk-discovery: require both adb and the emulator binary. - emulator-bridge: fall back to the platform-primary backend (Android off-mac) so setup errors aren't iOS/CoreSimulator on Windows/Linux. - scrcpy-server-download: dedupe concurrent first-use downloads + add a timeout. - scrcpy-stream-session: idle-socket connect timeout; surface control-socket errors instead of swallowing them. - android-exec: pass the whole command so the device shell parses quotes/pipes. - avd-manager: match emulator log prefixes exactly (keep AVD names like PixelWARNINGTest). - permissions: `pm reset-permissions` is global and takes no package argument. - stream controller/starter: drop stale handles for dead streams; idempotent teardown. use-emulator-video-stream: stopVideoStream returns Promise. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(emulator): populate the GOP buffer and reuse live scrcpy streams Self-audit follow-ups in the same class as the CodeRabbit GOP-replay finding: - scrcpy-video-registry: pushFrame never wrote to entry.gop, so the replay loop added for late subscribers iterated an empty array — a no-op. Build the GOP on ingest (start at each keyframe, append following deltas; don't buffer deltas before the first keyframe). Adds tests for population, reset, and the pre-keyframe guard. - android backend: isSessionReusable was stubbed to always return false with a "no persistent stream yet" note, but scrcpy streams are persistent now — so every renderer remount tore down and respawned the server. Reuse a live stream (scrcpyVideoRegistry.has) so remounts reconnect, matching iOS. The device-mismatch check still runs first, so device switching is unaffected. - Refresh stale comments that implied unfinished/unverified work. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * WIP: Changes before auto-review fixes Co-authored-by: Orca <help@stably.ai> * Refine mobile emulator availability settings Co-authored-by: Orca <help@stably.ai> * Address emulator review follow-ups Co-authored-by: Orca <help@stably.ai> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Co-authored-by: Jinwoo-H <jinwoo0825@gmail.com> Co-authored-by: Orca <help@stably.ai> |
||
|
|
1419193bea |
Add explicit task title and display name to orchestration tasks (#5901)
Introduce optional `task-title` and `display-name` parameters for orchestration tasks, persisting them in the database and propagating them through the RPC and Orca runtime. This allows the CLI, dashboard, activity page, and sidebar to display concise, user-friendly labels for dispatched worker agents instead of verbose, raw system preambles. |
||
|
|
a1cbb31f27 |
Clarify worktree lineage and handoff rules in skills and CLI help (#5892)
- Explain that `--no-parent` only controls Orca lineage, not the Git base branch, and detail how to target independent top-level work. - Define full handoffs as ownership transfer and forbid the use of orchestration dispatch injection for them. - Update CLI help text for `orca worktree create` to reflect the lineage and base-branch guidance. - Add tests to verify that these guidance patterns exist in the skill markdown files. |
||
|
|
cfc003452c | Hide workspace parent flag from worktree create CLI (#5743) | ||
|
|
e3ffdbfa3a |
Clarify terminal vs worktree creation for fresh local agents (#5549)
- Document and update the CLI help, specs, and tests to explicitly guide users toward `orca terminal create --worktree active --command <agent>` to launch a fresh agent session in the current checkout. - Update orchestration and orca-cli skills to prefer active-worktree terminals when dependent on uncommitted files or active branch state, distinguishing them from separate worktree creation. |
||
|
|
2b8d9a43de |
feat(linear): add project support to agent CLI (#5433)
* feat(linear): add project support to agent CLI * fix(linear): resolve project names across search pages * fix(linear): harden agent project support * fix(linear): address project review feedback --------- Co-authored-by: Jinjing <6427696+AmethystLiang@users.noreply.github.com> |