Show supervisor advice and restore reliable review lifecycle

Render exact instructions, restore monitoring/read-only tools on resume, report actual Pi idle state, reject stale approval views, and stop completed-plan timers. Ask for brief evidence-based judgment. Add real TUI rendering and lifecycle regressions; retain explicit limits on background state and unmeasured cost benefit.

Co-Authored-By: Pi/OpenAI <288921227+claudypoo@users.noreply.github.com>
This commit is contained in:
wassname
2026-09-08 10:50:18 +08:00
co-authored by Pi/OpenAI
parent 06794bfd44
commit a4ed6cfbaa
12 changed files with 417 additions and 26 deletions
+3 -1
View File
@@ -13,7 +13,9 @@ Plan in one Pi session, then do the work there while a stronger visible Pi sessi
7. The supervisor compacts again when its context reaches 100k tokens.
8. The supervisor records a private approval only after it sees a stopped worker, no active work, a clean commit, evidence, and saved verification output. `CompleteGoal` checks that approval against the exact plan block and Git tree before it ticks `[x]`.
The two Pi sessions are visible. You can switch to the supervisor pane and talk to it directly.
The two Pi sessions are visible. You can switch to the supervisor pane and talk to it directly. Supervisor instructions are shown in full, including in collapsed tool rows; ordinary messages and emitted thinking use Pi's display settings. The supervisor is prompted to give brief progress assessments and use judgment about when to intervene.
On resume, monitoring and read-only tools are restored. Periodic views report whether Pi is idle; they do not measure background jobs. Readiness is a startup receipt, not a continuous health check. Reviews stop after all goals are completed or cancelled, and both panes remain available. These mechanics are tested; useful judgment and savings from a cheaper worker still require a representative two-model run. -- Pi/OpenAI
## Install
+187
View File
File diff suppressed because it is too large Load Diff
+2
View File
@@ -23,6 +23,7 @@
],
"peerDependencies": {
"@earendil-works/pi-coding-agent": "*",
"@earendil-works/pi-tui": "*",
"typebox": "*"
},
"files": [
@@ -45,6 +46,7 @@
"devDependencies": {
"@biomejs/biome": "^2.4.8",
"@earendil-works/pi-coding-agent": "^0.84.1",
"@earendil-works/pi-tui": "^0.85.1",
"@types/node": "^20.0.0",
"typebox": "^1.3.7",
"typescript": "^5.0.0",
@@ -1,6 +1,24 @@
# Review against user intent
Verdict: not achieved.
## Follow-up: code fixes, full goal still unproven
Implemented directly after the subagent runner failed and the user authorized direct work. The tests now exercise full advice in real Pi tool components (collapsed, expanded, restored, streaming arguments), emitted thinking/text display, resume without replay of persisted views, latest-view coalescing, actual idle/busy status, stale-view approval rejection, and stopping completed-plan timers. The supervisor prompt now asks for a brief evidence-based progress assessment and useful judgment instead of instruction-only reviews. Background job status is explicitly unmeasured; approval still requires the supervisor to inspect job evidence when relevant.
[Saved validation output](20260908_supervision-fixes-validation.txt):
> Tests 34 passed (34)
> resumed: deliveredViews=0, activeTools=read, readyReceipt=true
> interval view without any work: The worker stopped.
Typecheck and lint also succeeded in that log. The reproduction script now asserts the corrected behavior; the original reproduction output below is retained as historical evidence. Readiness is cleared on startup and normal shutdown, but it is not a heartbeat or proof of worker receipt. The review here is my own source/diff review, not the independent review that failed to launch. Existing user panes and the separate pi-supervise worktree were not modified.
Remaining acceptance: a real isolated two-pane run with the intended model pair, observed useful advice and worker response, plus measured token/cost totals. Prompt assertions do not establish judgment quality. No full-goal completion is claimed.
-- Pi/OpenAI
## Original review
Verdict at `06794bf`: not achieved.
Reviewed `experiment/goals-owned-supervision` at `4ebb4d1` against [AGENTS.md](../../AGENTS.md#user-intent-for-this-branch). This is a source review and isolated runtime reproduction by Pi/OpenAI, not an independent model review or a real two-pane acceptance test. No existing session or pane was operated.
@@ -0,0 +1,26 @@
> @wassname2/pi-goals@0.2.2 test
> vitest run
RUN v4.1.9 /home/code/.pi/agent/git/github.com/wassname/pi-goals
Test Files 9 passed (9)
Tests 34 passed (34)
Start at 10:49:05
Duration 1.34s (transform 639ms, setup 0ms, import 2.76s, tests 1.68s, environment 1ms)
> @wassname2/pi-goals@0.2.2 typecheck
> tsc --noEmit
> @wassname2/pi-goals@0.2.2 lint
> biome check src/ test/
Checked 17 files in 20ms. No fixes applied.
fresh: deliveredViews=1, activeTools=read, readyReceipt=true
steer: renderCall=function, result=Worker instruction 1 recorded. Worker receipt and execution are not confirmed.
resumed: deliveredViews=0, activeTools=read, readyReceipt=true
interval view without any work: The worker stopped.
@@ -23,7 +23,10 @@ function runtime() {
on: (name: string, handler: any) => hooks.set(name, handler),
registerTool: (tool: any) => tools.set(tool.name, tool),
appendEntry: (customType: string, data: unknown) => entries.push({ type: "custom", customType, data }),
sendUserMessage: (message: string) => messages.push(message),
sendUserMessage: (text: string) => {
messages.push(text);
entries.push({ type: "message", message: { role: "user", content: [{ type: "text", text }] } });
},
getActiveTools: () => activeTools,
setActiveTools: (tools: string[]) => { activeTools = tools; },
};
@@ -44,17 +47,20 @@ try {
await new Promise(setImmediate);
console.log(`${name}: deliveredViews=${run.messages.length}, activeTools=${run.activeTools().join(",")}, readyReceipt=${supervisorReady(mailbox.path)}`);
assert.equal(run.messages.length, name === "fresh" ? 1 : 0);
assert.deepEqual(run.activeTools(), name === "fresh" ? ["read"] : ["read", "write", "bash"]);
assert.deepEqual(run.activeTools(), ["read"]);
if (name === "fresh") {
const tool = run.tools.get("SteerWorker");
const result = await tool.execute("id", { instruction: "Compare the signs in the two saved outputs." });
assert.equal(typeof tool.renderCall, "function");
console.log(`steer: renderCall=${typeof tool.renderCall}, result=${result.content[0].text}`);
}
} finally {
await run.hooks.get("session_shutdown")();
}
}
console.log(`interval view without any work: ${workerView([], "interval").split("\n")[0]}`);
const idleView = workerView([], "interval", true).split("\n")[0];
assert.equal(idleView, "The worker stopped.");
console.log(`interval view without any work: ${idleView}`);
} finally {
rmSync(cwd, { recursive: true, force: true });
}
+12 -2
View File
@@ -184,9 +184,15 @@ export default function piGoalsExtension(pi: ExtensionAPI): void {
return createMailbox(ctx.cwd, ctx.sessionManager.getSessionId(), state.approvalId, planPath(ctx));
}
function publishWorkerView(ctx: ExtensionContext, reason: "ready" | "settled" | "turns" | "interval"): void {
function publishWorkerView(ctx: ExtensionContext, reason: "ready" | "settled" | "turns" | "interval" | "started"): void {
if (state.phase !== "working") return;
writeWorkerView(mailbox(ctx), reason, workerView(ctx.sessionManager.getBranch(), reason));
writeWorkerView(mailbox(ctx), reason, workerView(ctx.sessionManager.getBranch(), reason, reason !== "started" && ctx.isIdle()));
const goals = scanGoals(readPlan(ctx));
if (goals.length > 0 && goals.every((goal) => goal.status === "done" || goal.status === "cancelled")) {
stopWorkerTimers();
state = { ...state, phase: null };
persist();
}
}
function deliverWorkerSteers(ctx: ExtensionContext): void {
@@ -350,6 +356,10 @@ export default function piGoalsExtension(pi: ExtensionAPI): void {
if (state.phase === "planning" && event.source !== "extension") writePlan(ctx, appendInterview(readPlan(ctx), event.text));
});
pi.on("agent_start", async (_event, ctx) => {
publishWorkerView(ctx, "started");
});
pi.on("turn_end", async (_event, ctx) => {
updateWidget(ctx);
if (state.phase !== "working") return;
+3 -2
View File
@@ -15,7 +15,7 @@ export interface SupervisorMailbox {
export interface WorkerView {
version: 1;
sequence: number;
reason: "ready" | "settled" | "turns" | "interval";
reason: "ready" | "settled" | "turns" | "interval" | "started";
text: string;
timestamp: string;
}
@@ -71,7 +71,8 @@ export function supervisorReady(path: string): boolean {
export function writeWorkerView(mailbox: SupervisorMailbox, reason: WorkerView["reason"], text: string): WorkerView {
const directory = join(mailbox.path, VIEWS);
const view: WorkerView = { version: 1, sequence: nextSequence(directory, "view"), reason, text, timestamp: new Date().toISOString() };
const sequence = nextSequence(directory, "view");
const view: WorkerView = { version: 1, sequence, reason, text: `${text}\n\nworker view sequence: ${sequence}`, timestamp: new Date().toISOString() };
writeJson(join(directory, `view-${view.sequence}.json`), view);
return view;
}
+30 -9
View File
@@ -1,6 +1,7 @@
import { readFileSync } from "node:fs";
import { resolve } from "node:path";
import { readFileSync, rmSync } from "node:fs";
import { join, resolve } from "node:path";
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
import { Text } from "@earendil-works/pi-tui";
import { Type } from "typebox";
import { approvalPath, goalBlock, hashGoalBlock, repositoryState, verifyOutputPath, writeApproval } from "./approval.js";
import { readyMailbox, workerViewsAfter, writeWorkerSteer } from "./mailbox.js";
@@ -70,7 +71,7 @@ function latestWorkerView(ctx: ExtensionContext): string | null {
function supervisorPrompt(settings: SupervisorConfig): string {
return `You are the visible pi-goals supervisor for ${settings.planPath}. You are a stronger, read-only reviewer. The other Pi session is the implementation worker and keeps the full conversation. You keep the high-level intent from the compacted planning conversation and worker views. The complete plan at ${settings.planPath} is the source of truth; read it directly after every compaction.
Use SteerWorker to give one concrete instruction when work is incomplete. Do not edit files. For each open goal, inspect its exact plan block, repository state, cited evidence, and a saved nonempty verification-output file. When its discriminator is positively satisfied and the worker view says no work is active, call ApproveGoal with that repository-relative path. Then call SteerWorker and tell the worker to call CompleteGoal with the exact goal text. Do not call done until every plan goal is [x]. -- PI[Kimi K3]`;
At each review, give a brief visible recap of how work is tracking against the goal: what the evidence shows and your judgment about the next step. Add perspective rather than repeating the worker's account. Distinguish observations from guesses. Read more evidence when needed; keep routine recaps short, but do not suppress useful explanation or thinking. Use SteerWorker when a correction or continuation is warranted. If the worker is making useful progress, say why and let it continue; do not invent work or repeat an instruction already awaiting execution. When idle with unfinished goals, give a concrete next step unless blocked on the human. Do not edit files. For each open goal, inspect its exact plan block, repository state, cited evidence, and a saved nonempty verification-output file. A stopped view means Pi is idle, not that background jobs have finished. Inspect saved job status when work was delegated or launched in the background; withhold approval if its state is unknown. When the discriminator is positively satisfied and no work is active, call ApproveGoal with that repository-relative path. Then call SteerWorker and tell the worker to call CompleteGoal with the exact goal text. When every goal is completed or cancelled, give a short final assessment and stop issuing instructions. -- Pi/OpenAI`;
}
export function isVisibleSupervisor(): boolean {
@@ -85,23 +86,34 @@ export function registerVisibleSupervisor(pi: ExtensionAPI): void {
let viewTimer: ReturnType<typeof setInterval> | undefined;
const deliverWorkerViews = (): void => {
for (const view of workerViewsAfter(settings.mailboxPath, deliveredView)) {
const view = workerViewsAfter(settings.mailboxPath, deliveredView).at(-1);
if (view) {
if (view.reason !== "started") pi.sendUserMessage(view.text, { deliverAs: "followUp" });
deliveredView = view.sequence;
pi.sendUserMessage(view.text, { deliverAs: "followUp" });
}
};
const bootstrap = async (ctx: ExtensionContext): Promise<void> => {
if (bootstrapping) return;
const entries = ctx.sessionManager.getEntries();
if (entries.some((entry: { type?: string; customType?: string }) => entry.type === "custom" && entry.customType === BOOTSTRAPPED)) return;
bootstrapping = true;
try {
const active = pi.getActiveTools();
pi.setActiveTools(active.filter((tool) => !WRITER_TOOLS.has(tool.toLowerCase())));
const writers = pi.getActiveTools().filter((tool) => WRITER_TOOLS.has(tool.toLowerCase()));
if (writers.length) throw new Error(`Could not remove supervisor writing tools: ${writers.join(", ")}`);
pi.appendEntry(BOOTSTRAPPED, { version: 2, workerSessionId: settings.workerSessionId, planPath: settings.planPath });
if (!entries.some((entry: { type?: string; customType?: string }) => entry.type === "custom" && entry.customType === BOOTSTRAPPED)) {
pi.appendEntry(BOOTSTRAPPED, { version: 2, workerSessionId: settings.workerSessionId, planPath: settings.planPath });
}
for (const entry of entries) {
const message = (entry as { message?: { role?: string; content?: unknown } }).message;
if (message?.role !== "user" || !Array.isArray(message.content)) continue;
for (const part of message.content) {
if (part.type !== "text" || !part.text.startsWith("The worker ")) continue;
const sequence = /^worker view sequence: (\d+)$/m.exec(part.text);
if (sequence) deliveredView = Math.max(deliveredView, Number(sequence[1]));
}
}
readyMailbox(settings.mailboxPath);
viewTimer = setInterval(deliverWorkerViews, 1_000);
deliverWorkerViews();
@@ -112,7 +124,8 @@ export function registerVisibleSupervisor(pi: ExtensionAPI): void {
const bootstrapAfterInitialCompaction = (ctx: ExtensionContext): void => {
const tokens = ctx.getContextUsage()?.tokens;
if (typeof tokens === "number" && tokens <= INITIAL_COMPACT_AT_TOKENS) {
const resumed = ctx.sessionManager.getEntries().some((entry: { type?: string; customType?: string }) => entry.type === "custom" && entry.customType === BOOTSTRAPPED);
if (resumed || (typeof tokens === "number" && tokens <= INITIAL_COMPACT_AT_TOKENS)) {
void bootstrap(ctx);
return;
}
@@ -132,10 +145,13 @@ export function registerVisibleSupervisor(pi: ExtensionAPI): void {
};
pi.on("session_start", async (_event, ctx) => {
rmSync(join(settings.mailboxPath, "ready.json"), { force: true });
pi.setActiveTools(pi.getActiveTools().filter((tool) => !WRITER_TOOLS.has(tool.toLowerCase())));
setImmediate(() => { bootstrapAfterInitialCompaction(ctx); });
});
pi.on("session_shutdown", async () => {
if (viewTimer) clearInterval(viewTimer);
rmSync(join(settings.mailboxPath, "ready.json"), { force: true });
});
pi.on("before_agent_start", async (_event, ctx) => ({ systemPrompt: `${ctx.getSystemPrompt()}\n\n${supervisorPrompt(settings)}` }));
pi.on("agent_settled", async (_event, ctx) => {
@@ -160,11 +176,14 @@ export function registerVisibleSupervisor(pi: ExtensionAPI): void {
executionMode: "sequential",
description: "Write one concrete instruction for the implementation worker.",
parameters: Type.Object({ instruction: Type.String({ description: "Concrete next instruction for the worker." }) }),
renderCall(args, theme) {
return new Text(`${theme.fg("toolTitle", "Supervisor → worker")}\n${args.instruction ?? ""}`, 0, 0);
},
async execute(_id, params) {
const instruction = params.instruction.trim();
if (!instruction) return result("A worker instruction cannot be empty.", true);
const steer = writeWorkerSteer(settings.mailboxPath, instruction);
return result(`Worker instruction ${steer.sequence} recorded.`);
return result(`Worker instruction ${steer.sequence} recorded. Worker receipt and execution are not confirmed.`);
},
});
@@ -179,6 +198,8 @@ export function registerVisibleSupervisor(pi: ExtensionAPI): void {
}),
async execute(_id, params, _signal, _onUpdate, ctx) {
const view = latestWorkerView(ctx);
const newest = workerViewsAfter(settings.mailboxPath, 0).at(-1);
if (!newest || view !== newest.text) return result("Cannot approve without inspecting the latest worker view.", true);
if (!view?.startsWith("The worker stopped.")) return result("Cannot approve without a current stopped-worker view.", true);
const pendingTool = view.match(/^tool calls with no result: (?!none$)(.+)$/m);
const pendingChild = view.match(/^child pi processes still running: (?!none$)(.+)$/m);
+3 -3
View File
@@ -38,10 +38,10 @@ function outstandingTools(entries: SessionEntry[]): string[] {
return [...calls].filter(([id]) => !results.has(id)).map(([, name]) => name);
}
export function workerView(entries: SessionEntry[], reason: "ready" | "settled" | "turns" | "interval"): string {
export function workerView(entries: SessionEntry[], reason: "ready" | "settled" | "turns" | "interval" | "started", idle: boolean): string {
const summary = [...entries].reverse().find((entry) => entry.type === "compaction" && entry.summary)?.summary;
const recent = entries.flatMap((entry) => entry.type === "message" && entry.message ? [text(entry.message)] : []).filter(Boolean).slice(-12).join("\n\n").slice(-12_000);
const outstanding = outstandingTools(entries);
const state = reason === "settled" ? "stopped" : reason === "ready" ? "is ready to begin" : "is still working";
return `The worker ${state}.\n\nreview trigger: ${reason}\ntool calls with no result: ${outstanding.join(", ") || "none"}\n\n${summary ? `last compaction summary:\n${summary}\n\n` : ""}recent worker transcript:\n${recent || "none"}`;
const state = reason === "ready" ? "is ready to begin" : idle ? "stopped" : "is still working";
return `The worker ${state}.\n\nreview trigger: ${reason}\ntool calls with no result: ${outstanding.join(", ") || "none"}\nbackground job state: not measured; inspect job evidence before approval\n\n${summary ? `last compaction summary:\n${summary}\n\n` : ""}recent worker transcript:\n${recent || "none"}`;
}
+30 -1
View File
@@ -5,7 +5,7 @@ import { join } from "node:path";
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
import { afterEach, describe, expect, it, vi } from "vitest";
import { approvalPath, goalBlock, hashGoalBlock, repositoryState, writeApproval } from "../src/approval.js";
import { writeWorkerSteer } from "../src/mailbox.js";
import { workerViewsAfter, writeWorkerSteer } from "../src/mailbox.js";
const openSupervisorPane = vi.fn(async () => "pane-2");
const closeSupervisorPane = vi.fn(async () => undefined);
@@ -28,6 +28,7 @@ function setup(selectChoices: Array<string | undefined>, editorChoices: Array<st
const ctx = {
cwd,
hasUI: true,
isIdle: vi.fn(() => true),
getSystemPrompt: () => "base prompt",
sessionManager: {
getSessionId: () => "session-a",
@@ -75,6 +76,34 @@ afterEach(() => {
});
describe("/goals flow", () => {
it("reports actual idle state, invalidates stopped views on start, and stops completed plans", async () => {
vi.useFakeTimers({ toFake: ["setInterval", "clearInterval"] });
const flow = setup(["Ready"]);
try {
await flow.commands.get("goals").handler("make the file", flow.ctx);
const path = approvedPlan(flow.cwd);
await flow.hooks.get("agent_settled")({}, flow.ctx);
const mailbox = (flow.entries.at(-1)?.data as { mailboxPath: string }).mailboxPath;
await vi.advanceTimersByTimeAsync(60 * 60_000);
expect(workerViewsAfter(mailbox, 0).at(-1)?.text).toMatch(/^The worker stopped\./);
flow.ctx.isIdle.mockReturnValue(false);
await flow.hooks.get("agent_start")({}, flow.ctx);
expect(workerViewsAfter(mailbox, 0).at(-1)?.text).toMatch(/^The worker is still working\./);
await flow.hooks.get("agent_settled")({}, flow.ctx);
expect(workerViewsAfter(mailbox, 0).at(-1)?.text).toMatch(/^The worker is still working\./);
flow.ctx.isIdle.mockReturnValue(true);
writeFileSync(path, readFileSync(path, "utf8").replace("[ ] goal:", "[x] goal:"));
await flow.hooks.get("agent_settled")({}, flow.ctx);
const count = workerViewsAfter(mailbox, 0).length;
expect(flow.entries.at(-1)?.data).toMatchObject({ phase: null });
await vi.advanceTimersByTimeAsync(60 * 60_000);
expect(workerViewsAfter(mailbox, 0)).toHaveLength(count);
} finally {
await flow.hooks.get("session_shutdown")();
vi.useRealTimers();
rmSync(flow.cwd, { recursive: true, force: true });
}
});
it("preserves drafts, records the interview, and keeps planning read-only", async () => {
const flow = setup(["Refine"], ["Keep two columns."]);
try {
+93 -4
View File
@@ -2,12 +2,15 @@ import { execFileSync } from "node:child_process";
import { existsSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
import { stripVTControlCharacters } from "node:util";
import { AssistantMessageComponent, type ExtensionAPI, initTheme, ToolExecutionComponent } from "@earendil-works/pi-coding-agent";
import { afterEach, describe, expect, it, vi } from "vitest";
import { approvalPath } from "../src/approval.js";
import { createMailbox, supervisorReady, workerSteersAfter } from "../src/mailbox.js";
import { createMailbox, supervisorReady, workerSteersAfter, writeWorkerView } from "../src/mailbox.js";
import { registerVisibleSupervisor } from "../src/supervisor-session.js";
const shutdowns: Array<() => Promise<void>> = [];
function setup(cwd: string, planPath: string, tokens: number | null = 10, onCompact: (options: any) => void = (options) => options.onComplete()) {
const mailbox = createMailbox(cwd, "worker-session", "approval-1", planPath);
vi.stubEnv("PI_GOALS_WORKER_ID", "worker-session");
@@ -38,12 +41,93 @@ function setup(cwd: string, planPath: string, tokens: number | null = 10, onComp
setActiveTools: (next: string[]) => { activeTools = next; },
};
registerVisibleSupervisor(pi as unknown as ExtensionAPI);
shutdowns.push(() => hooks.get("session_shutdown")());
return { activeTools: () => activeTools, branch: (value: any[]) => { branch = value; }, ctx, entries, hooks, mailbox, messages, tools };
}
afterEach(() => vi.unstubAllEnvs());
afterEach(async () => {
for (const shutdown of shutdowns.splice(0)) await shutdown();
vi.useRealTimers();
vi.unstubAllEnvs();
});
describe("visible supervisor session", () => {
it("restores monitoring and read-only tools without replaying persisted views", async () => {
vi.useFakeTimers({ toFake: ["setInterval", "clearInterval"] });
const cwd = mkdtempSync(join(tmpdir(), "pi-goals-resume-"));
try {
const first = setup(cwd, join(cwd, "plan.md"));
const view = writeWorkerView(first.mailbox, "settled", "The worker stopped.");
await first.hooks.get("session_start")({}, first.ctx);
await new Promise((resolve) => setImmediate(resolve));
expect(first.messages).toEqual([view.text]);
await first.hooks.get("session_shutdown")();
expect(supervisorReady(first.mailbox.path)).toBe(false);
const resumed = setup(cwd, join(cwd, "plan.md"), 30_000);
resumed.entries.push(...first.entries, { type: "message", message: { role: "user", content: [{ type: "text", text: view.text }] } });
await resumed.hooks.get("session_start")({}, resumed.ctx);
await new Promise((resolve) => setImmediate(resolve));
expect(resumed.activeTools()).toEqual(["read", "grep"]);
expect(resumed.ctx.compact).not.toHaveBeenCalled();
expect(resumed.messages).toEqual([]);
writeWorkerView(resumed.mailbox, "interval", "The worker stopped.\nOld view.");
const latest = writeWorkerView(resumed.mailbox, "interval", "The worker stopped.\nCurrent view.");
await vi.advanceTimersByTimeAsync(1000);
expect(resumed.messages).toEqual([latest.text]);
writeWorkerView(resumed.mailbox, "started", "The worker is still working.");
await vi.advanceTimersByTimeAsync(1000);
expect(resumed.messages).toEqual([latest.text]);
} finally { rmSync(cwd, { recursive: true, force: true }); }
});
it("renders all advice in real Pi tool rows, including collapsed and restored rows", () => {
initTheme("dark");
const cwd = mkdtempSync(join(tmpdir(), "pi-goals-render-"));
try {
const runtime = setup(cwd, join(cwd, "plan.md"));
const tool = runtime.tools.get("SteerWorker");
const lines = Array.from({ length: 18 }, (_, i) => `Advice ${i + 1}: inspect evidence.`);
const instruction = lines.join("\n");
for (const restored of [false, true]) {
const row = new ToolExecutionComponent("SteerWorker", "call", restored ? { instruction } : {}, {}, tool, { requestRender() {} } as any, cwd);
expect(stripVTControlCharacters(row.render(40).join("\n"))).not.toContain("undefined");
row.updateArgs({ instruction });
row.setArgsComplete();
row.updateResult({ content: [{ type: "text", text: "Receipt unconfirmed." }], isError: false });
for (const expanded of [false, true]) {
row.setExpanded(expanded);
for (const width of [40, 100]) {
const output = stripVTControlCharacters(row.render(width).join("\n"));
for (const line of lines) expect(output).toContain(line);
expect(output).toContain("Receipt unconfirmed.");
}
}
}
const assistant = new AssistantMessageComponent(undefined, false);
for (const streaming of [true, false]) {
assistant.updateContent({ role: "assistant", content: [
{ type: "thinking", thinking: "The signs disagree. Inspect the outputs." },
{ type: "text", text: "Progress is mixed; the second check still fails." },
{ type: "toolCall", id: "call", name: "SteerWorker", arguments: { instruction } },
] } as any, streaming);
const output = stripVTControlCharacters(assistant.render(100).join("\n"));
expect(output).toContain("The signs disagree.");
expect(output).toContain("Progress is mixed;");
}
} finally { rmSync(cwd, { recursive: true, force: true }); }
});
it("asks for judgment and useful recaps without inventing instructions", async () => {
const cwd = mkdtempSync(join(tmpdir(), "pi-goals-prompt-"));
try {
const runtime = setup(cwd, join(cwd, "plan.md"));
const { systemPrompt } = await runtime.hooks.get("before_agent_start")({}, runtime.ctx);
expect(systemPrompt).toContain("brief visible recap");
expect(systemPrompt).toContain("your judgment");
expect(systemPrompt).toContain("do not invent work");
expect(systemPrompt).toContain("stop issuing instructions");
} finally { rmSync(cwd, { recursive: true, force: true }); }
});
it("writes readiness only after removing writing tools", async () => {
const cwd = mkdtempSync(join(tmpdir(), "pi-goals-supervisor-"));
try {
@@ -104,10 +188,15 @@ describe("visible supervisor session", () => {
execFileSync("mkdir", ["-p", join(cwd, ".pi/plan")]);
writeFileSync(planPath, "# Plan\n\n## Goals\n\n1. [ ] goal: make the file\n - discriminator: output exists\n - evidence:\n - `result.txt`: contains ok\n\n## Log\n");
const runtime = setup(cwd, planPath);
runtime.branch([{ type: "message", message: { role: "user", content: [{ type: "text", text: "The worker stopped.\n\ntool calls with no result: none" }] } }]);
const view = writeWorkerView(runtime.mailbox, "settled", "The worker stopped.\n\ntool calls with no result: none");
runtime.branch([{ type: "message", message: { role: "user", content: [{ type: "text", text: view.text }] } }]);
const approved = await runtime.tools.get("ApproveGoal").execute("id", { goal: "make the file", verifyOutputPath: "verify.txt" }, undefined, undefined, runtime.ctx);
expect(approved.isError).toBe(false);
expect(existsSync(approvalPath(cwd, "worker-session", "make the file"))).toBe(true);
writeWorkerView(runtime.mailbox, "started", "The worker is still working.");
const stale = await runtime.tools.get("ApproveGoal").execute("id", { goal: "make the file", verifyOutputPath: "verify.txt" }, undefined, undefined, runtime.ctx);
expect(stale.isError).toBe(true);
expect(stale.content[0].text).toContain("latest worker view");
} finally { rmSync(cwd, { recursive: true, force: true }); }
});
});