fix: keep pause/resume in sync between loop and tree; close read_artifact symlink escape
- research_control pause/resume now validate the loop state before touching the tree, so pausing a stopped loop can no longer leave the tree 'paused' while the loop is not (spawn would fail until a restart). - read_artifact confinement resolves symlinks (realpath) before checking the project boundary, preventing symlink-based file leaks. - Adds confinement tests (42 tests passing, tsc clean).
This commit is contained in:
+1
-1
@@ -49,7 +49,7 @@ export class SupervisorIntegration {
|
||||
name: "research_control", label: "Research loop control", description: "Start, record, inspect, pause, resume, or stop the explicit research state machine. Record one report per completed iteration; coded stop conditions prevent infinite prompt loops.",
|
||||
promptSnippet: "Control the bounded autonomous research loop",
|
||||
parameters: Type.Object({ action: StringEnum(["start", "status", "record_iteration", "pause", "resume", "stop"] as const), goal: Type.Optional(Type.String()), report: Type.Optional(Type.Object({ goal: Type.String(), tasks: Type.Array(Type.String()), activeAgents: Type.Array(Type.String()), expectedOutput: Type.String(), state: Type.String(), newFindings: Type.Integer({ minimum: 0 }), closedQuestions: Type.Integer({ minimum: 0 }), contradictions: Type.Integer({ minimum: 0 }), reason: Type.String(), goalAchieved: Type.Optional(Type.Boolean()), criticalMethodError: Type.Optional(Type.Boolean()), onlyExternalQuestions: Type.Optional(Type.Boolean()), userDecisionRequired: Type.Optional(Type.Boolean()) })) }),
|
||||
execute: async (_id, params) => { const { tree, loop } = this.required(); if (params.action === "start") { if (!params.goal?.trim()) throw new Error("goal is required"); const loopState = loop.snapshot(); const loopTerminal = ["stopped", "completed"].includes(loopState.status); const root = tree.inspect(tree.rootId); const rootSpawnable = !["cancelled", "failed", "archived"].includes(root.status); if (loopTerminal || tree.status !== "active" || !rootSpawnable) tree.restart(params.goal); loop.setGoal(params.goal); loop.start(); return toolText(loop.snapshot()); } if (params.action === "status") return toolText(loop.snapshot()); if (params.action === "pause") { loop.pause(); tree.pause(); } else if (params.action === "resume") { loop.resume(); tree.resume(); } else if (params.action === "stop") { loop.stop(); await tree.stop(); } else { if (!params.report) throw new Error("report is required"); return toolText(loop.record(params.report)); } return toolText(loop.snapshot()); },
|
||||
execute: async (_id, params) => { const { tree, loop } = this.required(); if (params.action === "start") { if (!params.goal?.trim()) throw new Error("goal is required"); const loopState = loop.snapshot(); const loopTerminal = ["stopped", "completed"].includes(loopState.status); const root = tree.inspect(tree.rootId); const rootSpawnable = !["cancelled", "failed", "archived"].includes(root.status); if (loopTerminal || tree.status !== "active" || !rootSpawnable) tree.restart(params.goal); loop.setGoal(params.goal); loop.start(); return toolText(loop.snapshot()); } if (params.action === "status") return toolText(loop.snapshot()); if (params.action === "pause") { if (loop.snapshot().status !== "running") throw new Error(`Cannot pause a ${loop.snapshot().status} loop`); loop.pause(); tree.pause(); } else if (params.action === "resume") { if (loop.snapshot().status !== "paused") throw new Error("Only a paused loop can resume"); loop.resume(); tree.resume(); } else if (params.action === "stop") { loop.stop(); await tree.stop(); } else { if (!params.report) throw new Error("report is required"); return toolText(loop.record(params.report)); } return toolText(loop.snapshot()); },
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user