feat(pi): label subagent work items

This commit is contained in:
2026-08-01 22:24:04 -04:00
parent 5fd8de031d
commit c5828e0591
9 changed files with 157 additions and 28 deletions

View File

@@ -1,7 +1,9 @@
import assert from "node:assert/strict";
import test from "node:test";
import { milestoneNotification } from "./status.ts";
import { Supervisor } from "./supervisor.ts";
import type { ChildHandle, ChildRunner, RunnerEvents, SpawnRequest } from "./types.ts";
import { widget } from "./ui.ts";
class FakeHandle implements ChildHandle {
cancelCalls = 0;
@@ -110,6 +112,77 @@ test("completed children ignore later cancel", async () => {
assert.equal(runner.starts[0].handle.cancelCalls, 0);
});
test("explicit labels are reused across accepted status list and result surfaces", async () => {
const runner = new FakeRunner();
const supervisor = new Supervisor(runner, "/tmp");
const label = "Review risky migration";
const accepted = supervisor.spawn({ prompt: "inspect the migration plan", label } as SpawnRequest & { label: string });
await sleep(0);
runner.starts[0].events.completed("done", "agent_settled");
assert.deepEqual(
{
accepted: accepted.label,
status: supervisor.status(accepted.id).label,
list: supervisor.list().find((status) => status.id === accepted.id)?.label,
result: (supervisor.result(accepted.id) as { label?: string }).label,
},
{
accepted: label,
status: label,
list: label,
result: label,
},
);
});
test("ad hoc fallback labels are prompt-derived and reused by widget and result surfaces", async () => {
const runner = new FakeRunner();
const supervisor = new Supervisor(runner, "/tmp");
const prompt = " Audit\n\tguest enablement plan ";
const label = "Audit guest enablement plan";
const accepted = supervisor.spawn({ prompt });
await sleep(0);
runner.starts[0].events.completed("done", "agent_settled");
const statuses = supervisor.list();
const inspectorLines = widget(statuses, true)().render(240);
assert.deepEqual(
{
accepted: accepted.label,
childRequest: runner.starts[0].request.label,
status: supervisor.status(accepted.id).label,
list: statuses.find((status) => status.id === accepted.id)?.label,
result: supervisor.result(accepted.id).label,
},
{
accepted: label,
childRequest: label,
status: label,
list: label,
result: label,
},
);
assert.ok(inspectorLines.some((line) => line.includes(`${accepted.id} ${label} independent completed`)), inspectorLines.join("\n"));
assert.doesNotMatch(accepted.label, /^ad-hoc sg-/u);
});
test("milestone notifications use the stored label", async () => {
const runner = new FakeRunner();
const supervisor = new Supervisor(runner, "/tmp");
const accepted = supervisor.spawn({ prompt: "work", label: "Review migration" });
await sleep(0);
runner.starts[0].events.completed("done", "agent_settled");
assert.deepEqual(milestoneNotification(supervisor.status(accepted.id), "completed"), {
message: "Subagent Review migration completed",
level: "info",
});
assert.equal(milestoneNotification(supervisor.status(accepted.id), "running"), undefined);
});
test("shutdown clears recent terminal expiry timer", async () => {
const runner = new FakeRunner();
let changes = 0;
@@ -128,17 +201,22 @@ test("shutdown clears recent terminal expiry timer", async () => {
assert.equal(changes, afterShutdown);
});
test("batch spawn returns accepted ids and per-entry failures", async () => {
test("batch spawn returns explicit labels on accepted child requests and statuses while preserving failures", async () => {
const runner = new FakeRunner();
const supervisor = new Supervisor(runner, "/tmp");
const result = supervisor.spawnBatch([{ prompt: "one" }, { prompt: "" }, { prompt: "two" }]);
const result = supervisor.spawnBatch([
{ prompt: "one", label: "Review docs" },
{ prompt: "" },
{ prompt: "two", label: "Check tests" },
]);
await sleep(0);
assert.equal(result.accepted.length, 2);
assert.deepEqual(result.accepted.map((accepted) => accepted.label), ["Review docs", "Check tests"]);
assert.equal(result.failed.length, 1);
assert.equal(result.failed[0].index, 1);
assert.equal(runner.starts.length, 2);
assert.deepEqual(runner.starts.map((start) => start.request.label), ["Review docs", "Check tests"]);
assert.deepEqual(result.accepted.map((accepted) => supervisor.status(accepted.id).label), ["Review docs", "Check tests"]);
});
test("maxConcurrent preserves queued records", async () => {