feat: select subagent runner from config
This commit is contained in:
37
index.ts
37
index.ts
@@ -2,8 +2,13 @@ import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|||||||
import { dirname, join } from "node:path";
|
import { dirname, join } from "node:path";
|
||||||
import { fileURLToPath } from "node:url";
|
import { fileURLToPath } from "node:url";
|
||||||
import { createRunArtifacts } from "./src/artifacts.ts";
|
import { createRunArtifacts } from "./src/artifacts.ts";
|
||||||
|
import { loadSubagentsConfig } from "./src/config.ts";
|
||||||
import { monitorRun } from "./src/monitor.ts";
|
import { monitorRun } from "./src/monitor.ts";
|
||||||
import { listAvailableModelReferences } from "./src/models.ts";
|
import { listAvailableModelReferences } from "./src/models.ts";
|
||||||
|
import { createProcessSingleRunner } from "./src/process-runner.ts";
|
||||||
|
import { createConfiguredRunSingleTask } from "./src/runner.ts";
|
||||||
|
import { createSubagentParamsSchema } from "./src/schema.ts";
|
||||||
|
import { createSubagentTool } from "./src/tool.ts";
|
||||||
import { createTmuxSingleRunner } from "./src/tmux-runner.ts";
|
import { createTmuxSingleRunner } from "./src/tmux-runner.ts";
|
||||||
import {
|
import {
|
||||||
buildCurrentWindowArgs,
|
buildCurrentWindowArgs,
|
||||||
@@ -12,31 +17,27 @@ import {
|
|||||||
buildWrapperShellCommand,
|
buildWrapperShellCommand,
|
||||||
isInsideTmux,
|
isInsideTmux,
|
||||||
} from "./src/tmux.ts";
|
} from "./src/tmux.ts";
|
||||||
import { createSubagentParamsSchema } from "./src/schema.ts";
|
|
||||||
import { createSubagentTool } from "./src/tool.ts";
|
|
||||||
|
|
||||||
const packageRoot = dirname(fileURLToPath(import.meta.url));
|
const packageRoot = dirname(fileURLToPath(import.meta.url));
|
||||||
const wrapperPath = join(packageRoot, "src", "wrapper", "cli.mjs");
|
const wrapperPath = join(packageRoot, "src", "wrapper", "cli.mjs");
|
||||||
|
|
||||||
export default function tmuxSubagentExtension(pi: ExtensionAPI) {
|
export default function subagentsExtension(pi: ExtensionAPI) {
|
||||||
if (process.env.PI_TMUX_SUBAGENT_GITHUB_COPILOT_INITIATOR === "agent") {
|
if (process.env.PI_SUBAGENTS_GITHUB_COPILOT_INITIATOR === "agent") {
|
||||||
pi.registerProvider("github-copilot", {
|
pi.registerProvider("github-copilot", {
|
||||||
headers: { "X-Initiator": "agent" },
|
headers: { "X-Initiator": "agent" },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// In wrapper/child sessions spawned by the tmux runner we must not register the
|
// In wrapper/child sessions spawned by subagent runners we must not register
|
||||||
// subagent tool (that would cause nested subagent registrations). Skip all
|
// the subagent tool again. Provider overrides above are still allowed in child
|
||||||
// subagent-tool registration logic when PI_TMUX_SUBAGENT_CHILD is set. Provider
|
// runs, so the guard stays after provider registration.
|
||||||
// overrides (above) are still allowed in child runs, so the guard is placed
|
if (process.env.PI_SUBAGENTS_CHILD === "1") {
|
||||||
// after provider registration.
|
|
||||||
if (process.env.PI_TMUX_SUBAGENT_CHILD === "1") {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let lastRegisteredModelsKey: string | undefined;
|
let lastRegisteredModelsKey: string | undefined;
|
||||||
|
|
||||||
const runSingleTask = createTmuxSingleRunner({
|
const tmuxRunner = createTmuxSingleRunner({
|
||||||
assertInsideTmux() {
|
assertInsideTmux() {
|
||||||
if (!isInsideTmux()) throw new Error("tmux-backed subagents require pi to be running inside tmux.");
|
if (!isInsideTmux()) throw new Error("tmux-backed subagents require pi to be running inside tmux.");
|
||||||
},
|
},
|
||||||
@@ -58,6 +59,20 @@ export default function tmuxSubagentExtension(pi: ExtensionAPI) {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const processRunner = createProcessSingleRunner({
|
||||||
|
createArtifacts: createRunArtifacts,
|
||||||
|
buildWrapperSpawn(metaPath: string) {
|
||||||
|
return { command: process.execPath, args: [wrapperPath, metaPath] };
|
||||||
|
},
|
||||||
|
monitorRun,
|
||||||
|
});
|
||||||
|
|
||||||
|
const runSingleTask = createConfiguredRunSingleTask({
|
||||||
|
loadConfig: (cwd) => loadSubagentsConfig(cwd),
|
||||||
|
processRunner,
|
||||||
|
tmuxRunner,
|
||||||
|
});
|
||||||
|
|
||||||
const registerSubagentTool = (availableModels: string[]) => {
|
const registerSubagentTool = (availableModels: string[]) => {
|
||||||
// Do not register a tool when no models are available. Remember that the
|
// Do not register a tool when no models are available. Remember that the
|
||||||
// last-registered key is different from the empty sentinel so that a later
|
// last-registered key is different from the empty sentinel so that a later
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
import test from "node:test";
|
import test from "node:test";
|
||||||
import assert from "node:assert/strict";
|
import assert from "node:assert/strict";
|
||||||
import tmuxSubagentExtension from "../index.ts";
|
import subagentsExtension from "../index.ts";
|
||||||
|
|
||||||
test("the extension entrypoint registers the subagent tool with the currently available models", async () => {
|
test("the extension entrypoint registers the subagent tool with the currently available models", async () => {
|
||||||
const original = process.env.PI_TMUX_SUBAGENT_CHILD;
|
const original = process.env.PI_SUBAGENTS_CHILD;
|
||||||
if (original !== undefined) delete process.env.PI_TMUX_SUBAGENT_CHILD;
|
if (original !== undefined) delete process.env.PI_SUBAGENTS_CHILD;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const registeredTools: any[] = [];
|
const registeredTools: any[] = [];
|
||||||
const handlers: Record<string, (event: any, ctx: any) => Promise<void> | void> = {};
|
const handlers: Record<string, (event: any, ctx: any) => Promise<void> | void> = {};
|
||||||
|
|
||||||
tmuxSubagentExtension({
|
subagentsExtension({
|
||||||
on(event: string, handler: (event: any, ctx: any) => Promise<void> | void) {
|
on(event: string, handler: (event: any, ctx: any) => Promise<void> | void) {
|
||||||
handlers[event] = handler;
|
handlers[event] = handler;
|
||||||
},
|
},
|
||||||
@@ -46,20 +46,20 @@ test("the extension entrypoint registers the subagent tool with the currently av
|
|||||||
"openai/gpt-5",
|
"openai/gpt-5",
|
||||||
]);
|
]);
|
||||||
} finally {
|
} finally {
|
||||||
if (original === undefined) delete process.env.PI_TMUX_SUBAGENT_CHILD;
|
if (original === undefined) delete process.env.PI_SUBAGENTS_CHILD;
|
||||||
else process.env.PI_TMUX_SUBAGENT_CHILD = original;
|
else process.env.PI_SUBAGENTS_CHILD = original;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
test("before_agent_start re-applies subagent registration when available models changed", async () => {
|
test("before_agent_start re-applies subagent registration when available models changed", async () => {
|
||||||
const original = process.env.PI_TMUX_SUBAGENT_CHILD;
|
const original = process.env.PI_SUBAGENTS_CHILD;
|
||||||
if (original !== undefined) delete process.env.PI_TMUX_SUBAGENT_CHILD;
|
if (original !== undefined) delete process.env.PI_SUBAGENTS_CHILD;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const registeredTools: any[] = [];
|
const registeredTools: any[] = [];
|
||||||
const handlers: Record<string, (event: any, ctx: any) => Promise<void> | void> = {};
|
const handlers: Record<string, (event: any, ctx: any) => Promise<void> | void> = {};
|
||||||
|
|
||||||
tmuxSubagentExtension({
|
subagentsExtension({
|
||||||
on(event: string, handler: (event: any, ctx: any) => Promise<void> | void) {
|
on(event: string, handler: (event: any, ctx: any) => Promise<void> | void) {
|
||||||
handlers[event] = handler;
|
handlers[event] = handler;
|
||||||
},
|
},
|
||||||
@@ -107,20 +107,20 @@ test("before_agent_start re-applies subagent registration when available models
|
|||||||
assert.deepEqual(registeredTools[1]?.parameters.properties.model.enum, ["openai/gpt-6"]);
|
assert.deepEqual(registeredTools[1]?.parameters.properties.model.enum, ["openai/gpt-6"]);
|
||||||
assert.deepEqual(registeredTools[1]?.parameters.properties.tasks.items.properties.model.enum, ["openai/gpt-6"]);
|
assert.deepEqual(registeredTools[1]?.parameters.properties.tasks.items.properties.model.enum, ["openai/gpt-6"]);
|
||||||
} finally {
|
} finally {
|
||||||
if (original === undefined) delete process.env.PI_TMUX_SUBAGENT_CHILD;
|
if (original === undefined) delete process.env.PI_SUBAGENTS_CHILD;
|
||||||
else process.env.PI_TMUX_SUBAGENT_CHILD = original;
|
else process.env.PI_SUBAGENTS_CHILD = original;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
test("child subagent sessions skip registering the subagent tool", async () => {
|
test("child subagent sessions skip registering the subagent tool", async () => {
|
||||||
const original = process.env.PI_TMUX_SUBAGENT_CHILD;
|
const original = process.env.PI_SUBAGENTS_CHILD;
|
||||||
process.env.PI_TMUX_SUBAGENT_CHILD = "1";
|
process.env.PI_SUBAGENTS_CHILD = "1";
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const registeredTools: any[] = [];
|
const registeredTools: any[] = [];
|
||||||
const handlers: Record<string, (event: any, ctx: any) => Promise<void> | void> = {};
|
const handlers: Record<string, (event: any, ctx: any) => Promise<void> | void> = {};
|
||||||
|
|
||||||
tmuxSubagentExtension({
|
subagentsExtension({
|
||||||
on(event: string, handler: (event: any, ctx: any) => Promise<void> | void) {
|
on(event: string, handler: (event: any, ctx: any) => Promise<void> | void) {
|
||||||
handlers[event] = handler;
|
handlers[event] = handler;
|
||||||
},
|
},
|
||||||
@@ -134,21 +134,21 @@ test("child subagent sessions skip registering the subagent tool", async () => {
|
|||||||
assert.equal(typeof handlers.before_agent_start, "undefined");
|
assert.equal(typeof handlers.before_agent_start, "undefined");
|
||||||
assert.equal(registeredTools.length, 0);
|
assert.equal(registeredTools.length, 0);
|
||||||
} finally {
|
} finally {
|
||||||
if (original === undefined) delete process.env.PI_TMUX_SUBAGENT_CHILD;
|
if (original === undefined) delete process.env.PI_SUBAGENTS_CHILD;
|
||||||
else process.env.PI_TMUX_SUBAGENT_CHILD = original;
|
else process.env.PI_SUBAGENTS_CHILD = original;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
test("registers github-copilot provider override when PI_TMUX_SUBAGENT_GITHUB_COPILOT_INITIATOR is set", () => {
|
test("registers github-copilot provider override when PI_SUBAGENTS_GITHUB_COPILOT_INITIATOR is set", () => {
|
||||||
const registeredProviders: Array<{ name: string; config: any }> = [];
|
const registeredProviders: Array<{ name: string; config: any }> = [];
|
||||||
const originalInitiator = process.env.PI_TMUX_SUBAGENT_GITHUB_COPILOT_INITIATOR;
|
const originalInitiator = process.env.PI_SUBAGENTS_GITHUB_COPILOT_INITIATOR;
|
||||||
const originalChild = process.env.PI_TMUX_SUBAGENT_CHILD;
|
const originalChild = process.env.PI_SUBAGENTS_CHILD;
|
||||||
// Ensure we exercise the non-child code path for this test
|
// Ensure we exercise the non-child code path for this test
|
||||||
if (originalChild !== undefined) delete process.env.PI_TMUX_SUBAGENT_CHILD;
|
if (originalChild !== undefined) delete process.env.PI_SUBAGENTS_CHILD;
|
||||||
process.env.PI_TMUX_SUBAGENT_GITHUB_COPILOT_INITIATOR = "agent";
|
process.env.PI_SUBAGENTS_GITHUB_COPILOT_INITIATOR = "agent";
|
||||||
|
|
||||||
try {
|
try {
|
||||||
tmuxSubagentExtension({
|
subagentsExtension({
|
||||||
on() {},
|
on() {},
|
||||||
registerTool() {},
|
registerTool() {},
|
||||||
registerProvider(name: string, config: any) {
|
registerProvider(name: string, config: any) {
|
||||||
@@ -156,11 +156,11 @@ test("registers github-copilot provider override when PI_TMUX_SUBAGENT_GITHUB_CO
|
|||||||
},
|
},
|
||||||
} as any);
|
} as any);
|
||||||
} finally {
|
} finally {
|
||||||
if (originalInitiator === undefined) delete process.env.PI_TMUX_SUBAGENT_GITHUB_COPILOT_INITIATOR;
|
if (originalInitiator === undefined) delete process.env.PI_SUBAGENTS_GITHUB_COPILOT_INITIATOR;
|
||||||
else process.env.PI_TMUX_SUBAGENT_GITHUB_COPILOT_INITIATOR = originalInitiator;
|
else process.env.PI_SUBAGENTS_GITHUB_COPILOT_INITIATOR = originalInitiator;
|
||||||
|
|
||||||
if (originalChild === undefined) delete process.env.PI_TMUX_SUBAGENT_CHILD;
|
if (originalChild === undefined) delete process.env.PI_SUBAGENTS_CHILD;
|
||||||
else process.env.PI_TMUX_SUBAGENT_CHILD = originalChild;
|
else process.env.PI_SUBAGENTS_CHILD = originalChild;
|
||||||
}
|
}
|
||||||
|
|
||||||
assert.deepEqual(registeredProviders, [
|
assert.deepEqual(registeredProviders, [
|
||||||
@@ -176,13 +176,13 @@ test("combined child+copilot run registers provider but no tools or startup hand
|
|||||||
const registeredTools: any[] = [];
|
const registeredTools: any[] = [];
|
||||||
const handlers: Record<string, (event: any, ctx: any) => Promise<void> | void> = {};
|
const handlers: Record<string, (event: any, ctx: any) => Promise<void> | void> = {};
|
||||||
|
|
||||||
const originalInitiator = process.env.PI_TMUX_SUBAGENT_GITHUB_COPILOT_INITIATOR;
|
const originalInitiator = process.env.PI_SUBAGENTS_GITHUB_COPILOT_INITIATOR;
|
||||||
const originalChild = process.env.PI_TMUX_SUBAGENT_CHILD;
|
const originalChild = process.env.PI_SUBAGENTS_CHILD;
|
||||||
process.env.PI_TMUX_SUBAGENT_GITHUB_COPILOT_INITIATOR = "agent";
|
process.env.PI_SUBAGENTS_GITHUB_COPILOT_INITIATOR = "agent";
|
||||||
process.env.PI_TMUX_SUBAGENT_CHILD = "1";
|
process.env.PI_SUBAGENTS_CHILD = "1";
|
||||||
|
|
||||||
try {
|
try {
|
||||||
tmuxSubagentExtension({
|
subagentsExtension({
|
||||||
on(event: string, handler: (event: any, ctx: any) => Promise<void> | void) {
|
on(event: string, handler: (event: any, ctx: any) => Promise<void> | void) {
|
||||||
handlers[event] = handler;
|
handlers[event] = handler;
|
||||||
},
|
},
|
||||||
@@ -205,23 +205,23 @@ test("combined child+copilot run registers provider but no tools or startup hand
|
|||||||
assert.equal(typeof handlers.session_start, "undefined");
|
assert.equal(typeof handlers.session_start, "undefined");
|
||||||
assert.equal(typeof handlers.before_agent_start, "undefined");
|
assert.equal(typeof handlers.before_agent_start, "undefined");
|
||||||
} finally {
|
} finally {
|
||||||
if (originalInitiator === undefined) delete process.env.PI_TMUX_SUBAGENT_GITHUB_COPILOT_INITIATOR;
|
if (originalInitiator === undefined) delete process.env.PI_SUBAGENTS_GITHUB_COPILOT_INITIATOR;
|
||||||
else process.env.PI_TMUX_SUBAGENT_GITHUB_COPILOT_INITIATOR = originalInitiator;
|
else process.env.PI_SUBAGENTS_GITHUB_COPILOT_INITIATOR = originalInitiator;
|
||||||
|
|
||||||
if (originalChild === undefined) delete process.env.PI_TMUX_SUBAGENT_CHILD;
|
if (originalChild === undefined) delete process.env.PI_SUBAGENTS_CHILD;
|
||||||
else process.env.PI_TMUX_SUBAGENT_CHILD = originalChild;
|
else process.env.PI_SUBAGENTS_CHILD = originalChild;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
test("does not re-register the subagent tool when models list unchanged, but re-registers when changed", async () => {
|
test("does not re-register the subagent tool when models list unchanged, but re-registers when changed", async () => {
|
||||||
const original = process.env.PI_TMUX_SUBAGENT_CHILD;
|
const original = process.env.PI_SUBAGENTS_CHILD;
|
||||||
if (original !== undefined) delete process.env.PI_TMUX_SUBAGENT_CHILD;
|
if (original !== undefined) delete process.env.PI_SUBAGENTS_CHILD;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
let registerToolCalls = 0;
|
let registerToolCalls = 0;
|
||||||
const handlers: Record<string, (event: any, ctx: any) => Promise<void> | void> = {};
|
const handlers: Record<string, (event: any, ctx: any) => Promise<void> | void> = {};
|
||||||
|
|
||||||
tmuxSubagentExtension({
|
subagentsExtension({
|
||||||
on(event: string, handler: (event: any, ctx: any) => Promise<void> | void) {
|
on(event: string, handler: (event: any, ctx: any) => Promise<void> | void) {
|
||||||
handlers[event] = handler;
|
handlers[event] = handler;
|
||||||
},
|
},
|
||||||
@@ -278,8 +278,8 @@ test("does not re-register the subagent tool when models list unchanged, but re-
|
|||||||
|
|
||||||
assert.equal(registerToolCalls, 2);
|
assert.equal(registerToolCalls, 2);
|
||||||
} finally {
|
} finally {
|
||||||
if (original === undefined) delete process.env.PI_TMUX_SUBAGENT_CHILD;
|
if (original === undefined) delete process.env.PI_SUBAGENTS_CHILD;
|
||||||
else process.env.PI_TMUX_SUBAGENT_CHILD = original;
|
else process.env.PI_SUBAGENTS_CHILD = original;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -287,14 +287,14 @@ test("does not re-register the subagent tool when models list unchanged, but re-
|
|||||||
// New tests for robustness: order-independence and empty model handling
|
// New tests for robustness: order-independence and empty model handling
|
||||||
|
|
||||||
test("same model set in different orders should NOT trigger re-registration", async () => {
|
test("same model set in different orders should NOT trigger re-registration", async () => {
|
||||||
const original = process.env.PI_TMUX_SUBAGENT_CHILD;
|
const original = process.env.PI_SUBAGENTS_CHILD;
|
||||||
if (original !== undefined) delete process.env.PI_TMUX_SUBAGENT_CHILD;
|
if (original !== undefined) delete process.env.PI_SUBAGENTS_CHILD;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
let registerToolCalls = 0;
|
let registerToolCalls = 0;
|
||||||
const handlers: Record<string, (event: any, ctx: any) => Promise<void> | void> = {};
|
const handlers: Record<string, (event: any, ctx: any) => Promise<void> | void> = {};
|
||||||
|
|
||||||
tmuxSubagentExtension({
|
subagentsExtension({
|
||||||
on(event: string, handler: (event: any, ctx: any) => Promise<void> | void) {
|
on(event: string, handler: (event: any, ctx: any) => Promise<void> | void) {
|
||||||
handlers[event] = handler;
|
handlers[event] = handler;
|
||||||
},
|
},
|
||||||
@@ -337,21 +337,21 @@ test("same model set in different orders should NOT trigger re-registration", as
|
|||||||
|
|
||||||
assert.equal(registerToolCalls, 1);
|
assert.equal(registerToolCalls, 1);
|
||||||
} finally {
|
} finally {
|
||||||
if (original === undefined) delete process.env.PI_TMUX_SUBAGENT_CHILD;
|
if (original === undefined) delete process.env.PI_SUBAGENTS_CHILD;
|
||||||
else process.env.PI_TMUX_SUBAGENT_CHILD = original;
|
else process.env.PI_SUBAGENTS_CHILD = original;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
test("empty model list should NOT register the tool, but a later non-empty list should", async () => {
|
test("empty model list should NOT register the tool, but a later non-empty list should", async () => {
|
||||||
const original = process.env.PI_TMUX_SUBAGENT_CHILD;
|
const original = process.env.PI_SUBAGENTS_CHILD;
|
||||||
if (original !== undefined) delete process.env.PI_TMUX_SUBAGENT_CHILD;
|
if (original !== undefined) delete process.env.PI_SUBAGENTS_CHILD;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
let registerToolCalls = 0;
|
let registerToolCalls = 0;
|
||||||
const handlers: Record<string, (event: any, ctx: any) => Promise<void> | void> = {};
|
const handlers: Record<string, (event: any, ctx: any) => Promise<void> | void> = {};
|
||||||
|
|
||||||
tmuxSubagentExtension({
|
subagentsExtension({
|
||||||
on(event: string, handler: (event: any, ctx: any) => Promise<void> | void) {
|
on(event: string, handler: (event: any, ctx: any) => Promise<void> | void) {
|
||||||
handlers[event] = handler;
|
handlers[event] = handler;
|
||||||
},
|
},
|
||||||
@@ -390,8 +390,8 @@ test("empty model list should NOT register the tool, but a later non-empty list
|
|||||||
|
|
||||||
assert.equal(registerToolCalls, 1);
|
assert.equal(registerToolCalls, 1);
|
||||||
} finally {
|
} finally {
|
||||||
if (original === undefined) delete process.env.PI_TMUX_SUBAGENT_CHILD;
|
if (original === undefined) delete process.env.PI_SUBAGENTS_CHILD;
|
||||||
else process.env.PI_TMUX_SUBAGENT_CHILD = original;
|
else process.env.PI_SUBAGENTS_CHILD = original;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
70
src/runner.test.ts
Normal file
70
src/runner.test.ts
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
import test from "node:test";
|
||||||
|
import assert from "node:assert/strict";
|
||||||
|
import { createConfiguredRunSingleTask } from "./runner.ts";
|
||||||
|
|
||||||
|
function makeResult(finalText: string) {
|
||||||
|
return {
|
||||||
|
runId: "run-1",
|
||||||
|
agent: "scout",
|
||||||
|
agentSource: "builtin" as const,
|
||||||
|
task: "inspect auth",
|
||||||
|
exitCode: 0,
|
||||||
|
finalText,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
test("createConfiguredRunSingleTask uses process runner when config says process", async () => {
|
||||||
|
const calls: string[] = [];
|
||||||
|
const runSingleTask = createConfiguredRunSingleTask({
|
||||||
|
loadConfig: () => ({ runner: "process" }),
|
||||||
|
processRunner: async () => {
|
||||||
|
calls.push("process");
|
||||||
|
return makeResult("process");
|
||||||
|
},
|
||||||
|
tmuxRunner: async () => {
|
||||||
|
calls.push("tmux");
|
||||||
|
return makeResult("tmux");
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await runSingleTask({ cwd: "/repo", meta: { task: "inspect auth" } as any });
|
||||||
|
|
||||||
|
assert.equal(result.finalText, "process");
|
||||||
|
assert.deepEqual(calls, ["process"]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("createConfiguredRunSingleTask uses tmux runner when config says tmux", async () => {
|
||||||
|
const calls: string[] = [];
|
||||||
|
const runSingleTask = createConfiguredRunSingleTask({
|
||||||
|
loadConfig: () => ({ runner: "tmux" }),
|
||||||
|
processRunner: async () => {
|
||||||
|
calls.push("process");
|
||||||
|
return makeResult("process");
|
||||||
|
},
|
||||||
|
tmuxRunner: async () => {
|
||||||
|
calls.push("tmux");
|
||||||
|
return makeResult("tmux");
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await runSingleTask({ cwd: "/repo", meta: { task: "inspect auth" } as any });
|
||||||
|
|
||||||
|
assert.equal(result.finalText, "tmux");
|
||||||
|
assert.deepEqual(calls, ["tmux"]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("createConfiguredRunSingleTask passes task cwd into config loader", async () => {
|
||||||
|
let cwdSeen = "";
|
||||||
|
const runSingleTask = createConfiguredRunSingleTask({
|
||||||
|
loadConfig: (cwd) => {
|
||||||
|
cwdSeen = cwd;
|
||||||
|
return { runner: "process" };
|
||||||
|
},
|
||||||
|
processRunner: async () => makeResult("process"),
|
||||||
|
tmuxRunner: async () => makeResult("tmux"),
|
||||||
|
});
|
||||||
|
|
||||||
|
await runSingleTask({ cwd: "/repo/worktree", meta: { task: "inspect auth" } as any });
|
||||||
|
|
||||||
|
assert.equal(cwdSeen, "/repo/worktree");
|
||||||
|
});
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import type { RunnerMode } from "./config.ts";
|
||||||
import type { SubagentRunResult } from "./schema.ts";
|
import type { SubagentRunResult } from "./schema.ts";
|
||||||
|
|
||||||
export interface RunSingleTaskInput {
|
export interface RunSingleTaskInput {
|
||||||
@@ -7,3 +8,14 @@ export interface RunSingleTaskInput {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type RunSingleTask = (input: RunSingleTaskInput) => Promise<SubagentRunResult>;
|
export type RunSingleTask = (input: RunSingleTaskInput) => Promise<SubagentRunResult>;
|
||||||
|
|
||||||
|
export function createConfiguredRunSingleTask(deps: {
|
||||||
|
loadConfig: (cwd: string) => { runner: RunnerMode };
|
||||||
|
processRunner: RunSingleTask;
|
||||||
|
tmuxRunner: RunSingleTask;
|
||||||
|
}): RunSingleTask {
|
||||||
|
return (input) => {
|
||||||
|
const config = deps.loadConfig(input.cwd);
|
||||||
|
return (config.runner === "tmux" ? deps.tmuxRunner : deps.processRunner)(input);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user