* WIP: Changes before auto-review fixes
Co-authored-by: Orca <help@stably.ai>
* WIP: Changes before auto-review fixes
Co-authored-by: Orca <help@stably.ai>
* fix(worktree): preserve user push.autoSetupRemote, include path in warn
- Probe push.autoSetupRemote with `git config --get` before writing so a
deliberate user value at any scope (local/global/system) is preserved.
- Include worktree path in the warn log for failed config writes.
- Add test pinning the preserve-existing-value behavior.
- Remove stray 00-review-context.md committed during review tooling.
Co-authored-by: Orca <help@stably.ai>
* WIP: Changes before auto-review fixes
Co-authored-by: Orca <help@stably.ai>
* fix(worktree): narrow config --get error handling, tighten test asserts
Treat only exit code 1 from `git config --get push.autoSetupRemote`
as "key unset". Other read failures (corrupt config, locked file,
parse error) now re-throw to the outer warn handler instead of being
silently treated as unset and overwriting whatever value the user
actually has.
Also: add test for the non-unset read-error path; convert the
"preserves existing value" test from `.some()` predicates to a
full-array `toEqual` matching sibling-test style; explicitly mock
`config --get` (with code: 1) in the sparse-failure rollback test
so it exercises the intended branch instead of the helper's empty-
stdout fallthrough; document in the design notes that
addSparseWorktree's rollback intentionally does not unset
push.autoSetupRemote.
Co-authored-by: Orca <help@stably.ai>
* test(worktree): pin --get-empty-stdout and worktree-add-fail invariants
Why: addWorktree's post-create config probe has two ordering
invariants worth pinning so a future refactor can't silently
regress them: (1) `git config --get` succeeding with empty stdout
still counts as "already set" so we don't overwrite an explicit
empty value, and (2) the entire config block is skipped when
`worktree add` itself rejects.
Co-authored-by: Orca <help@stably.ai>
* WIP: Changes before auto-review fixes
Co-authored-by: Orca <help@stably.ai>
* docs(worktree): cross-ref local↔SSH addWorktree, clarify SSH-host git version, add empty-stdout parity test
JSDoc on local addWorktree now flags the push.autoSetupRemote side
effect; both paths cross-reference each other so the next change keeps
them in lockstep. Relay comment clarifies that the git version that
matters is the SSH host's, not the client's. Adds the missing
empty-stdout-as-already-set parity test on the relay side.
Co-authored-by: Orca <help@stably.ai>
* chore: remove 00-review-context.md from PR
Stray file from local review workflow; should not ship in this PR.
Co-authored-by: Orca <help@stably.ai>
* chore: remove worktree-ssh-no-track-parity.md from PR
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: Orca <help@stably.ai>
When a remote SSH workspace contains a symlink whose target lies outside
the registered repo/worktree roots, file reads failed with 'Path outside
authorized workspace'. This silently broke common workflows: HPC dataset
mounts, multi-checkout repos, dotfile editing, and any cross-mount
symlink.
Drop `RelayContext.authorizedRoots`, `validatePath`, and
`validatePathResolved` along with all ~33 call sites in fs-handler.ts
and git-handler.ts. The relay's threat model becomes 'the relay runs as
the SSH user and trusts the renderer.'
Why this is acceptable: `pty.spawn` and `git.exec` already concede the
same threat. A renderer that wants to reach `/etc/passwd` can spawn a
shell or run `git -C /etc cat-file`; the FS allowlist was friction, not
a security boundary. Intra-worktree path checks in `getDiff` and
`discard` are intentionally preserved.
Back-compat preserved: `session.registerRoot` (notification + request)
remains a valid RPC, retained as no-ops on new relays. Old main + new
relay and new main + old relay both keep working through the upgrade
window. `registerRelayRoots` is also kept for the same reason. A
narrowed error-translation block in `worktree-remote.ts` handles old
relays still surfacing the legacy error string to users.
Tests: removed two negative-allowlist tests; added a positive control
('reads files outside any registered root') and a direct regression
test for #1661 ('reads files via symlinks resolving outside the
workspace'). All 469 relay/SSH/IPC tests pass.
See docs/relay-fs-allowlist-removal.md for the full rationale,
back-compat matrix, alternatives considered, and follow-up cleanup
plan.
Closes#1661
Co-authored-by: Orca <help@stably.ai>
Fixed 6 findings: legacy fetch fallback for local-only bases, memoized canonical fetch key, recorded reconcile token before await, cleared token in finally, configurable publish remote, and fetch-failure-aware base ref error.
Rebased onto current main applying only the Fixer-summary files (worktree-remote.ts, orca-runtime.ts) to avoid silent reverts from a stale base.
Co-authored-by: neil <neil@nous.com>
Surface worktree creation immediately and reconcile remote base state
asynchronously, emitting drift/conflict events as fetches complete.
Co-authored-by: Orca <help@stably.ai>
* feat(orchestration): transport keepalive + delivered_at split for check --wait
Implements the four §3 fixes from the check-wait design doc:
- §3.1 Transport keepalive: long-poll RPCs (orchestration.check --wait) emit
`{"_keepalive":true}` frames every 10s so neither server nor client tears
the socket down on idle. A `longPoll` admission counter capped at 16 fails
fast with `runtime_busy` when saturated; an AbortController wired through
the RPC dispatcher cancels the inner waiter the moment the socket closes.
- §3.2 delivered_at split: push-on-idle now stamps `delivered_at` instead of
flipping `read`, so the check caller remains the sole consumer of its
queue. Adds a synchronous idempotent schema migration that hard-fails on
error.
- §3.3 inbox/check parity: `orchestration inbox --terminal <handle>` and
`orchestration check --all` agree on the same rows (sequence DESC, no
mark-read). `check --unread=false` kept for one release as a compat shim.
- §3.4 CLI heartbeat: `orca orchestration check --wait` emits JSON heartbeat
lines to stderr every 15s so Claude Code's Bash tool sees continuous
output and doesn't auto-background the subprocess.
Tests: extends runtime-rpc, orca-runtime, envelope-schema, orchestration
method, and formatter suites; adds a subprocess test that spawns the built
CLI and verifies stderr line-flushing, heartbeat cadence, and stdout
cleanliness end-to-end.
Co-authored-by: Orca <help@stably.ai>
* feat(orchestration): preamble rules + heartbeat schema
- Preamble (#7, #15, #9): worker_done body ("3-sentence summary" + reportPath),
BEHAVIOR RULE #1 forbidding AskUserQuestion, heartbeat every 5 minutes with
taskId+dispatchId payload, AFTER YOU SEND grace window.
- Schema v2 migration: adds 'heartbeat' to messages.type CHECK, adds
dispatch_contexts.last_heartbeat_at, gated by user_version PRAGMA with
transactional rebuild + explicit CREATE INDEX to avoid silent perf regress.
- DB helpers: recordHeartbeat (dispatched-only), getStaleDispatches,
getThreadMessagesFor (thread+handle scoped for ask).
Co-authored-by: Orca <help@stably.ai>
* feat(orchestration): coordinator heartbeat + stale detector
Handle incoming 'heartbeat' messages by calling recordHeartbeat keyed on
payload.dispatchId (strict — log-and-skip if missing, no taskId fallback so
a straggler heartbeat from a previously-failed dispatch cannot mask a hung
retry per §5.3.4). On every tick after the 10-minute threshold, emit one
log per stale dispatched row — no auto-fail.
Also threads dispatchId through buildDispatchPreamble so workers can
attribute their heartbeats back to the correct dispatch context.
Co-authored-by: Orca <help@stably.ai>
* feat(orchestration): orca orchestration ask verb
Adds a CLI verb that sends a decision_gate message and blocks on the
coordinator's reply, scoped to the outbound message's thread. Group
addresses (@all, @idle, …) are rejected — fan-out questions must use
send --type decision_gate explicitly.
--json emits bare single-line JSON (bypassing printResult) so workers can
pipe `orca orchestration ask … --json | jq -r .answer` without unwrapping
an RPC envelope; human mode prints just the answer. On timeout the verb
exits 1 and returns {answer: null, timedOut: true}.
This is the CLI surface BEHAVIOR RULE #1 in the dispatch preamble points
workers at instead of AskUserQuestion.
Co-authored-by: Orca <help@stably.ai>
* feat(orchestration): QoL bundle — preamble visibility, status enum, dispatch cross-ref, inbox --full
Addresses four items from ORCHESTRATOR_FEEDBACK:
- #5 preamble visibility: `dispatch-show --preamble` regenerates the preamble
text for a task; `dispatch --inject --dry-run` previews without mutating
state; `dispatch --return-preamble` echoes the injected preamble in the JSON
response so coordinators can audit what a worker received.
- #6 status enum validation: CLI rejects unknown `task-update --status` values
with `invalid status '<x>', expected one of: pending, ready, dispatched,
completed, failed, blocked` before the RPC's generic Zod message. Valid
statuses are listed under Notes in `task-update --help`.
- #13 task-list dispatch cross-ref: `task-list --json` now includes
`assignee_handle` and `dispatch_id` for tasks in status=dispatched via a
read-only LEFT JOIN on dispatch_contexts. Non-dispatched rows keep their
legacy shape so existing consumers are unaffected.
- #14 inbox body visibility: `inbox --full` prints body + payload verbatim;
default output is unchanged (id/from/to/subject only).
No DB migrations; join-only change on dispatch_contexts so the sibling
preamble PR's `last_heartbeat_at` column addition will not conflict.
Co-authored-by: Orca <help@stably.ai>
* fix(worktree): prevent stale-base worktree creation and dispatch
Addresses feedback #16 per DESIGN_DOC_STALE_BASE_FIX.md §0. Four v1
components coordinated by a single shared fetch cache on the runtime:
1. Concurrent-fetch-with-gate in UI create path: `createLocalWorktree`
fires `git fetch` BEFORE the suffix loop / PR probe / path
resolution, then awaits right before `addWorktree` so the new branch
always spawns from a fresh remote tip. Renderer sees a two-phase
spinner via the new `createWorktree:progress` IPC event. The cache
is a `Map<repoPath::remote, Promise<void>>` + 30s success-only
timestamp on `OrcaRuntimeService` (§7.1 — shared with dispatch).
2. Dispatch pre-flight drift guard in `Coordinator.dispatchTask`:
probes `rev-list --left-right --count` against the target worktree
and silently returns (preserves `ready`, no circuit-breaker burn)
when `behind > 20` unless the task spec carries
`allow-stale-base: true`. Parsing strips the flag so it never leaks
into the worker's `--- TASK ---` block.
3. Preamble drift section: populated only when dispatch detected drift.
Workers see `--- BASE DRIFT ---` with the N-most-recent subjects
they don't have, so they can pull them in before running.
4. §3.3 Lifecycle: `.finally()` evicts Map entries on BOTH success and
rejection; timestamp is written ONLY on success. Prevents a single
DNS hiccup from wedging every future create on the repo until
restart, and keeps the freshness window honest.
Defers the DB `allow_stale_base` column (§0.2) and the create-time
warn toast; both can layer in later without migration.
Tests: 35 new/updated unit tests covering drift preamble, dispatch
refusal, spec-text flag parsing, fetch Map eviction after rejection,
freshness-window short-circuit, and concurrent-caller serialization.
Co-authored-by: Orca <help@stably.ai>
* test(orchestration): seed v2 DB in migration hard-fail test
After consolidating the schema bump, fresh DBs are initialized directly at
v3 via createTables(), so the v2→v3 ALTER TABLE is skipped on new installs
and the prior test's stub never fired. Seed a v2-shape file on disk so the
guarded ALTER actually runs and the "simulated migration failure" stub
propagates as intended.
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: Orca <help@stably.ai>
* fix(hooks): generate worktree setup-runner from target worktree's orca.yaml
Extend getEffectiveHooks, getSetupCommandSource, and runHook with an
optional worktreePath parameter. When provided, loadHooks reads the
yaml from that path; otherwise it falls back to repo.path. Update
the worktree-creation paths in worktree-remote.ts and orca-runtime.ts
to thread the new worktreePath through after the worktree exists, so
the generated setup-runner reflects the yaml at the tip of the target
worktree's branch instead of the primary checkout's stale yaml.
Add a regression test in hooks.test.ts that mocks two distinct
orca.yaml files (primary and worktree) and asserts the worktree's
content wins when worktreePath is passed.
The legacy hooks:check IPC handler keeps reading from repo.path
unchanged.
Closes#1256
* fix(hooks): skip auto-setup when worktree script differs from preview
Add setupScriptsMatch helper that compares the primary checkout's
setup script (what the renderer shows the user before worktree
creation) to the target worktree's script (what would actually run
after creation). When they differ, createLocalWorktree skips the
auto-launch and logs a warning, so a base-branch yaml that introduces
or modifies setup commands cannot execute under the trust granted to
the primary's preview. CLI-created worktrees use the worktree-bound
load directly because trust is granted by the CLI invocation context,
which is annotated in orca-runtime.ts.
Adds regression coverage for both matching and differing script cases.
* test(hooks): add setupScriptsMatch to worktree IPC test mocks
The new setupScriptsMatch import in worktree-remote.ts means the
existing vi.mock('../hooks') blocks in worktrees.test.ts and
worktrees-windows.test.ts now need to expose it. Default the mock to
returning true so existing tests continue to exercise the run-setup
path; the new behavior gating is covered by the dedicated
setupScriptsMatch unit tests.
* New workspace page with agent catalog, composer modal, and terminal integration
* fix lint
* better blank state
* fix: resolve typecheck errors in new-workspace flow
- widen activateAndRevealWorktree's issueCommand to accept direct
command shape used by NewWorkspacePage, not just the main-process
runner-script variant
- use a ref object in pty-connection tests to dodge TS narrowing the
captured callback to never across the mock closure boundary
* fix: bound worktree name auto-suffix loop to prevent OOM in tests
The auto-suffix loop in createLocalWorktree had no termination cap.
When tests (or a misconfigured env) mocked getBranchConflictKind /
getPRForBranch to always return a collision, the loop ran forever and
the vitest worker crashed with "Ineffective mark-compacts near heap
limit".
Cap the search at 100 suffixes, and if all fail, fall back to the
original specific error messages (branch already exists / PR already
owns the name) instead of a generic failure.
Update the two tests that asserted the old throw-on-first-conflict
behavior to verify the new auto-suffix path end-to-end.