Adding support for herdr - agents in new tabs
This commit is contained in:
+74
-19
@@ -30,7 +30,12 @@ import {
|
||||
summarizeSessionStats,
|
||||
} from "../pi-extension/subagents/session.ts";
|
||||
|
||||
import { shellEscape } from "../pi-extension/subagents/tmux.ts";
|
||||
import {
|
||||
__pollForExitTest__,
|
||||
selectMuxBackend,
|
||||
shellEscape,
|
||||
} from "../pi-extension/subagents/surface.ts";
|
||||
import { parseCreatedPane, parseCreatedTab } from "../pi-extension/subagents/herdr.ts";
|
||||
import {
|
||||
advanceStatusState,
|
||||
capStatusLines,
|
||||
@@ -56,7 +61,6 @@ import {
|
||||
runningChildrenCount,
|
||||
} from "../pi-extension/subagents/subagent-done.ts";
|
||||
import subagentDoneExtension from "../pi-extension/subagents/subagent-done.ts";
|
||||
import { __pollForExitTest__ } from "../pi-extension/subagents/tmux.ts";
|
||||
|
||||
// --- Helpers ---
|
||||
|
||||
@@ -1172,10 +1176,12 @@ describe("subagent discovery", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("bundled scout/researcher/worker all resolve as non-interactive (auto-exit)", () => {
|
||||
it("bundled scout/researcher/worker use the configured model and resolve as non-interactive", () => {
|
||||
for (const name of ["scout", "researcher", "worker"]) {
|
||||
const defs = testApi.loadAgentDefaults(name);
|
||||
assert.ok(defs, `expected bundled agent ${name} to be discoverable`);
|
||||
assert.equal(defs.model, "openai-codex/gpt-5.6-luna");
|
||||
assert.equal(defs.thinking, "xhigh");
|
||||
assert.equal(
|
||||
testApi.resolveEffectiveInteractive({ name, task: "" }, defs),
|
||||
false,
|
||||
@@ -1209,10 +1215,16 @@ describe("subagent discovery", () => {
|
||||
it("getToolExtensionPath maps custom tools and skips built-ins", () => {
|
||||
assert.equal(testApi.getToolExtensionPath("read"), undefined);
|
||||
assert.equal(testApi.getToolExtensionPath("bash"), undefined);
|
||||
assert.ok(testApi.getToolExtensionPath("web_search")?.endsWith("web-search/index.ts"));
|
||||
assert.ok(testApi.getToolExtensionPath("safe_bash")?.endsWith("tools/safe-bash.ts"));
|
||||
// Spawning tools are registered by this extension itself.
|
||||
assert.ok(testApi.getToolExtensionPath("subagent")?.endsWith("index.ts"));
|
||||
|
||||
withTempDir((dir) => {
|
||||
const extensionPath = join(dir, "portable-test-tool.ts");
|
||||
writeFileSync(extensionPath, "export default function () {}\n");
|
||||
subagentsModule.registerToolExtension("portable_test_tool", extensionPath);
|
||||
assert.equal(testApi.getToolExtensionPath("portable_test_tool"), extensionPath);
|
||||
});
|
||||
});
|
||||
|
||||
it("ignores invalid session-mode values", async () => {
|
||||
@@ -1741,8 +1753,21 @@ describe("subagent-done.ts", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("tmux.ts interpretExitSidecar", () => {
|
||||
const { interpretExitSidecar } = __pollForExitTest__;
|
||||
describe("surface exit signals", () => {
|
||||
const { interpretExitSidecar, readCompletionFile } = __pollForExitTest__;
|
||||
|
||||
it("reads and removes a shell completion file", () => {
|
||||
withTempDir((dir) => {
|
||||
const completionFile = join(dir, "child.exit-code");
|
||||
writeFileSync(completionFile, "7\n");
|
||||
assert.deepEqual(readCompletionFile(completionFile), {
|
||||
reason: "sentinel",
|
||||
exitCode: 7,
|
||||
});
|
||||
assert.equal(existsSync(completionFile), false);
|
||||
assert.equal(readCompletionFile(completionFile), null);
|
||||
});
|
||||
});
|
||||
|
||||
it("no longer decodes ping payloads (ask_question keeps the session open instead)", () => {
|
||||
// ask_question writes a `.ask` signal, not a `.exit` ping sidecar, so an
|
||||
@@ -2460,19 +2485,11 @@ describe("subagent status renderer", () => {
|
||||
});
|
||||
|
||||
describe("subagent startup delay", () => {
|
||||
it("defaults to 500ms when no env var is set", () => {
|
||||
it("uses backend-specific defaults", () => {
|
||||
const testApi = (subagentsModule as any).__test__;
|
||||
assert.ok(testApi, "expected subagents test helpers to be exported");
|
||||
assert.equal(typeof testApi.getShellReadyDelayMs, "function");
|
||||
|
||||
const original = process.env.PI_SUBAGENT_SHELL_READY_DELAY_MS;
|
||||
delete process.env.PI_SUBAGENT_SHELL_READY_DELAY_MS;
|
||||
try {
|
||||
assert.equal(testApi.getShellReadyDelayMs(), 500);
|
||||
} finally {
|
||||
if (original == null) delete process.env.PI_SUBAGENT_SHELL_READY_DELAY_MS;
|
||||
else process.env.PI_SUBAGENT_SHELL_READY_DELAY_MS = original;
|
||||
}
|
||||
assert.equal(testApi.defaultShellReadyDelayMs("tmux"), 500);
|
||||
assert.equal(testApi.defaultShellReadyDelayMs("herdr"), 1000);
|
||||
assert.equal(testApi.defaultShellReadyDelayMs(null), 500);
|
||||
});
|
||||
|
||||
it("uses PI_SUBAGENT_SHELL_READY_DELAY_MS when it is set", () => {
|
||||
@@ -2652,7 +2669,45 @@ describe("subagent display helpers", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("tmux.ts", () => {
|
||||
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("parses Herdr tab and pane creation responses", () => {
|
||||
assert.deepEqual(
|
||||
parseCreatedTab(JSON.stringify({
|
||||
result: {
|
||||
root_pane: { pane_id: "w3:p9" },
|
||||
tab: { tab_id: "w3:t6" },
|
||||
},
|
||||
})),
|
||||
{ paneId: "w3:p9", tabId: "w3:t6" },
|
||||
);
|
||||
assert.equal(
|
||||
parseCreatedPane(JSON.stringify({ result: { pane: { pane_id: "w3:p10" } } })),
|
||||
"w3:p10",
|
||||
);
|
||||
assert.throws(() => parseCreatedTab("{}"), /result\.root_pane\.pane_id/);
|
||||
});
|
||||
});
|
||||
|
||||
describe("subagent completion wrapper", () => {
|
||||
it("records the child exit code without losing the terminal sentinel", () => {
|
||||
const wrapped = (subagentsModule as any).__test__.wrapWithCompletionFile(
|
||||
"pi --session test.jsonl",
|
||||
"/tmp/subagent exit",
|
||||
);
|
||||
assert.match(wrapped, /^pi --session test\.jsonl; exit_code=\$\?/);
|
||||
assert.match(wrapped, /printf '%s\\n' "\$exit_code" > '\/tmp\/subagent exit'/);
|
||||
assert.match(wrapped, /__SUBAGENT_DONE_\$\{exit_code\}__/);
|
||||
});
|
||||
});
|
||||
|
||||
describe("surface.ts", () => {
|
||||
describe("shellEscape", () => {
|
||||
it("wraps in single quotes", () => {
|
||||
assert.equal(shellEscape("hello"), "'hello'");
|
||||
|
||||
Reference in New Issue
Block a user