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
+48
View File
@@ -97,7 +97,42 @@ function parseHerdrJson(command: string, args: string[]): any {
return JSON.parse(output);
}
function orcaCli(): string {
return process.env.ORCA_CLI_COMMAND?.trim() || "orca";
}
function parseOrcaJson(args: string[]): any {
const output = execFileSync(orcaCli(), args, { encoding: "utf8" });
const response = JSON.parse(output);
if (response.ok === false) {
throw new Error(`Orca ${args.slice(0, 2).join(" ")} failed: ${response.error?.message}`);
}
return response;
}
/** Depth-first search of an Orca visual-layout pane tree for the active terminal. */
function findActiveOrcaTerminal(node: any): string | null {
if (!node || typeof node !== "object") return null;
if (node.type === "terminal") return node.active ? node.handle ?? null : null;
for (const child of Object.values(node)) {
if (Array.isArray(child)) {
for (const item of child) {
const found = findActiveOrcaTerminal(item);
if (found) return found;
}
} else if (child && typeof child === "object") {
const found = findActiveOrcaTerminal(child);
if (found) return found;
}
}
return null;
}
export function focusSurface(surface: string): void {
if (getMuxBackend() === "orca") {
parseOrcaJson(["terminal", "switch", "--terminal", surface, "--json"]);
return;
}
if (getMuxBackend() === "herdr") {
const pane = parseHerdrJson("herdr", ["pane", "get", surface]);
const tabId = pane.result?.pane?.tab_id;
@@ -110,6 +145,19 @@ export function focusSurface(surface: string): void {
export function getFocusedSurface(): string | null {
try {
if (getMuxBackend() === "orca") {
const response = parseOrcaJson(["terminal", "list", "--include-visual-layouts", "--json"]);
const layouts = response.result?.visualLayouts ?? [];
for (const layout of layouts) {
const root = layout.root;
if (!root) continue;
const activeTab = root.tabs?.find((tab: any) => tab.tabId === root.activeTabId);
const handle = findActiveOrcaTerminal(activeTab?.panes);
if (handle) return handle;
}
return null;
}
if (getMuxBackend() === "herdr") {
const workspace = process.env.HERDR_WORKSPACE_ID;
if (!workspace) return null;