Commit Graph
24 Commits
Author SHA1 Message Date
brennanb2025andOrca 7948ef2ce8 Add workspace lineage grouping
Co-authored-by: Orca <help@stably.ai>
2026-05-15 01:13:54 -07:00
Neil 812831a18e fix: remember worktree creation agent (#1814) 2026-05-14 00:42:07 -07:00
Jinjing 5a6b2994ea fix: address review findings (#1770) 2026-05-13 16:14:50 -07:00
Jinjing 0273e38240 Fix start-from-PR display refresh (#1751) 2026-05-13 13:08:45 -07:00
Brennan BensonandOrca 8f3c783767 fix(worktree): create branches with --no-track and auto-setup remote (#1563)
* 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>
2026-05-11 12:46:03 -07:00
Jinwoo HongandOrca ea9a718e29 fix(ssh): remove relay FS path allowlist to support symlinks outside workspace (#1661) (#1672)
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>
2026-05-10 15:59:54 -07:00
Neil 41b4a2bc50 Use artifact titles for created workspaces (#1643) 2026-05-09 18:51:07 -07:00
buf0-bot[bot]andneil 6e91777f96 fix: pr-bug-scan findings from #1600 (#1619)
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>
2026-05-09 03:58:28 -07:00
JinjingandOrca 11b362c92d feat(worktree): optimistic create with reconciled base status (#1600)
Surface worktree creation immediately and reconcile remote base state
asynchronously, emitting drift/conflict events as fetches complete.

Co-authored-by: Orca <help@stably.ai>
2026-05-08 14:59:23 -07:00
Brennan BensonandOrca 8de43cd3b4 fix(sidebar): stamp lastActivityAt on first worktree discovery (#905)
Co-authored-by: Orca <help@stably.ai>
2026-05-05 16:17:51 -07:00
82023dbe1b feat(worktrees): add experimental symlinks for shared paths on worktree creation (#1029)
Co-authored-by: Orca <help@stably.ai>
Co-authored-by: federico-ntb <federico@notchatbot.com>
Co-authored-by: Jinwoo-H <jinwoo0825@gmail.com>
2026-05-04 14:19:20 -07:00
Jinwoo HongandOrca ea8ea08116 feat(orchestration): bundled improvements — check-wait, stale-base, preamble+ask, QoL (#1403)
* 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>
2026-05-04 12:10:53 -07:00
NeilandOrca 772be947d8 fix(hooks): don't silently skip worktree setup on benign orca.yaml divergence (#1388)
Co-authored-by: Orca <help@stably.ai>
2026-05-04 00:56:22 -07:00
Trevin Chow fd3e62603d fix(hooks): generate worktree setup-runner from target worktree's orca.yaml (#1280)
* 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.
2026-05-02 18:27:09 -07:00
bbd77c2449 fix(worktree): restore sparse checkout without per-poll git subprocesses (#1316)
Co-authored-by: Orca <help@stably.ai>
Co-authored-by: Colin <colin.chambachan@gmail.com>
2026-05-01 13:25:22 -07:00
Jinwoo Hong 000d4dc08b Revert sparse checkout worktree creation (#1290) 2026-04-30 15:47:40 -07:00
79faafcc98 Add sparse checkout worktree creation (#1131)
Co-authored-by: Orca <help@stably.ai>
Co-authored-by: Jinwoo-H <jinwoo0825@gmail.com>
2026-04-30 00:43:00 -07:00
Jinwoo HongandOrca 451d7cab42 fix(ssh): await root registration before remote worktree creation (#1120)
Co-authored-by: Orca <help@stably.ai>
2026-04-26 14:14:30 -07:00
Jinwoo Hong c2cf9b32a1 fix(ssh): register linked worktree paths with relay on connect (#994) 2026-04-23 11:11:53 -07:00
Neil ed32ca2c21 fix(worktrees): fail loudly when no default base ref is resolvable (#922) 2026-04-21 17:40:27 -07:00
Neil fbbebe98eb perf: make worktree creation click snappy (#776) 2026-04-17 16:02:30 -07:00
Neil d2a53c8fcc New workspace create page (#710)
* 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.
2026-04-16 15:41:40 -07:00
Jinwoo Hong 60a092686b perf: systematic performance optimizations (#623) 2026-04-14 00:28:56 -07:00
Jinwoo Hong cc66e120eb feat: Add SSH remote support (beta) (#590) 2026-04-13 19:23:09 -07:00