Add Orca terminal surface backend

Subagents run in unfocused Orca terminal tabs in the current worktree,
selected automatically between Herdr, Orca (ORCA_TERMINAL_HANDLE), and tmux.

- Tabs are created against ORCA_WORKTREE_ID since pi's cwd may live outside
  any Orca worktree, and start in pi's cwd via the startup command
- createSurface blocks on two-phase readiness (startup marker, then a
  verified probe echo) because input typed during shell init is swallowed
  by the initial shell's exec or flushed during bash startup
- terminal read reports ok with status exited for closed terminals, so
  reads throw on non-running status to match herdr/tmux semantics
- Integration harness gains Orca focus support via terminal switch and
  visual-layout active-pane lookup
This commit is contained in:
jb
2026-08-30 15:54:59 -04:00
parent bc3e3d6049
commit 208cd6f4e7
8 changed files with 421 additions and 23 deletions
+46 -5
View File
@@ -36,6 +36,11 @@ import {
shellEscape,
} from "../pi-extension/subagents/surface.ts";
import { parseCreatedPane, parseCreatedTab } from "../pi-extension/subagents/herdr.ts";
import {
parseCreatedSplit,
parseCreatedTerminal,
parseReadTail,
} from "../pi-extension/subagents/orca.ts";
import {
advanceStatusState,
capStatusLines,
@@ -2613,6 +2618,7 @@ describe("subagent startup delay", () => {
const testApi = (subagentsModule as any).__test__;
assert.equal(testApi.defaultShellReadyDelayMs("tmux"), 500);
assert.equal(testApi.defaultShellReadyDelayMs("herdr"), 1000);
assert.equal(testApi.defaultShellReadyDelayMs("orca"), 500);
assert.equal(testApi.defaultShellReadyDelayMs(null), 500);
});
@@ -3149,11 +3155,13 @@ describe("subagent display helpers", () => {
});
describe("surface backend selection", () => {
it("prefers Herdr and otherwise falls back to tmux", () => {
assert.equal(selectMuxBackend({ herdr: true, tmux: true }), "herdr");
assert.equal(selectMuxBackend({ herdr: true, tmux: false }), "herdr");
assert.equal(selectMuxBackend({ herdr: false, tmux: true }), "tmux");
assert.equal(selectMuxBackend({ herdr: false, tmux: false }), null);
it("prefers Herdr, then Orca, and otherwise falls back to tmux", () => {
assert.equal(selectMuxBackend({ herdr: true, orca: true, tmux: true }), "herdr");
assert.equal(selectMuxBackend({ herdr: true, orca: false, tmux: false }), "herdr");
assert.equal(selectMuxBackend({ herdr: false, orca: true, tmux: true }), "orca");
assert.equal(selectMuxBackend({ herdr: false, orca: true, tmux: false }), "orca");
assert.equal(selectMuxBackend({ herdr: false, orca: false, tmux: true }), "tmux");
assert.equal(selectMuxBackend({ herdr: false, orca: false, tmux: false }), null);
});
it("parses Herdr tab and pane creation responses", () => {
@@ -3172,6 +3180,39 @@ describe("surface backend selection", () => {
);
assert.throws(() => parseCreatedTab("{}"), /result\.root_pane\.pane_id/);
});
it("parses Orca terminal create, split, and read responses", () => {
assert.deepEqual(
parseCreatedTerminal(JSON.stringify({
ok: true,
result: { terminal: { handle: "term_abc", tabId: "tab-1" } },
})),
{ handle: "term_abc", tabId: "tab-1" },
);
assert.equal(
parseCreatedSplit(JSON.stringify({ ok: true, result: { split: { handle: "term_def" } } })),
"term_def",
);
assert.equal(
parseReadTail(JSON.stringify({
ok: true,
result: { terminal: { status: "running", tail: ["line one", "line two"] } },
})),
"line one\nline two",
);
assert.throws(
() => parseReadTail(JSON.stringify({
ok: true,
result: { terminal: { status: "exited", tail: [] } },
})),
/not running \(status: exited\)/,
);
assert.throws(() => parseCreatedTerminal("{}"), /result\.terminal\.handle/);
assert.throws(
() => parseCreatedTerminal(JSON.stringify({ ok: false, error: { message: "nope" } })),
/terminal create failed: nope/,
);
});
});
describe("subagent completion wrapper", () => {