@@ -0,0 +1,599 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { cpSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import { dirname, join } from "node:path";
|
||||
import { fileURLToPath, pathToFileURL } from "node:url";
|
||||
import { test } from "node:test";
|
||||
|
||||
import { fencedStateFilePath } from "../lib/store.ts";
|
||||
import {
|
||||
publishGoalPolicy,
|
||||
registerGoalTrackedItemResolver,
|
||||
resetGoalPolicyRegistryForTest,
|
||||
} from "../../mosaic-core/lib/goal-policy.ts";
|
||||
|
||||
interface SentMessage {
|
||||
content: string;
|
||||
options?: { deliverAs?: "steer" | "followUp" };
|
||||
}
|
||||
|
||||
const GOAL_ROOT = dirname(fileURLToPath(new URL("../index.ts", import.meta.url)));
|
||||
const INCARNATION_SOURCE = join(GOAL_ROOT, "..", "mosaic-core", "lib", "incarnation.ts");
|
||||
const EXECUTIVE_UPDATE_FORMAT = "structured progress in the ms-executive-update format (Just Completed / Next Step / Blocked)";
|
||||
const PROACTIVE_LOOP_DIRECTIVE =
|
||||
"Before reporting, run the ms-proactive-agent loop: reconcile records, perform the next authorized ready task, and verify its result. If no task can proceed, report a concrete wait or blocker. This extension owns goal lifecycle; use skills for task tracking, not a second goal loop.";
|
||||
|
||||
async function loadGoalExtension(root: string): Promise<(pi: any) => void> {
|
||||
const extensionRoot = join(root, "extensions", "goal-runtime");
|
||||
cpSync(join(GOAL_ROOT, "lib"), join(extensionRoot, "lib"), { recursive: true });
|
||||
mkdirSync(join(root, "extensions", "mosaic-core", "lib"), { recursive: true });
|
||||
cpSync(INCARNATION_SOURCE, join(root, "extensions", "mosaic-core", "lib", "incarnation.ts"));
|
||||
cpSync(join(GOAL_ROOT, "..", "mosaic-core", "lib", "goal-policy.ts"), join(root, "extensions", "mosaic-core", "lib", "goal-policy.ts"));
|
||||
|
||||
const source = readFileSync(join(GOAL_ROOT, "index.ts"), "utf8");
|
||||
const withoutTypebox = source.replace(
|
||||
'import { Type } from "typebox";',
|
||||
'const Type = { Object: (properties: unknown) => ({ type: "object", properties }), Optional: (schema: unknown) => schema, String: (options: unknown = {}) => ({ type: "string", ...(options as object) }) };',
|
||||
);
|
||||
const selfContained = withoutTypebox.replace(
|
||||
'import { StringEnum } from "@earendil-works/pi-ai";',
|
||||
'const StringEnum = (values: readonly string[]) => ({ type: "string", enum: values });',
|
||||
);
|
||||
assert.notEqual(withoutTypebox, source, "headless harness must replace TypeBox runtime import");
|
||||
assert.notEqual(selfContained, withoutTypebox, "headless harness must replace pi-ai runtime import");
|
||||
writeFileSync(join(extensionRoot, "index.ts"), selfContained);
|
||||
return (await import(`${pathToFileURL(join(extensionRoot, "index.ts")).href}?runtime=${Date.now()}`)).default;
|
||||
}
|
||||
|
||||
async function createHeadlessHarness(root: string, incarnation: string) {
|
||||
const agentDir = join(root, "state", "goal");
|
||||
const handlers = new Map<string, Array<(event: any, ctx: any) => any>>();
|
||||
const commands = new Map<string, any>();
|
||||
const tools = new Map<string, any>();
|
||||
const sent: SentMessage[] = [];
|
||||
const notifications: Array<{ message: string; level: string }> = [];
|
||||
const widgets: unknown[] = [];
|
||||
const statuses: string[] = [];
|
||||
const shortcuts = new Map<string, any>();
|
||||
|
||||
const previousAgentDir = process.env.PI_CODING_AGENT_DIR;
|
||||
const previousIncarnation = process.env.MOSAIC_LAUNCH_INCARNATION;
|
||||
process.env.PI_CODING_AGENT_DIR = agentDir;
|
||||
process.env.MOSAIC_LAUNCH_INCARNATION = incarnation;
|
||||
|
||||
const pi: any = {
|
||||
on(name: string, handler: (event: any, ctx: any) => any) {
|
||||
handlers.set(name, [...(handlers.get(name) ?? []), handler]);
|
||||
},
|
||||
registerCommand(name: string, command: any) {
|
||||
commands.set(name, command);
|
||||
},
|
||||
registerShortcut(key: string, shortcut: any) { shortcuts.set(key, shortcut); },
|
||||
registerTool(tool: any) {
|
||||
tools.set(tool.name, tool);
|
||||
},
|
||||
sendUserMessage(content: string, options?: SentMessage["options"]) {
|
||||
sent.push({ content, options });
|
||||
},
|
||||
};
|
||||
|
||||
let idle = false;
|
||||
const ctx: any = {
|
||||
mode: "interactive",
|
||||
hasUI: true,
|
||||
isIdle: () => idle,
|
||||
ui: {
|
||||
theme: { fg: (color: string, text: string) => `[${color}]${text}` },
|
||||
setStatus(_id: string, text: string) { statuses.push(text); },
|
||||
notify(message: string, level: string) {
|
||||
notifications.push({ message, level });
|
||||
},
|
||||
setWidget(_id: string, value: unknown) {
|
||||
widgets.push(value);
|
||||
},
|
||||
},
|
||||
sessionManager: {
|
||||
getBranch: () => [{ type: "message", message: { role: "assistant", stopReason: "stop" } }],
|
||||
},
|
||||
};
|
||||
|
||||
const goalExtension = await loadGoalExtension(root);
|
||||
goalExtension(pi);
|
||||
|
||||
return {
|
||||
commands,
|
||||
tools,
|
||||
sent,
|
||||
notifications,
|
||||
widgets,
|
||||
statuses,
|
||||
shortcuts,
|
||||
ctx,
|
||||
setIdle(value: boolean) {
|
||||
idle = value;
|
||||
},
|
||||
async emit(name: string, event: any = { type: name }) {
|
||||
for (const handler of handlers.get(name) ?? []) await handler(event, ctx);
|
||||
},
|
||||
state() {
|
||||
return JSON.parse(readFileSync(fencedStateFilePath(agentDir, incarnation), "utf8"));
|
||||
},
|
||||
restoreEnv() {
|
||||
if (previousAgentDir === undefined) delete process.env.PI_CODING_AGENT_DIR;
|
||||
else process.env.PI_CODING_AGENT_DIR = previousAgentDir;
|
||||
if (previousIncarnation === undefined) delete process.env.MOSAIC_LAUNCH_INCARNATION;
|
||||
else process.env.MOSAIC_LAUNCH_INCARNATION = previousIncarnation;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
test("goal reporting instructions name the executive-update format and proactive loop", () => {
|
||||
const source = readFileSync(join(GOAL_ROOT, "index.ts"), "utf8");
|
||||
const checkPrompt = source.slice(source.indexOf("function checkPrompt(): string"), source.indexOf("/** Inject one check prompt"));
|
||||
const goalReport = source.slice(source.indexOf('name: "goal_report"'), source.indexOf("promptSnippet:", source.indexOf('name: "goal_report"')));
|
||||
const beforeAgentStart = source.slice(source.indexOf('pi.on("before_agent_start"'), source.indexOf('pi.on("tool_result"'));
|
||||
|
||||
for (const [section, text] of Object.entries({ checkPrompt, goalReport, beforeAgentStart })) {
|
||||
assert.ok(text.includes(EXECUTIVE_UPDATE_FORMAT), `${section} must name the executive-update format`);
|
||||
assert.ok(text.includes(PROACTIVE_LOOP_DIRECTIVE), `${section} must name the proactive loop`);
|
||||
}
|
||||
});
|
||||
|
||||
test("headless runtime exercises goal_report, agent_settled, follow-up injection, and pause", async () => {
|
||||
const dir = mkdtempSync(join(tmpdir(), "goal-ng8-runtime-"));
|
||||
const harness = await createHeadlessHarness(dir, "ng8-headless-runtime");
|
||||
try {
|
||||
const goal = harness.commands.get("goal");
|
||||
const report = harness.tools.get("goal_report");
|
||||
assert.ok(goal);
|
||||
assert.ok(report);
|
||||
|
||||
await goal.handler("ship safely", harness.ctx);
|
||||
assert.equal(harness.sent.length, 1);
|
||||
assert.equal(harness.sent[0].options?.deliverAs, "followUp");
|
||||
assert.match(harness.sent[0].content, /no-progress 0\/3/);
|
||||
|
||||
for (let cycle = 1; cycle <= 3; cycle++) {
|
||||
const result = await report.execute(`report-${cycle}`, { status: "in_progress" }, undefined, undefined, harness.ctx);
|
||||
await harness.emit("tool_result", {
|
||||
type: "tool_result",
|
||||
toolCallId: `report-${cycle}`,
|
||||
toolName: "goal_report",
|
||||
input: { status: "in_progress" },
|
||||
content: result.content,
|
||||
isError: false,
|
||||
});
|
||||
await harness.emit("agent_settled");
|
||||
if (cycle < 3) assert.equal(harness.sent.length, cycle + 1);
|
||||
}
|
||||
|
||||
assert.equal(harness.sent.length, 3, "paused loop injects no fourth check");
|
||||
assert.equal(harness.state().status, "paused");
|
||||
assert.equal(harness.state().noProgressReports, 3);
|
||||
assert.ok(harness.notifications.some((notice) => notice.level === "warning" && /no substantive progress/.test(notice.message)));
|
||||
} finally {
|
||||
harness.restoreEnv();
|
||||
rmSync(dir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test("C7/C8: attested parser and resolver reject before Goal state changes", async () => {
|
||||
resetGoalPolicyRegistryForTest();
|
||||
const dir = mkdtempSync(join(tmpdir(), "goal-v4-policy-"));
|
||||
const incarnation = "inc-goal-v4";
|
||||
const harness = await createHeadlessHarness(dir, incarnation);
|
||||
try {
|
||||
assert.equal(registerGoalTrackedItemResolver(incarnation, async () => Object.freeze({ outcome: "zero" as const })).ok, true);
|
||||
const journalReasons: string[] = [];
|
||||
const publication = publishGoalPolicy({
|
||||
schemaVersion: 1,
|
||||
role: "plan-ng",
|
||||
roleRevision: 4,
|
||||
manifestSha256: "a".repeat(64),
|
||||
format: "ms-executive-update/v1",
|
||||
contractPath: "skills-local/ms-executive-update/SKILL.md",
|
||||
contractSection: "Machine contract (for `goal_report` payloads and any parser)",
|
||||
contractBlob: "df30c6fbb54b4a65a298c9e51c07f742610d171c",
|
||||
contractSha256: "bbea48a46b1f8da7bc759f86856fb52830b7dde456b826317163c6dc6ccab319",
|
||||
enforcement: "pre-state-change-fail-closed",
|
||||
identifierResolution: "consumer-fail-closed",
|
||||
launchGeneration: 1,
|
||||
incarnationId: incarnation,
|
||||
}, (reason) => journalReasons.push(reason));
|
||||
assert.equal(publication.ok, true);
|
||||
|
||||
const goal = harness.commands.get("goal");
|
||||
const report = harness.tools.get("goal_report");
|
||||
await goal.handler("attested policy", harness.ctx);
|
||||
const before = harness.state();
|
||||
const update = [
|
||||
"Just Completed:", "", "* T165: contracts added", "", "Next Step:", "", "* T165: run tests", "", "Blocked:", "", "* T165: none \u2014 nothing from you", "",
|
||||
].join("\n");
|
||||
await assert.rejects(
|
||||
() => report.execute("attested-reject", { status: "satisfied", evidence: update }, undefined, undefined, harness.ctx),
|
||||
/resolver-zero/,
|
||||
);
|
||||
const after = harness.state();
|
||||
assert.equal(after.status, "active");
|
||||
assert.equal(after.checks, before.checks);
|
||||
assert.equal(after.noProgressReports, before.noProgressReports);
|
||||
|
||||
const noChange = "No change since T165; still waiting on T165.";
|
||||
for (const status of ["satisfied", "blocked"] as const) {
|
||||
await assert.rejects(
|
||||
() => report.execute(`no-change-${status}`, { status, evidence: noChange }, undefined, undefined, harness.ctx),
|
||||
/no-change-status/,
|
||||
);
|
||||
}
|
||||
const terminalAfter = harness.state();
|
||||
assert.equal(terminalAfter.status, "active", "unchanged form cannot settle or pause the goal");
|
||||
assert.equal(terminalAfter.checks, before.checks);
|
||||
assert.equal(terminalAfter.noProgressReports, before.noProgressReports);
|
||||
assert.deepEqual(
|
||||
journalReasons,
|
||||
["resolver-zero", "no-change-status", "no-change-status"],
|
||||
"all denials journal stable codes without report payload text",
|
||||
);
|
||||
} finally {
|
||||
resetGoalPolicyRegistryForTest();
|
||||
harness.restoreEnv();
|
||||
rmSync(dir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test("headless runtime accepts a tool-backed measurement and preserves terminal behavior", async () => {
|
||||
const dir = mkdtempSync(join(tmpdir(), "goal-ng8-runtime-work-"));
|
||||
const harness = await createHeadlessHarness(dir, "ng8-headless-work");
|
||||
try {
|
||||
const goal = harness.commands.get("goal");
|
||||
const report = harness.tools.get("goal_report");
|
||||
await goal.handler("measure", harness.ctx);
|
||||
|
||||
await harness.emit("tool_result", {
|
||||
type: "tool_result",
|
||||
toolCallId: "read-error",
|
||||
toolName: "read",
|
||||
input: { path: "/tmp/missing" },
|
||||
content: [{ type: "text", text: "missing" }],
|
||||
isError: true,
|
||||
});
|
||||
assert.equal(harness.state().workEventSinceReport, false, "failed tools are not progress events");
|
||||
|
||||
await harness.emit("tool_result", {
|
||||
type: "tool_result",
|
||||
toolCallId: "read-1",
|
||||
toolName: "read",
|
||||
input: { path: "/tmp/status" },
|
||||
content: [{ type: "text", text: "measured" }],
|
||||
isError: false,
|
||||
});
|
||||
assert.equal(harness.state().workEventSinceReport, true);
|
||||
|
||||
const progress = {
|
||||
kind: "measurement",
|
||||
gate: "review",
|
||||
owner: "reviewer",
|
||||
lastMeasurement: "head abc remains pending at 2026-08-31T15:00:00Z",
|
||||
nextAction: "dispatch reviewer",
|
||||
};
|
||||
const accepted = await report.execute(
|
||||
"progress-1",
|
||||
{ status: "in_progress", evidence: "measured exact head", progress },
|
||||
undefined,
|
||||
undefined,
|
||||
harness.ctx,
|
||||
);
|
||||
assert.match(accepted.content[0].text, /Recorded substantive progress/);
|
||||
assert.equal(harness.state().noProgressReports, 0);
|
||||
assert.equal(harness.state().workEventSinceReport, false);
|
||||
|
||||
await goal.handler("done goal", harness.ctx);
|
||||
await assert.rejects(
|
||||
() => report.execute("satisfied-empty", { status: "satisfied" }, undefined, undefined, harness.ctx),
|
||||
/requires non-empty evidence/,
|
||||
);
|
||||
const satisfied = await report.execute(
|
||||
"satisfied",
|
||||
{ status: "satisfied", evidence: "acceptance evidence" },
|
||||
undefined,
|
||||
undefined,
|
||||
harness.ctx,
|
||||
);
|
||||
assert.match(satisfied.content[0].text, /SATISFIED/);
|
||||
assert.equal(harness.state().status, "none");
|
||||
|
||||
await goal.handler("blocked goal", harness.ctx);
|
||||
const blocked = await report.execute(
|
||||
"blocked",
|
||||
{ status: "blocked", evidence: "missing credential" },
|
||||
undefined,
|
||||
undefined,
|
||||
harness.ctx,
|
||||
);
|
||||
assert.match(blocked.content[0].text, /BLOCKED/);
|
||||
assert.equal(harness.state().status, "blocked");
|
||||
} finally {
|
||||
harness.restoreEnv();
|
||||
rmSync(dir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
const waitReport = {
|
||||
status: "in_progress", evidence: "Input is pending",
|
||||
progress: { kind: "wait", gate: "input", owner: "operator", lastMeasurement: "input absent",
|
||||
nextAction: "Read input and finish output", nextCheck: "input file available" },
|
||||
};
|
||||
|
||||
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-"));
|
||||
const harness = await createHeadlessHarness(dir, "quiet-runtime");
|
||||
try {
|
||||
harness.setIdle(true);
|
||||
const goal = harness.commands.get("goal");
|
||||
const report = harness.tools.get("goal_report");
|
||||
await goal.handler("--wait-timeout 60 write output", harness.ctx);
|
||||
await report.execute("wait-1", waitReport, undefined, undefined, harness.ctx);
|
||||
const deadline = harness.state().activeWait.deadlineAt;
|
||||
for (let i = 0; i < 100; i++) await harness.emit("agent_settled");
|
||||
assert.equal(harness.sent.length, 1, "settle events cannot generate idle heartbeats");
|
||||
t.mock.timers.tick(30000);
|
||||
await report.execute("wait-2", { ...waitReport, evidence: "Still pending, differently worded" }, undefined, undefined, harness.ctx);
|
||||
assert.equal(harness.state().activeWait.deadlineAt, deadline, "unchanged waits never extend deadline");
|
||||
t.mock.timers.tick(29999);
|
||||
assert.equal(harness.sent.length, 1);
|
||||
t.mock.timers.tick(1);
|
||||
assert.equal(harness.sent.length, 2);
|
||||
assert.match(harness.sent[1].content, /Wait deadline reached/);
|
||||
assert.equal(harness.state().waitWakeUsed, true);
|
||||
const result = await report.execute("wait-3", waitReport, undefined, undefined, harness.ctx);
|
||||
assert.equal(result.details.goalStatus, "paused");
|
||||
t.mock.timers.tick(86400000);
|
||||
await harness.emit("agent_settled");
|
||||
assert.equal(harness.sent.length, 2, "unresolved deadline never re-arms itself");
|
||||
await assert.rejects(() => report.execute("late-satisfied", { status: "satisfied", evidence: "late result" }, undefined, undefined, harness.ctx), /paused/);
|
||||
} finally {
|
||||
await harness.emit("session_shutdown");
|
||||
harness.restoreEnv();
|
||||
rmSync(dir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test("quiet wait: persisted deadline survives reload without an immediate check", async (t) => {
|
||||
t.mock.timers.enable({ apis: ["setTimeout", "Date"], now: 100000 });
|
||||
const dir = mkdtempSync(join(tmpdir(), "goal-quiet-reload-"));
|
||||
const harness = await createHeadlessHarness(dir, "quiet-reload");
|
||||
try {
|
||||
harness.setIdle(true);
|
||||
await harness.commands.get("goal").handler("--wait-timeout 60 write output", harness.ctx);
|
||||
await harness.tools.get("goal_report").execute("wait", waitReport, undefined, undefined, harness.ctx);
|
||||
await harness.emit("session_shutdown");
|
||||
t.mock.timers.tick(30000);
|
||||
await harness.emit("session_start");
|
||||
assert.equal(harness.sent.length, 1);
|
||||
t.mock.timers.tick(30000);
|
||||
assert.equal(harness.sent.length, 2);
|
||||
assert.match(harness.sent[1].content, /Wait deadline reached/);
|
||||
} finally {
|
||||
await harness.emit("session_shutdown");
|
||||
harness.restoreEnv();
|
||||
rmSync(dir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test("quiet wait: stop, clear, replacement, and completion cancel the timer", async (t) => {
|
||||
t.mock.timers.enable({ apis: ["setTimeout", "Date"], now: 100000 });
|
||||
const dir = mkdtempSync(join(tmpdir(), "goal-quiet-cancel-"));
|
||||
const harness = await createHeadlessHarness(dir, "quiet-cancel");
|
||||
try {
|
||||
harness.setIdle(true);
|
||||
const goal = harness.commands.get("goal");
|
||||
const report = harness.tools.get("goal_report");
|
||||
for (const operation of ["stop", "clear", "replacement", "satisfied"]) {
|
||||
await goal.handler("--wait-timeout 60 write output", harness.ctx);
|
||||
await report.execute("wait", waitReport, undefined, undefined, harness.ctx);
|
||||
if (operation === "satisfied") {
|
||||
await report.execute("done", { status: "satisfied", evidence: "Output verified" }, undefined, undefined, harness.ctx);
|
||||
} else {
|
||||
await goal.handler(operation, harness.ctx);
|
||||
}
|
||||
const before = harness.sent.length;
|
||||
t.mock.timers.tick(61000);
|
||||
assert.equal(harness.sent.length, before, `${operation} leaves no deadline wake`);
|
||||
}
|
||||
} finally {
|
||||
await harness.emit("session_shutdown");
|
||||
harness.restoreEnv();
|
||||
rmSync(dir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test("quiet wait: a busy turn is allowed to settle before deadline injection", async (t) => {
|
||||
t.mock.timers.enable({ apis: ["setTimeout", "Date"], now: 100000 });
|
||||
const dir = mkdtempSync(join(tmpdir(), "goal-quiet-busy-"));
|
||||
const harness = await createHeadlessHarness(dir, "quiet-busy");
|
||||
try {
|
||||
await harness.commands.get("goal").handler("--wait-timeout 60 write output", harness.ctx);
|
||||
await harness.tools.get("goal_report").execute("wait", waitReport, undefined, undefined, harness.ctx);
|
||||
t.mock.timers.tick(60000);
|
||||
assert.equal(harness.sent.length, 1, "no deadline queued during a running turn");
|
||||
await harness.commands.get("goal").handler("stop", harness.ctx);
|
||||
await harness.emit("agent_settled");
|
||||
assert.equal(harness.sent.length, 1, "stop cancels deferred deadline");
|
||||
await harness.commands.get("goal").handler("resume", harness.ctx);
|
||||
assert.equal(harness.state().activeWait, undefined);
|
||||
await harness.tools.get("goal_report").execute("wait-again", waitReport, undefined, undefined, harness.ctx);
|
||||
t.mock.timers.tick(60000);
|
||||
const before = harness.sent.length;
|
||||
await harness.emit("agent_settled");
|
||||
assert.equal(harness.sent.length, before + 1);
|
||||
} finally {
|
||||
await harness.emit("session_shutdown");
|
||||
harness.restoreEnv();
|
||||
rmSync(dir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test("quiet wait: relevant progress cancels the wait; print mode has no timer wake", async (t) => {
|
||||
t.mock.timers.enable({ apis: ["setTimeout", "Date"], now: 100000 });
|
||||
const dir = mkdtempSync(join(tmpdir(), "goal-quiet-progress-"));
|
||||
const harness = await createHeadlessHarness(dir, "quiet-progress");
|
||||
try {
|
||||
harness.setIdle(true);
|
||||
const goal = harness.commands.get("goal");
|
||||
const report = harness.tools.get("goal_report");
|
||||
await goal.handler("--wait-timeout 60 write output", harness.ctx);
|
||||
await report.execute("wait", waitReport, undefined, undefined, harness.ctx);
|
||||
await harness.emit("tool_result", { toolName: "read", isError: false });
|
||||
await report.execute("progress", { status: "in_progress", evidence: "Input arrived and was verified", progress: { ...waitReport.progress, kind: "measurement", lastMeasurement: "Input present" } }, undefined, undefined, harness.ctx);
|
||||
assert.equal(harness.state().activeWait, undefined);
|
||||
t.mock.timers.tick(60000);
|
||||
assert.equal(harness.sent.length, 1);
|
||||
await harness.emit("agent_settled");
|
||||
assert.equal(harness.sent.length, 2, "ready work continues after wait is resolved");
|
||||
harness.ctx.mode = "print";
|
||||
await goal.handler("--wait-timeout 60 write output", harness.ctx);
|
||||
await report.execute("wait-print", waitReport, undefined, undefined, harness.ctx);
|
||||
t.mock.timers.tick(60000);
|
||||
await harness.emit("agent_settled");
|
||||
assert.equal(harness.sent.length, 2, "print mode never schedules a model wake");
|
||||
} finally {
|
||||
await harness.emit("session_shutdown");
|
||||
harness.restoreEnv();
|
||||
rmSync(dir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test("deadline readiness recovers after compaction without agent_settled", async (t) => {
|
||||
t.mock.timers.enable({ apis: ["setTimeout", "Date"], now: 100000 });
|
||||
const dir = mkdtempSync(join(tmpdir(), "goal-compaction-"));
|
||||
const harness = await createHeadlessHarness(dir, "quiet-compaction");
|
||||
try {
|
||||
await harness.commands.get("goal").handler("--wait-timeout 60 output", harness.ctx);
|
||||
await harness.tools.get("goal_report").execute("wait", waitReport, undefined, undefined, harness.ctx);
|
||||
t.mock.timers.tick(60000);
|
||||
assert.equal(harness.sent.length, 1);
|
||||
harness.setIdle(true);
|
||||
t.mock.timers.tick(1000);
|
||||
assert.equal(harness.sent.length, 2, "runtime readiness alone releases the deferred wake");
|
||||
} finally {
|
||||
await harness.emit("session_shutdown"); harness.restoreEnv(); rmSync(dir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test("unobserved deadline dispatch pauses once; observed start cancels watchdog", async (t) => {
|
||||
t.mock.timers.enable({ apis: ["setTimeout", "Date"], now: 100000 });
|
||||
const dir = mkdtempSync(join(tmpdir(), "goal-dispatch-"));
|
||||
const harness = await createHeadlessHarness(dir, "quiet-dispatch");
|
||||
try {
|
||||
harness.setIdle(true);
|
||||
const goal = harness.commands.get("goal");
|
||||
const report = harness.tools.get("goal_report");
|
||||
await goal.handler("--wait-timeout 60 output", harness.ctx);
|
||||
await report.execute("wait", waitReport, undefined, undefined, harness.ctx);
|
||||
t.mock.timers.tick(60000);
|
||||
const sent = harness.sent.length;
|
||||
// Ordinary Pi sendUserMessage returns void; a rejected async delivery may never start.
|
||||
t.mock.timers.tick(30000);
|
||||
assert.equal(harness.state().status, "paused");
|
||||
assert.match(harness.state().pausedReason, /not observed/);
|
||||
assert.equal(harness.sent.length, sent, "watchdog never retries a potentially delivered request");
|
||||
await goal.handler("resume", harness.ctx);
|
||||
await report.execute("wait-again", waitReport, undefined, undefined, harness.ctx);
|
||||
t.mock.timers.tick(60000);
|
||||
await harness.emit("before_agent_start", { prompt: harness.sent.at(-1)!.content, systemPrompt: "base" });
|
||||
assert.equal(harness.state().activeWait.wakeObserved, true);
|
||||
t.mock.timers.tick(30000);
|
||||
assert.equal(harness.state().status, "active", "observed model start cancels the acknowledgment timer");
|
||||
} finally {
|
||||
await harness.emit("session_shutdown"); harness.restoreEnv(); rmSync(dir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test("continuously busy runtime pauses after bounded readiness checks", async (t) => {
|
||||
t.mock.timers.enable({ apis: ["setTimeout", "Date"], now: 100000 });
|
||||
const dir = mkdtempSync(join(tmpdir(), "goal-busy-limit-"));
|
||||
const harness = await createHeadlessHarness(dir, "quiet-busy-limit");
|
||||
try {
|
||||
await harness.commands.get("goal").handler("--wait-timeout 60 output", harness.ctx);
|
||||
await harness.tools.get("goal_report").execute("wait", waitReport, undefined, undefined, harness.ctx);
|
||||
t.mock.timers.tick(60000);
|
||||
for (let i = 0; i < 60; i++) t.mock.timers.tick(1000);
|
||||
assert.equal(harness.state().status, "paused");
|
||||
assert.match(harness.state().pausedReason, /remained busy/);
|
||||
assert.equal(harness.sent.length, 1);
|
||||
} finally {
|
||||
await harness.emit("session_shutdown"); harness.restoreEnv(); rmSync(dir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test("footer and both recall paths cover lifecycle without adding widget content", async () => {
|
||||
const dir = mkdtempSync(join(tmpdir(), "goal-footer-"));
|
||||
const harness = await createHeadlessHarness(dir, "footer");
|
||||
const previousNoColor = process.env.NO_COLOR;
|
||||
delete process.env.NO_COLOR;
|
||||
try {
|
||||
const goal = harness.commands.get("goal");
|
||||
const report = harness.tools.get("goal_report");
|
||||
const text = "Full goal " + "acceptance criteria ".repeat(100) + " END-OF-GOAL";
|
||||
await harness.emit("session_start");
|
||||
assert.match(harness.statuses.at(-1)!, /\[muted\]Goal: None/);
|
||||
await goal.handler(text, harness.ctx);
|
||||
assert.match(harness.statuses.at(-1)!, /\[accent\]Goal: Active/);
|
||||
const sent = harness.sent.length;
|
||||
await goal.handler("", harness.ctx);
|
||||
const recall = harness.notifications.at(-1)!.message;
|
||||
assert.ok(recall.includes(text));
|
||||
await harness.shortcuts.get("alt+g").handler(harness.ctx);
|
||||
assert.equal(harness.notifications.at(-1)!.message, recall);
|
||||
assert.equal(harness.sent.length, sent, "recall never starts a turn");
|
||||
assert.equal(harness.shortcuts.has("tab"), false);
|
||||
await report.execute("wait", waitReport, undefined, undefined, harness.ctx);
|
||||
assert.match(harness.statuses.at(-1)!, /\[warning\]Goal: Waiting/);
|
||||
await goal.handler("stop", harness.ctx);
|
||||
assert.match(harness.statuses.at(-1)!, /\[warning\]Goal: Paused/);
|
||||
await goal.handler("resume", harness.ctx);
|
||||
await report.execute("blocked", { status: "blocked", evidence: "permission needed" }, undefined, undefined, harness.ctx);
|
||||
assert.match(harness.statuses.at(-1)!, /\[error\]Goal: Blocked/);
|
||||
await goal.handler("resume", harness.ctx);
|
||||
await report.execute("done", { status: "satisfied", evidence: "verified" }, undefined, undefined, harness.ctx);
|
||||
assert.match(harness.statuses.at(-1)!, /\[success\]Goal: Complete/);
|
||||
assert.equal(harness.state().lastOutcome.text, text);
|
||||
const finishedSent = harness.sent.length;
|
||||
await harness.emit("agent_settled");
|
||||
await harness.emit("session_start");
|
||||
await goal.handler("resume", harness.ctx);
|
||||
assert.equal(harness.sent.length, finishedSent);
|
||||
await goal.handler("", harness.ctx);
|
||||
assert.ok(harness.notifications.at(-1)!.message.includes(text));
|
||||
process.env.NO_COLOR = "1";
|
||||
await harness.emit("session_start");
|
||||
assert.equal(harness.statuses.at(-1), "Goal: Complete (/goal, Alt+G)");
|
||||
await goal.handler("clear", harness.ctx);
|
||||
assert.equal(harness.state().lastOutcome, undefined);
|
||||
assert.equal(harness.statuses.at(-1), "Goal: None (/goal, Alt+G)");
|
||||
assert.ok(harness.widgets.every(value => value === undefined));
|
||||
} finally {
|
||||
if (previousNoColor === undefined) delete process.env.NO_COLOR;
|
||||
else process.env.NO_COLOR = previousNoColor;
|
||||
await harness.emit("session_shutdown"); harness.restoreEnv(); rmSync(dir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test("state-write failure cannot report goal completion or inject another check", async () => {
|
||||
const dir = mkdtempSync(join(tmpdir(), "goal-save-failure-"));
|
||||
const incarnation = "quiet-save-failure";
|
||||
const harness = await createHeadlessHarness(dir, incarnation);
|
||||
try {
|
||||
await harness.commands.get("goal").handler("--wait-timeout 60 output", harness.ctx);
|
||||
const statePath = fencedStateFilePath(join(dir, "state", "goal"), incarnation);
|
||||
rmSync(statePath);
|
||||
mkdirSync(statePath); // force atomic replacement failure without mocking away filesystem behavior
|
||||
await assert.rejects(() => harness.tools.get("goal_report").execute("done", { status: "satisfied", evidence: "done" }, undefined, undefined, harness.ctx), /could not be saved/);
|
||||
await harness.emit("agent_settled");
|
||||
assert.equal(harness.sent.length, 1);
|
||||
assert.equal(harness.notifications.some((n) => n.message.includes("satisfied and cleared")), false);
|
||||
} finally {
|
||||
await harness.emit("session_shutdown"); harness.restoreEnv(); rmSync(dir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user