refactor: isolate tmux runner implementation
This commit is contained in:
@@ -1,44 +1,9 @@
|
||||
export function createTmuxSingleRunner(deps: {
|
||||
assertInsideTmux(): void;
|
||||
getCurrentWindowId: () => Promise<string>;
|
||||
createArtifacts: (cwd: string, meta: Record<string, unknown>) => Promise<any>;
|
||||
buildWrapperCommand: (metaPath: string) => string;
|
||||
createPane: (input: { windowId: string; cwd: string; command: string }) => Promise<string>;
|
||||
monitorRun: (input: { eventsPath: string; resultPath: string; onEvent?: (event: any) => void }) => Promise<any>;
|
||||
killPane: (paneId: string) => Promise<void>;
|
||||
}) {
|
||||
return async function runSingleTask(input: {
|
||||
cwd: string;
|
||||
meta: Record<string, unknown>;
|
||||
onEvent?: (event: any) => void;
|
||||
}) {
|
||||
deps.assertInsideTmux();
|
||||
import type { SubagentRunResult } from "./schema.ts";
|
||||
|
||||
const artifacts = await deps.createArtifacts(input.cwd, input.meta);
|
||||
const windowId = await deps.getCurrentWindowId();
|
||||
const command = deps.buildWrapperCommand(artifacts.metaPath);
|
||||
const paneId = await deps.createPane({ windowId, cwd: input.cwd, command });
|
||||
|
||||
try {
|
||||
const result = await deps.monitorRun({
|
||||
eventsPath: artifacts.eventsPath,
|
||||
resultPath: artifacts.resultPath,
|
||||
onEvent: input.onEvent,
|
||||
});
|
||||
|
||||
return {
|
||||
...result,
|
||||
runId: result.runId ?? artifacts.runId,
|
||||
paneId,
|
||||
windowId,
|
||||
sessionPath: result.sessionPath ?? artifacts.sessionPath,
|
||||
stdoutPath: result.stdoutPath ?? artifacts.stdoutPath,
|
||||
stderrPath: result.stderrPath ?? artifacts.stderrPath,
|
||||
resultPath: artifacts.resultPath,
|
||||
eventsPath: artifacts.eventsPath,
|
||||
};
|
||||
} finally {
|
||||
await deps.killPane(paneId);
|
||||
}
|
||||
};
|
||||
export interface RunSingleTaskInput {
|
||||
cwd: string;
|
||||
meta: Record<string, unknown>;
|
||||
onEvent?: (event: any) => void;
|
||||
}
|
||||
|
||||
export type RunSingleTask = (input: RunSingleTaskInput) => Promise<SubagentRunResult>;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import { createTmuxSingleRunner } from "./runner.ts";
|
||||
import { createTmuxSingleRunner } from "./tmux-runner.ts";
|
||||
|
||||
test("createTmuxSingleRunner always kills the pane after monitor completion", async () => {
|
||||
const killed: string[] = [];
|
||||
@@ -31,3 +31,30 @@ test("createTmuxSingleRunner always kills the pane after monitor completion", as
|
||||
assert.equal(result.finalText, "done");
|
||||
assert.deepEqual(killed, ["%9"]);
|
||||
});
|
||||
|
||||
test("createTmuxSingleRunner surfaces explicit tmux precondition errors", async () => {
|
||||
const runSingleTask = createTmuxSingleRunner({
|
||||
assertInsideTmux() {
|
||||
throw new Error("tmux-backed subagents require pi to be running inside tmux.");
|
||||
},
|
||||
getCurrentWindowId: async () => "@1",
|
||||
createArtifacts: async () => ({
|
||||
metaPath: "/tmp/meta.json",
|
||||
runId: "run-1",
|
||||
eventsPath: "/tmp/events.jsonl",
|
||||
resultPath: "/tmp/result.json",
|
||||
sessionPath: "/tmp/child-session.jsonl",
|
||||
stdoutPath: "/tmp/stdout.log",
|
||||
stderrPath: "/tmp/stderr.log",
|
||||
}),
|
||||
buildWrapperCommand: () => "'node' '/wrapper.mjs' '/tmp/meta.json'",
|
||||
createPane: async () => "%9",
|
||||
monitorRun: async () => ({ finalText: "done", exitCode: 0 }),
|
||||
killPane: async () => {},
|
||||
});
|
||||
|
||||
await assert.rejects(
|
||||
() => runSingleTask({ cwd: "/repo", meta: { task: "inspect auth" } as any }),
|
||||
/tmux-backed subagents require pi to be running inside tmux/,
|
||||
);
|
||||
});
|
||||
44
src/tmux-runner.ts
Normal file
44
src/tmux-runner.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
export function createTmuxSingleRunner(deps: {
|
||||
assertInsideTmux(): void;
|
||||
getCurrentWindowId: () => Promise<string>;
|
||||
createArtifacts: (cwd: string, meta: Record<string, unknown>) => Promise<any>;
|
||||
buildWrapperCommand: (metaPath: string) => string;
|
||||
createPane: (input: { windowId: string; cwd: string; command: string }) => Promise<string>;
|
||||
monitorRun: (input: { eventsPath: string; resultPath: string; onEvent?: (event: any) => void }) => Promise<any>;
|
||||
killPane: (paneId: string) => Promise<void>;
|
||||
}) {
|
||||
return async function runSingleTask(input: {
|
||||
cwd: string;
|
||||
meta: Record<string, unknown>;
|
||||
onEvent?: (event: any) => void;
|
||||
}) {
|
||||
deps.assertInsideTmux();
|
||||
|
||||
const artifacts = await deps.createArtifacts(input.cwd, input.meta);
|
||||
const windowId = await deps.getCurrentWindowId();
|
||||
const command = deps.buildWrapperCommand(artifacts.metaPath);
|
||||
const paneId = await deps.createPane({ windowId, cwd: input.cwd, command });
|
||||
|
||||
try {
|
||||
const result = await deps.monitorRun({
|
||||
eventsPath: artifacts.eventsPath,
|
||||
resultPath: artifacts.resultPath,
|
||||
onEvent: input.onEvent,
|
||||
});
|
||||
|
||||
return {
|
||||
...result,
|
||||
runId: result.runId ?? artifacts.runId,
|
||||
paneId,
|
||||
windowId,
|
||||
sessionPath: result.sessionPath ?? artifacts.sessionPath,
|
||||
stdoutPath: result.stdoutPath ?? artifacts.stdoutPath,
|
||||
stderrPath: result.stderrPath ?? artifacts.stderrPath,
|
||||
resultPath: artifacts.resultPath,
|
||||
eventsPath: artifacts.eventsPath,
|
||||
};
|
||||
} finally {
|
||||
await deps.killPane(paneId);
|
||||
}
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user