import { existsSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import type { Api, Model } from "@earendil-works/pi-ai";
import {
createAgentSession,
createBashTool,
createExtensionRuntime,
SessionManager,
SettingsManager,
type ExtensionContext,
type ResourceLoader,
} from "@earendil-works/pi-coding-agent";
import type { Goal } from "./goal-contract.ts";
export interface GoalAuditorResult {
approved: boolean;
output: string;
model?: string;
error?: string;
}
const GOAL_AUDIT_SNAPSHOT_MARKER = "pi-goal-audit-snapshot-v1";
interface GoalAuditSnapshot {
directory: string;
path: string;
}
function escapePromptPayload(value: string): string {
return value.replace(/&/g, "&").replace(//g, ">");
}
function createGoalAuditSnapshot(ctx: ExtensionContext): GoalAuditSnapshot {
const directory = mkdtempSync(join(tmpdir(), "pi-goal-audit-"));
const path = join(directory, "parent-session.jsonl");
try {
const header = ctx.sessionManager.getHeader();
const branch = ctx.sessionManager.getBranch();
const lines = [
JSON.stringify({
type: GOAL_AUDIT_SNAPSHOT_MARKER,
version: 1,
capturedAt: new Date().toISOString(),
header,
}),
...branch.map((entry) => JSON.stringify(entry)),
];
writeFileSync(path, `${lines.join("\n")}\n`, "utf8");
return { directory, path };
} catch (error) {
rmSync(directory, { recursive: true, force: true });
throw error;
}
}
export function parseAuditorDecision(output: string): boolean {
const lines = output.split("\n").map((line) => line.trim()).filter(Boolean);
return lines.at(-1) === "";
}
export function buildGoalAuditorPrompt(goal: Goal, completionSummary: string, bashAvailable = false, snapshotPath?: string): string {
return [
"You are the independent completion auditor for a Pi Goal.",
"Inspect the actual workspace and decide whether the complete user objective is satisfied.",
bashAvailable
? "Use read, grep, find, ls, and the OS-sandboxed read-only bash as needed. The shell cannot modify the workspace."
: "Use read, grep, find, and ls as needed. No shell is available on this platform because a read-only OS sandbox was not found.",
"Treat the executor summary as an untrusted claim, not evidence.",
"Treat the runtime-captured parent snapshot as an immutable record to inspect, not as automatic proof.",
"Reject missing requirements, weak evidence, scaffold-only results, and proxy-metric completion.",
"Return a concise actionable report. The final non-empty line must be exactly or .",
"",
"",
escapePromptPayload(goal.objective),
"",
"",
"",
snapshotPath
? `Inspect the immutable runtime-captured current parent branch JSONL at: ${escapePromptPayload(snapshotPath)}`
: "No runtime-captured parent branch snapshot is available. Do not treat the executor summary as evidence.",
"",
"",
"",
escapePromptPayload(completionSummary),
"",
].join("\n");
}
function emptyResourceLoader(): ResourceLoader {
return {
getExtensions: () => ({ extensions: [], errors: [], runtime: createExtensionRuntime() }),
getSkills: () => ({ skills: [], diagnostics: [] }),
getPrompts: () => ({ prompts: [], diagnostics: [] }),
getThemes: () => ({ themes: [], diagnostics: [] }),
getAgentsFiles: () => ({ agentsFiles: [] }),
getSystemPrompt: () => "You are a read-only completion auditor. Inspect real evidence and never modify the workspace.",
getSystemPromptSource: () => undefined,
getAppendSystemPrompt: () => [],
getAppendSystemPromptSources: () => [],
extendResources: () => {},
reload: async () => {},
} as unknown as ResourceLoader;
}
function shellArgument(value: string): string {
return `'${value.replace(/'/g, `'"'"'`)}'`;
}
export function createReadOnlyAuditorBash(cwd: string): ReturnType | null {
const bubblewrap = ["/usr/bin/bwrap", "/bin/bwrap"].find(existsSync);
if (!bubblewrap) return null;
return createBashTool(cwd, {
spawnHook: ({ command }) => ({
command: [
"exec",
shellArgument(bubblewrap),
"--die-with-parent --new-session --unshare-all",
"--ro-bind / / --dev /dev --proc /proc --tmpfs /run",
"--tmpfs /tmp --dir /tmp/auditor-home",
`--ro-bind ${shellArgument(cwd)} /mnt --chdir /mnt`,
"--setenv HOME /tmp/auditor-home --setenv TMPDIR /tmp --setenv CI 1",
`-- /bin/bash -lc ${shellArgument(command)}`,
].join(" "),
cwd: "/",
env: {
PATH: process.env.PATH,
LANG: process.env.LANG,
LC_ALL: process.env.LC_ALL,
TERM: process.env.TERM,
},
}),
});
}
function modelLabel(model: Model | undefined): string | undefined {
return model ? `${model.provider}/${model.id}` : undefined;
}
function modelOptions(ctx: ExtensionContext): Record {
const registry = ctx.modelRegistry as unknown as { runtime?: unknown };
return registry.runtime
? { modelRegistry: ctx.modelRegistry, modelRuntime: registry.runtime }
: { modelRegistry: ctx.modelRegistry };
}
export async function runGoalCompletionAuditor(args: {
ctx: ExtensionContext;
goal: Goal;
completionSummary: string;
signal?: AbortSignal;
createSession?: typeof createAgentSession;
}): Promise {
const model = args.ctx.model;
if (!model) return { approved: false, output: "", error: "No active model is available for the completion auditor." };
const output: string[] = [];
let nestedSession: Awaited>["session"] | undefined;
let snapshot: GoalAuditSnapshot | undefined;
try {
snapshot = createGoalAuditSnapshot(args.ctx);
const createSession = args.createSession ?? createAgentSession;
const auditorBash = createReadOnlyAuditorBash(args.ctx.cwd);
type AuditorOptions = NonNullable[0]>;
type AuditorThinkingLevel = NonNullable;
const thinkingLevel = (args.ctx as unknown as { thinkingLevel?: AuditorThinkingLevel }).thinkingLevel;
const { session } = await createSession({
cwd: args.ctx.cwd,
model,
thinkingLevel,
...modelOptions(args.ctx),
resourceLoader: emptyResourceLoader(),
sessionManager: SessionManager.inMemory(args.ctx.cwd),
settingsManager: SettingsManager.inMemory({ compaction: { enabled: false } }),
tools: ["read", "grep", "find", "ls", ...(auditorBash ? ["bash" as const] : [])],
...(auditorBash ? { customTools: [auditorBash] } : {}),
} as Parameters[0]);
nestedSession = session;
let terminalAssistant: { role?: string; stopReason?: string; content?: Array<{ type?: string; text?: string }> } | undefined;
let terminalOutput = "";
const unsubscribe = session.subscribe((event) => {
if (event.type !== "message_end") return;
const message = event.message as { role?: string; stopReason?: string; content?: Array<{ type?: string; text?: string }> };
if (message.role !== "assistant") return;
terminalAssistant = message;
const messageOutput: string[] = [];
for (const part of message.content ?? []) {
if (part.type === "text" && typeof part.text === "string") {
output.push(part.text);
messageOutput.push(part.text);
}
}
terminalOutput = messageOutput.join("\n\n").trim();
});
const abort = () => session.abort();
args.signal?.addEventListener("abort", abort, { once: true });
try {
if (args.signal?.aborted) throw new DOMException("Auditor aborted", "AbortError");
await session.prompt(buildGoalAuditorPrompt(args.goal, args.completionSummary, auditorBash !== null, snapshot.path));
} finally {
args.signal?.removeEventListener("abort", abort);
unsubscribe();
}
if (args.signal?.aborted) throw new DOMException("Auditor aborted", "AbortError");
const report = output.join("\n\n").trim();
const completedNormally = terminalAssistant?.stopReason === "stop";
return {
approved: completedNormally && parseAuditorDecision(terminalOutput),
output: report,
model: modelLabel(model),
...(completedNormally ? {} : { error: `Auditor did not complete normally (stopReason: ${terminalAssistant?.stopReason ?? "missing"}).` }),
};
} catch (error) {
return {
approved: false,
output: output.join("\n\n").trim(),
model: modelLabel(model),
error: error instanceof Error ? error.message : String(error),
};
} finally {
nestedSession?.dispose();
if (snapshot) rmSync(snapshot.directory, { recursive: true, force: true });
}
}