Recover paused supervision and tighten approval boundaries

Restore bindings before model availability checks, require explicit reconnect/restart recovery, detach inactive plans, remove the general Intercom actuator, and reject placeholder evidence without hashing the plan log. Preserve synchronous handoff-before-ack; document that the void SDK cannot confirm durable message delivery.
This commit is contained in:
wassname committed 2026-09-08 18:57:43 +08:00
1 parent 2824396a71
commit 325b93983f
12 files changed
+403 -39

No files matched your search

+3 -3
View File
@@ -42,7 +42,7 @@ export function repositoryState(cwd: string): { repoRoot: string; head: string;
}
export function goalBlock(plan: string, goal: string): string | null {
const lines = plan.split("\n");
const lines = plan.split(/^##\s+Log\s*$/im, 1)[0].split("\n");
const wanted = goal.trim().toLowerCase();
const hits = lines.flatMap((line, index) => {
const match = GOAL_LINE.exec(line);
@@ -52,12 +52,12 @@ export function goalBlock(plan: string, goal: string): string | null {
const start = hits[0];
let end = lines.length;
for (let index = start + 1; index < lines.length; index++) {
if (GOAL_LINE.test(lines[index])) {
if (GOAL_LINE.test(lines[index]) || /^#{1,2}\s/.test(lines[index])) {
end = index;
break;
}
}
return lines.slice(start, end).join("\n");
return lines.slice(start, end).join("\n").trimEnd();
}
export function hashGoalBlock(block: string): string {
+98 -15
View File
@@ -111,7 +111,7 @@ export default function piGoalsExtension(pi: ExtensionAPI): void {
const intercom = new GoalIntercom(pi);
const models = new RoleModels(pi);
intercom.onSteer = (instruction) => {
if (state.phase !== "working") throw new Error("Worker plan is not active; instruction rejected.");
if (state.phase !== "working" || modelError) throw new Error("Worker is paused or its plan is not active; instruction not delivered. Use /goals reconnect after selecting an available model.");
pi.sendUserMessage(`[supervisor] ${instruction}`, { deliverAs: "steer" });
};
let state: PlanState = {
@@ -122,6 +122,8 @@ export default function piGoalsExtension(pi: ExtensionAPI): void {
planVersion: null,
latestDirection: "",
};
let modelError: string | null = null;
intercom.onConnectionChange = (ctx) => updateWidget(ctx);
let planningContextPending = false;
let resyncReason: string | null = "New session.";
@@ -142,6 +144,24 @@ export default function piGoalsExtension(pi: ExtensionAPI): void {
pi.appendEntry<PlanState>(STATE, state);
}
function pauseReason(): string | null {
if (!state.phase) return null;
if (modelError) return `${modelError} Select /model, then run /goals reconnect.`;
if (state.phase === "working" && !intercom.connected) return "Supervisor disconnected. Run /goals reconnect, or /goals restart to replace its tracked pane without discarding the plan.";
return null;
}
async function restoreModel(role: "planning" | "worker", ctx: ExtensionContext): Promise<void> {
modelError = `${role} model restoration is pending.`;
try {
await models.enter(role, ctx);
modelError = null;
} catch (error) {
modelError = error instanceof Error ? error.message : String(error);
throw error;
}
}
function beginReview(ctx: ExtensionContext): void {
for (const goal of scanGoals(readPlan(ctx))) {
rmSync(approvalPath(ctx.cwd, ctx.sessionManager.getSessionId(), goal.subject), { force: true });
@@ -157,12 +177,13 @@ export default function piGoalsExtension(pi: ExtensionAPI): void {
}
async function startSupervisor(ctx: ExtensionContext): Promise<void> {
if (intercom.ended) throw new Error("Session ended before supervisor startup.");
repositoryRoot(ctx.cwd);
const sourceSessionFile = ctx.sessionManager.getSessionFile();
if (!sourceSessionFile) throw new Error("The current session is not persisted, so it cannot be forked.");
if (state.supervisorPaneId && state.approvalId) {
intercom.configure(state.approvalId, "worker", ctx);
await intercom.waitReady();
await intercom.waitReady(5000);
return;
}
beginReview(ctx);
@@ -177,6 +198,8 @@ export default function piGoalsExtension(pi: ExtensionAPI): void {
extensionPath: fileURLToPath(import.meta.url),
model: state.supervisorModel,
}, (opened) => {
if (intercom.ended) throw new Error("Session ended during supervisor startup.");
paneId = opened;
state = { ...state, supervisorPaneId: opened };
persist();
});
@@ -184,6 +207,7 @@ export default function piGoalsExtension(pi: ExtensionAPI): void {
if (paneId) throw new Error(`Supervisor startup failed in Herdr pane ${paneId}; it remains open for inspection. ${error instanceof Error ? error.message : String(error)}`);
throw error;
}
if (intercom.ended) throw new Error("Session ended during supervisor startup.");
state = { ...state, supervisorPaneId: paneId };
persist();
await intercom.waitReady();
@@ -194,11 +218,11 @@ export default function piGoalsExtension(pi: ExtensionAPI): void {
let viewTimer: ReturnType<typeof setInterval> | undefined;
async function publishWorkerView(ctx: ExtensionContext, reason: "ready" | "settled" | "turns" | "interval" | "started"): Promise<void> {
if (state.phase !== "working" || intercom.ended) return;
if (state.phase !== "working" || modelError || !intercom.bound) return;
const generation = ++viewGeneration;
const binding = state.approvalId;
const background = reason === "started" ? { quiet: false, description: "agent starting; background state not queried" } : await backgroundState(pi);
if (intercom.ended || generation !== viewGeneration || binding !== state.approvalId || state.phase !== "working") return;
if (!intercom.bound || modelError || generation !== viewGeneration || binding !== state.approvalId || state.phase !== "working") return;
const entries = ctx.sessionManager.getBranch();
const view = workerView(entries, reason, reason !== "started" && ctx.isIdle(), {
sourceSession: ctx.sessionManager.getSessionFile()!, latestDirection: state.latestDirection,
@@ -212,6 +236,7 @@ export default function piGoalsExtension(pi: ExtensionAPI): void {
state = { ...state, phase: null };
models.leave();
persist();
intercom.detach();
}
}
@@ -227,10 +252,11 @@ export default function piGoalsExtension(pi: ExtensionAPI): void {
}
async function stopSupervisor(): Promise<boolean> {
stopWorkerTimers();
if (!state.supervisorPaneId) return true;
if (!state.supervisorPaneId) { stopWorkerTimers(); intercom.detach(); return true; }
try {
await closeSupervisorPane(state.supervisorPaneId);
stopWorkerTimers();
intercom.detach();
state = { ...state, supervisorPaneId: null };
persist();
return true;
@@ -240,6 +266,12 @@ export default function piGoalsExtension(pi: ExtensionAPI): void {
}
function updateWidget(ctx: ExtensionContext): void {
const paused = pauseReason();
if (paused) {
ctx.ui.setStatus(STATUS_KEY, ctx.ui.theme.fg("warning", "goals paused"));
ctx.ui.setWidget(WIDGET_KEY, [`pi-goals paused: ${paused}`]);
return;
}
if (state.phase === "planning") {
ctx.ui.setStatus(STATUS_KEY, ctx.ui.theme.fg("warning", "planning"));
ctx.ui.setWidget(WIDGET_KEY, ["pi-goals: drafting goals"]);
@@ -271,9 +303,39 @@ export default function piGoalsExtension(pi: ExtensionAPI): void {
// --- /goals: enter plan mode or configure supervision -- Pi/Codex -----------------------------
pi.registerCommand("goals", {
description: `Plan goals, then open a visible supervisor session. /goals <objective> | clear | model <supervisor>`,
description: `Plan goals, then open a visible supervisor session. /goals <objective> | reconnect | restart | clear | model <supervisor>`,
handler: async (args, ctx) => {
const arg = args.trim();
if (arg === "reconnect" || arg === "restart") {
if (!state.phase) { ctx.ui.notify("No active plan to recover.", "info"); return; }
if (!ctx.isIdle()) { ctx.ui.notify("Stop the current turn before recovering goal supervision.", "warning"); return; }
try {
await restoreModel(state.phase === "planning" ? "planning" : "worker", ctx);
if (arg === "restart") {
if (!(await stopSupervisor())) throw new Error("Could not close the tracked supervisor pane; no replacement was opened.");
state = { ...state, supervisorPaneId: null, approvalId: null };
persist();
}
if (state.phase === "working" || state.supervisorPaneId) {
if (arg === "reconnect") {
if (!state.approvalId) throw new Error("No saved supervision binding. Use /goals restart.");
intercom.configure(state.approvalId, "worker", ctx);
await intercom.waitReady(5000);
} else await startSupervisor(ctx);
}
if (intercom.ended) return;
if (state.phase === "working") {
startWorkerTimers(ctx);
await publishWorkerView(ctx, "settled");
}
ctx.ui.notify(state.phase === "planning" ? "Planning model restored. Choose Ready when the plan is agreed." : "Goal supervision reconnected; the current plan is unchanged.", "info");
} catch (error) {
if (intercom.ended) return;
ctx.ui.notify(`Goal recovery failed: ${String(error)} Use /goals reconnect to retry, or /goals restart to explicitly replace the tracked pane.`, "warning");
}
updateWidget(ctx);
return;
}
if (arg === "clear") {
if (state.planVersion === null) {
ctx.ui.notify("No active plan to disconnect.", "info");
@@ -286,6 +348,7 @@ export default function piGoalsExtension(pi: ExtensionAPI): void {
}
state = { ...state, phase: null, supervisorPaneId: null, approvalId: null, planVersion: null };
models.leave();
modelError = null;
persist();
updateWidget(ctx);
ctx.ui.notify(`Disconnected from ${currentPlan}; the file remains on disk.`, "info");
@@ -310,7 +373,7 @@ export default function piGoalsExtension(pi: ExtensionAPI): void {
ctx.ui.notify("Could not close the visible supervisor; no new plan was started.", "warning");
return;
}
await models.enter("planning", ctx);
await restoreModel("planning", ctx);
state = { ...state, phase: "planning", supervisorPaneId: null, approvalId: null, planVersion: nextVersion(ctx), latestDirection: arg };
planningContextPending = true;
resyncReason = null;
@@ -339,6 +402,8 @@ export default function piGoalsExtension(pi: ExtensionAPI): void {
// The phase snapshot enters context only when planning starts or context was lost.
pi.on("before_agent_start", async (_event, ctx) => {
const paused = pauseReason();
if (paused) return { systemPrompt: `${ctx.getSystemPrompt()}\n\nGoal work is paused: ${paused} Do not implement or sign off goals. Human input and read-only diagnosis remain available; wait for recovery before resuming autonomous work.` };
if (state.phase === "working") {
return {
systemPrompt: `${ctx.getSystemPrompt()}\n\nYou are the implementation worker for ${planRel(ctx)}. Keep the full conversation and do the work directly. A stronger read-only supervisor watches this session through pi-intercom and can steer you. Commit clean evidence before asking for sign-off. Stop when a goal appears complete so the supervisor can inspect a settled worker view. Call CompleteGoal only after the supervisor says it recorded approval. -- PI[Kimi K3]`,
@@ -385,6 +450,10 @@ export default function piGoalsExtension(pi: ExtensionAPI): void {
});
pi.on("tool_call", async (event, ctx) => {
const paused = pauseReason();
if (paused && !(["read", "grep", "find", "ls"].includes(event.toolName) || (event.toolName === "bash" && isPlanningReadOnlyCommand(String((event.input as { command?: string }).command))))) {
return { block: true, terminate: true, reason: `Goal work is paused: ${paused} Only read-only diagnosis is available.` };
}
if (state.phase === "planning") {
if (PLAN_MODE_BLOCKED_TOOLS.includes(event.toolName)) {
const target = (event.input as { path?: string }).path;
@@ -410,7 +479,7 @@ export default function piGoalsExtension(pi: ExtensionAPI): void {
await publishWorkerView(ctx, "settled");
return;
}
if (state.phase !== "planning" || !ctx.hasUI) return;
if (state.phase !== "planning" || modelError || !ctx.hasUI) return;
let printed = "";
while (true) {
if (intercom.ended) return;
@@ -440,6 +509,7 @@ export default function piGoalsExtension(pi: ExtensionAPI): void {
continue;
}
if (choice === "Cancel") {
if (!(await stopSupervisor())) { ctx.ui.notify("Could not close the tracked supervisor; plan was not discarded.", "warning"); return; }
rmSync(planPath(ctx), { force: true });
models.leave();
state = { ...state, phase: null, supervisorPaneId: null, approvalId: null, planVersion: null };
@@ -452,7 +522,7 @@ export default function piGoalsExtension(pi: ExtensionAPI): void {
try {
await startSupervisor(ctx);
if (intercom.ended) return;
await models.enter("worker", ctx);
await restoreModel("worker", ctx);
state = { ...state, phase: "working" };
resyncReason = "The plan was approved.";
persist();
@@ -463,7 +533,7 @@ export default function piGoalsExtension(pi: ExtensionAPI): void {
pi.sendUserMessage("The plan is approved. Begin implementation as the worker.");
} catch (error) {
if (intercom.ended) return;
ctx.ui.notify(`Goal supervisor could not start: ${error instanceof Error ? error.message : String(error)}`, "warning");
ctx.ui.notify(`Goal supervisor could not start: ${error instanceof Error ? error.message : String(error)} Use /goals reconnect to retry, or /goals restart to replace the tracked pane.`, "warning");
state = { ...state, phase: "planning" };
persist();
updateWidget(ctx);
@@ -485,13 +555,25 @@ export default function piGoalsExtension(pi: ExtensionAPI): void {
planVersion: last?.data?.planVersion ?? null,
latestDirection: last?.data?.latestDirection ?? "",
};
if (state.phase) await models.enter(state.phase === "planning" ? "planning" : "worker", ctx);
modelError = state.phase ? "Role model restoration is pending." : null;
planningContextPending = state.phase === "planning";
resyncReason = state.phase === "working" ? "New session." : null;
if (state.phase === "working") {
intercom.configure(state.approvalId!, "worker", ctx);
if (state.phase === "working" && state.approvalId) {
intercom.configure(state.approvalId, "worker", ctx, false);
startWorkerTimers(ctx);
}
try {
if (state.phase) await restoreModel(state.phase === "planning" ? "planning" : "worker", ctx);
} catch (error) {
if (!intercom.ended) ctx.ui.notify(`Goal work paused: ${String(error)} Use /model, then /goals reconnect.`, "warning");
}
if (intercom.ended) return;
if (state.phase === "working" && state.approvalId && !modelError) {
intercom.markReady();
void intercom.waitReady(5000).then(() => publishWorkerView(ctx, "settled")).catch(error => {
if (!intercom.ended && state.phase === "working") ctx.ui.notify(`Goal work paused: ${String(error)} Use /goals reconnect or /goals restart.`, "warning");
});
}
updateWidget(ctx);
});
@@ -508,9 +590,10 @@ export default function piGoalsExtension(pi: ExtensionAPI): void {
}),
async execute(_id, params, _signal, _onUpdate, ctx) {
if (state.phase !== "working") return result("Planning is not approved. Choose Ready before signing off a goal.", true);
if (pauseReason()) return result(`Goal sign-off blocked: ${pauseReason()}`, true);
if (!state.approvalId) return result("Goal sign-off blocked: no current supervisor review.", true);
const background = await backgroundState(pi);
if (intercom.ended || !background.quiet) return result(`Goal sign-off blocked: ${background.description}`, true);
if (intercom.ended || !background.quiet || pauseReason()) return result(`Goal sign-off blocked: ${pauseReason() ?? background.description}`, true);
const plan = readPlan(ctx);
if (!plan.trim()) return result(`No plan file at ${planRel(ctx)}. Run /goals to draft one.`, true);
const block = goalBlock(plan, params.goal);
+24 -4
View File
@@ -24,6 +24,7 @@ export class GoalIntercom {
acknowledgedEntry?: string;
onView: (view: View) => void = () => {};
onSteer: (text: string) => void = () => {};
onConnectionChange: (ctx: ExtensionContext) => void = () => {};
constructor(private pi: ExtensionAPI) {
pi.events.on("intercom:extension-registry-ready", () => this.register());
@@ -39,11 +40,11 @@ export class GoalIntercom {
});
}
configure(binding: string, role: Role, ctx: ExtensionContext): void {
configure(binding: string, role: Role, ctx: ExtensionContext, ready = role === "worker"): void {
this.binding = binding;
this.role = role;
this.ctx = ctx;
this.ready = role === "worker";
this.ready = ready;
this.peer = undefined;
this.peerReady = false;
this.pending.clear();
@@ -66,9 +67,22 @@ export class GoalIntercom {
this.hello();
}
// End this plan's binding without disposing the session's transport.
detach(): void {
this.ready = false;
this.hello();
this.binding = "";
this.peer = undefined;
this.peerReady = false;
this.latestView = undefined;
this.pending.clear();
if (this.ctx) this.onConnectionChange(this.ctx);
}
markReady(): void { if (!this.stopped) { this.ready = true; this.hello(); } }
get ended(): boolean { return this.stopped; }
get connected(): boolean { return Boolean(this.channel?.snapshot().connected && this.peerReady); }
get bound(): boolean { return !this.stopped && Boolean(this.binding); }
get connected(): boolean { return Boolean(!this.stopped && this.ready && this.binding && this.channel?.snapshot().connected && this.peerReady); }
async waitReady(timeoutMs = 300_000): Promise<void> {
if (this.connected) return;
@@ -86,6 +100,7 @@ export class GoalIntercom {
}
view(text: string, reason: string, through?: string, backgroundQuiet = false): View {
if (!this.bound) throw new Error("No active supervision binding for a worker view.");
const id = randomUUID();
const message: Message = { binding: this.binding, role: this.role, kind: "view", id, text: `${text}\n\nworker view id: ${id}`, reason, through, backgroundQuiet };
this.record("out", message);
@@ -121,11 +136,13 @@ export class GoalIntercom {
this.peer = undefined; this.peerReady = false;
}
else this.hello();
if (this.ctx) this.onConnectionChange(this.ctx);
return;
}
if (event.type === "session_left" && event.sessionId === this.peer) {
this.peer = undefined; this.peerReady = false;
this.ctx?.ui.notify("Goal supervision peer disconnected; reconnect the existing session.", "warning");
if (this.ctx) this.onConnectionChange(this.ctx);
return;
}
if (event.type === "session_joined") { this.hello(); return; }
@@ -139,11 +156,12 @@ export class GoalIntercom {
this.peerReady = Boolean(message.ready);
if (changed) {
this.hello();
if (this.peerReady) {
if (this.peerReady && this.ready) {
if (this.role === "worker" && this.latestView) this.publish({ binding: this.binding, role: this.role, kind: "view", ...this.latestView });
for (const pending of this.pending.values()) this.publish(pending);
}
}
if (changed && this.ctx) this.onConnectionChange(this.ctx);
for (const wake of this.waiters) wake();
return;
}
@@ -166,6 +184,8 @@ export class GoalIntercom {
this.publish({ binding: this.binding, role: this.role, kind: "received", id: message.id });
}
} else if (message.kind === "steer" && this.role === "worker") {
// Pi's void message API provides synchronous handoff, not a durable queue receipt.
// Ack only after that handoff; asynchronous enqueue errors are not observable here.
this.onSteer(message.text!);
this.received.add(message.id);
this.record("in", message);
+1
View File
@@ -23,6 +23,7 @@ export class RoleModels {
leave(): void { this.role = undefined; }
async enter(role: ModelRole, ctx: ExtensionContext, useCurrent = false): Promise<void> {
if (this.stopped) throw new Error("Role model session ended.");
this.role = role;
this.ctx = ctx;
let choice: Choice | undefined;
+32 -10
View File
@@ -10,7 +10,7 @@ import { RoleModels } from "./role-models.js";
const BOOTSTRAPPED = "pi-goals-visible-supervisor-v2";
const INITIAL_COMPACT_AT_TOKENS = 20_000;
const COMPACT_AT_TOKENS = 100_000;
const WRITER_TOOLS = new Set(["bash", "edit", "write", "multi_edit", "multiedit", "apply_patch", "notebook_edit", "edit_file", "write_file", "quick_edit", "target_edit"]);
const BLOCKED_TOOLS = new Set(["intercom", "bash", "edit", "write", "multi_edit", "multiedit", "apply_patch", "notebook_edit", "edit_file", "write_file", "quick_edit", "target_edit"]);
interface SupervisorConfig {
workerSessionId: string;
@@ -49,7 +49,7 @@ function hasEvidenceEntry(block: string): boolean {
const childIndent = lines[child].match(/^\s*/)?.[0].length ?? 0;
if (lines[child].trim() && childIndent <= indent) break;
const entry = /^\s+[-*]\s+(.+?)\s*$/.exec(lines[child]);
if (entry?.[1].trim()) return true;
if (entry?.[1].trim() && !/^\(empty until sign-off\)$/i.test(entry[1].trim())) return true;
}
}
return false;
@@ -93,6 +93,7 @@ export function registerVisibleSupervisor(pi: ExtensionAPI): void {
const settings = config();
let compacting = false;
let bootstrapping = false;
let modelError: string | null = null;
const intercom = new GoalIntercom(pi);
const models = new RoleModels(pi);
intercom.onView = (view) => pi.sendUserMessage(view.text, { deliverAs: "followUp" });
@@ -103,16 +104,16 @@ export function registerVisibleSupervisor(pi: ExtensionAPI): void {
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.setActiveTools(active.filter((tool) => !BLOCKED_TOOLS.has(tool.toLowerCase())));
const blocked = pi.getActiveTools().filter((tool) => BLOCKED_TOOLS.has(tool.toLowerCase()));
if (blocked.length) throw new Error(`Could not remove supervisor writing or messaging tools: ${blocked.join(", ")}`);
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 });
}
intercom.markReady();
} catch (error) {
ctx.ui.notify(`Supervisor startup failed: ${error instanceof Error ? error.message : String(error)}`, "error");
}
} finally { bootstrapping = false; }
};
const bootstrapAfterInitialCompaction = (ctx: ExtensionContext): void => {
@@ -139,11 +140,30 @@ export function registerVisibleSupervisor(pi: ExtensionAPI): void {
});
};
pi.on("session_start", async (_event, ctx) => {
const start = async (ctx: ExtensionContext): Promise<void> => {
modelError = "Supervisor model restoration is pending.";
intercom.configure(settings.approvalId, "supervisor", ctx);
pi.setActiveTools(pi.getActiveTools().filter((tool) => !WRITER_TOOLS.has(tool.toLowerCase())));
await models.enter("supervisor", ctx, process.env.PI_GOALS_MODEL_EXPLICIT === "1");
setImmediate(() => { bootstrapAfterInitialCompaction(ctx); });
pi.setActiveTools(pi.getActiveTools().filter((tool) => !BLOCKED_TOOLS.has(tool.toLowerCase())));
try {
await models.enter("supervisor", ctx, process.env.PI_GOALS_MODEL_EXPLICIT === "1");
modelError = null;
setImmediate(() => { if (!intercom.ended) bootstrapAfterInitialCompaction(ctx); });
} catch (error) {
modelError = String(error);
if (!intercom.ended) ctx.ui.notify(`Supervisor paused: ${modelError} Select /model, then /goals reconnect.`, "error");
}
};
pi.on("session_start", async (_event, ctx) => start(ctx));
pi.registerCommand("goals", {
description: "Retry supervisor model restoration and readiness: /goals reconnect",
handler: async (args, ctx) => {
if (args.trim() !== "reconnect") { ctx.ui.notify("Use /goals reconnect here; manage the plan or restart the pane from the worker session.", "info"); return; }
if (!ctx.isIdle() || compacting) { ctx.ui.notify("Wait for the supervisor to settle before reconnecting.", "warning"); return; }
await start(ctx);
},
});
pi.on("tool_call", async (event) => {
if (BLOCKED_TOOLS.has(event.toolName.toLowerCase())) return { block: true, terminate: true, reason: "Supervisor is read-only; use SteerWorker for the bound worker, not the general intercom tool." };
});
pi.on("before_agent_start", async (_event, ctx) => ({ systemPrompt: `${ctx.getSystemPrompt()}\n\n${supervisorPrompt(settings)}` }));
pi.on("agent_settled", async (_event, ctx) => {
@@ -174,6 +194,7 @@ export function registerVisibleSupervisor(pi: ExtensionAPI): void {
return new Text(`${theme.fg("toolTitle", "Supervisor → worker")}\n${args.instruction ?? ""}`, 0, 0);
},
async execute(_id, params) {
if (modelError) return result(`Supervisor paused: ${modelError} Use /model, then /goals reconnect.`, true);
const instruction = params.instruction.trim();
if (!instruction) return result("A worker instruction cannot be empty.", true);
const id = intercom.steer(instruction);
@@ -191,6 +212,7 @@ export function registerVisibleSupervisor(pi: ExtensionAPI): void {
verifyOutputPath: Type.String({ description: "Nonempty repository-relative file containing the verification output you inspected." }),
}),
async execute(_id, params, _signal, _onUpdate, ctx) {
if (modelError) return result(`Supervisor paused: ${modelError} Use /model, then /goals reconnect.`, true);
const view = latestWorkerView(ctx);
const newest = intercom.latestView;
if (!intercom.connected || !newest || view !== newest.text) return result("Cannot approve without inspecting the latest worker view.", true);