@@ -51,6 +51,7 @@ async function createHeadlessHarness(root: string, incarnation: string) {
|
||||
const commands = new Map<string, any>();
|
||||
const tools = new Map<string, any>();
|
||||
const sent: SentMessage[] = [];
|
||||
const observedChecks = new Set<string>();
|
||||
const notifications: Array<{ message: string; level: string }> = [];
|
||||
const widgets: unknown[] = [];
|
||||
const statuses: string[] = [];
|
||||
@@ -70,7 +71,18 @@ async function createHeadlessHarness(root: string, incarnation: string) {
|
||||
},
|
||||
registerShortcut(key: string, shortcut: any) { shortcuts.set(key, shortcut); },
|
||||
registerTool(tool: any) {
|
||||
tools.set(tool.name, tool);
|
||||
// Reports execute inside a model turn. Deliver its queued check's start
|
||||
// rather than bypassing before_agent_start as the old fixture did.
|
||||
tools.set(tool.name, { ...tool, execute: async (...args: any[]) => {
|
||||
const queued = sent.at(-1)?.content;
|
||||
if (queued && !observedChecks.has(queued)) {
|
||||
observedChecks.add(queued);
|
||||
for (const handler of handlers.get("before_agent_start") ?? []) {
|
||||
await handler({ prompt: queued, systemPrompt: "base" }, ctx);
|
||||
}
|
||||
}
|
||||
return tool.execute(...args);
|
||||
} });
|
||||
},
|
||||
sendUserMessage(content: string, options?: SentMessage["options"]) {
|
||||
sent.push({ content, options });
|
||||
@@ -113,6 +125,7 @@ async function createHeadlessHarness(root: string, incarnation: string) {
|
||||
idle = value;
|
||||
},
|
||||
async emit(name: string, event: any = { type: name }) {
|
||||
if (name === "before_agent_start") observedChecks.add(event.prompt);
|
||||
for (const handler of handlers.get(name) ?? []) await handler(event, ctx);
|
||||
},
|
||||
state() {
|
||||
@@ -324,6 +337,114 @@ const waitReport = {
|
||||
nextAction: "Read input and finish output", nextCheck: "input file available" },
|
||||
};
|
||||
|
||||
test("queued check latch deduplicates lifecycle events until the matching start", async () => {
|
||||
const dir = mkdtempSync(join(tmpdir(), "goal-check-latch-"));
|
||||
const harness = await createHeadlessHarness(dir, "check-latch");
|
||||
try {
|
||||
await harness.commands.get("goal").handler("ready work", harness.ctx);
|
||||
const first = harness.sent[0].content;
|
||||
for (let i = 0; i < 10; i++) {
|
||||
await harness.emit("session_start");
|
||||
await harness.emit("agent_settled");
|
||||
}
|
||||
assert.equal(harness.sent.length, 1);
|
||||
await harness.emit("before_agent_start", { prompt: "unrelated incoming question", systemPrompt: "base" });
|
||||
await harness.emit("agent_settled");
|
||||
assert.equal(harness.sent.length, 1, "unrelated input does not release a pending check");
|
||||
await harness.emit("before_agent_start", { prompt: first, systemPrompt: "base" });
|
||||
await harness.emit("agent_settled");
|
||||
assert.equal(harness.sent.length, 2, "a completed check can continue ready work");
|
||||
await harness.emit("before_agent_start", { prompt: first, systemPrompt: "base" });
|
||||
await harness.emit("agent_settled");
|
||||
assert.equal(harness.sent.length, 2, "duplicate old start cannot release a newer check");
|
||||
await harness.commands.get("goal").handler("replacement", harness.ctx);
|
||||
assert.equal(harness.sent.length, 3, "explicit replacement may schedule its new goal");
|
||||
await harness.commands.get("goal").handler("stop", harness.ctx);
|
||||
await harness.commands.get("goal").handler("resume", harness.ctx);
|
||||
assert.equal(harness.sent.length, 4);
|
||||
} finally {
|
||||
await harness.emit("session_shutdown"); harness.restoreEnv(); rmSync(dir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test("untimed wait: settle and duplicate reports never inject a heartbeat", async (t) => {
|
||||
t.mock.timers.enable({ apis: ["setTimeout", "Date"], now: 100000 });
|
||||
const dir = mkdtempSync(join(tmpdir(), "goal-untimed-"));
|
||||
const harness = await createHeadlessHarness(dir, "untimed");
|
||||
try {
|
||||
await harness.commands.get("goal").handler("wait for coordinator", harness.ctx);
|
||||
const report = harness.tools.get("goal_report");
|
||||
await report.execute("wait", waitReport, undefined, undefined, harness.ctx);
|
||||
const before = harness.sent.length;
|
||||
for (let i = 0; i < 100; i++) {
|
||||
await harness.emit("agent_settled");
|
||||
await report.execute(`repeat-${i}`, waitReport, undefined, undefined, harness.ctx);
|
||||
}
|
||||
t.mock.timers.tick(7 * 86400000);
|
||||
assert.equal(harness.sent.length, before, "accepted waits must not generate model turns");
|
||||
assert.equal(harness.state().checks, 0);
|
||||
assert.equal(harness.state().noProgressReports, 0);
|
||||
assert.equal(harness.state().activeWait.deadlineAt, undefined);
|
||||
} finally {
|
||||
await harness.emit("session_shutdown"); harness.restoreEnv(); rmSync(dir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test("untimed wait: reload, unrelated input and tool work preserve suspension", async () => {
|
||||
const dir = mkdtempSync(join(tmpdir(), "goal-untimed-reload-"));
|
||||
const harness = await createHeadlessHarness(dir, "untimed-reload");
|
||||
try {
|
||||
await harness.commands.get("goal").handler("wait for coordinator", harness.ctx);
|
||||
const report = harness.tools.get("goal_report");
|
||||
await report.execute("wait", waitReport, undefined, undefined, harness.ctx);
|
||||
const wait = harness.state().activeWait;
|
||||
const before = harness.sent.length;
|
||||
await harness.emit("session_shutdown");
|
||||
await harness.emit("session_start");
|
||||
await harness.emit("session_start");
|
||||
assert.equal(harness.sent.length, before, "startup must preserve even an untimed wait");
|
||||
await harness.emit("before_agent_start", { prompt: "unrelated question", systemPrompt: "base" });
|
||||
await harness.emit("tool_result", { toolName: "read", isError: false });
|
||||
await harness.emit("agent_settled");
|
||||
assert.deepEqual(harness.state().activeWait, wait);
|
||||
assert.equal(harness.sent.length, before);
|
||||
await harness.emit("before_agent_start", { prompt: "dependency result arrived", systemPrompt: "base" });
|
||||
await report.execute("progress", { status: "in_progress", evidence: "dependency verified", progress: { ...waitReport.progress, kind: "measurement", lastMeasurement: "verified input" } }, undefined, undefined, harness.ctx);
|
||||
assert.equal(harness.state().activeWait, undefined);
|
||||
await harness.emit("agent_settled");
|
||||
assert.equal(harness.sent.length, before + 1, "verified progress allows continuation");
|
||||
} finally {
|
||||
await harness.emit("session_shutdown"); harness.restoreEnv(); rmSync(dir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test("untimed wait: explicit resume reconciles once, paused waits remain owner-controlled", async () => {
|
||||
const dir = mkdtempSync(join(tmpdir(), "goal-untimed-resume-"));
|
||||
const harness = await createHeadlessHarness(dir, "untimed-resume");
|
||||
try {
|
||||
const goal = harness.commands.get("goal");
|
||||
const report = harness.tools.get("goal_report");
|
||||
await goal.handler("wait for coordinator", harness.ctx);
|
||||
await report.execute("wait", waitReport, undefined, undefined, harness.ctx);
|
||||
await goal.handler("resume", harness.ctx);
|
||||
assert.equal(harness.sent.length, 2, "resume must work for an active waiting goal");
|
||||
assert.equal(harness.state().activeWait, undefined);
|
||||
await report.execute("still-waiting", waitReport, undefined, undefined, harness.ctx);
|
||||
await harness.emit("agent_settled");
|
||||
assert.equal(harness.sent.length, 2);
|
||||
await goal.handler("stop", harness.ctx);
|
||||
await harness.emit("before_agent_start", { prompt: "dependency result", systemPrompt: "base" });
|
||||
await harness.emit("agent_settled");
|
||||
assert.equal(harness.state().status, "paused");
|
||||
assert.equal(harness.sent.length, 2);
|
||||
await goal.handler("resume", harness.ctx);
|
||||
assert.equal(harness.sent.length, 3);
|
||||
assert.equal(harness.state().activeWait, undefined);
|
||||
} finally {
|
||||
await harness.emit("session_shutdown"); harness.restoreEnv(); rmSync(dir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test("quiet wait: no heartbeats, one deadline wake, unresolved dependency pauses", async (t) => {
|
||||
t.mock.timers.enable({ apis: ["setTimeout", "Date"], now: 100000 });
|
||||
const dir = mkdtempSync(join(tmpdir(), "goal-quiet-runtime-"));
|
||||
|
||||
Reference in New Issue
Block a user