Start supervisors from loaded extension

Co-Authored-By: PI[Kimi K3] <288921227+claudypoo@users.noreply.github.com>
This commit is contained in:
wassname
2026-09-07 21:16:05 +08:00
co-authored by PI[Kimi K3]
parent ba2799a1d9
commit ee1ab3ec26
8 changed files with 85 additions and 18 deletions
+2 -1
View File
@@ -11,6 +11,7 @@ interface LaunchSupervisorInput {
planPath: string;
approvalId: string;
extensionPath: string;
superviseExtensionPath: string | null;
model: string | null;
}
@@ -57,7 +58,7 @@ export function supervisorCommand(input: LaunchSupervisorInput): string {
"pi",
"--no-extensions",
"-e", "npm:pi-intercom",
"-e", process.env.PI_GOALS_SUPERVISE_EXTENSION ?? "npm:@wassname2/pi-supervise@0.0.4",
"-e", process.env.PI_GOALS_SUPERVISE_EXTENSION ?? input.superviseExtensionPath ?? "npm:@wassname2/pi-supervise@0.0.4",
"-e", input.extensionPath,
"--fork", input.sourceSessionFile,
"--name", `goals-supervisor-${input.workerSessionId.slice(0, 8)}`,
+6
View File
@@ -139,6 +139,11 @@ export default function piGoalsExtension(pi: ExtensionAPI): void {
persist();
}
function loadedPiSuperviseExtensionPath(): string | null {
const tool = pi.getAllTools().find((candidate) => candidate.name === "worker_view") as { sourceInfo?: { path?: unknown } } | undefined;
return typeof tool?.sourceInfo?.path === "string" ? tool.sourceInfo.path : null;
}
function repositoryRoot(cwd: string): string {
return execFileSync("git", ["rev-parse", "--show-toplevel"], { cwd, encoding: "utf8" }).trim();
}
@@ -159,6 +164,7 @@ export default function piGoalsExtension(pi: ExtensionAPI): void {
planPath: planPath(ctx),
approvalId: state.approvalId!,
extensionPath: fileURLToPath(import.meta.url),
superviseExtensionPath: loadedPiSuperviseExtensionPath(),
model: state.supervisorModel,
});
await worker.waitForPair();
+7 -6
View File
@@ -29,16 +29,17 @@ You are in plan mode. You are making a short judgeable plan that captures the us
resolve a fact. Do not write or run code in this phase (edit/write are blocked except for the plan
file; don't mutate state via bash either).
2. Before you draft a goal, identify its object, observable result, scope, and any decision that the
human would need to approve later. If any is uncertain, reduce uncertainty now: inspect files or
search the web when they can answer, then ask the human to confirm your interpretation, pin down the
outcome or task, or approve an editorial or other preference choice. Do not present the review menu
with a placeholder goal such as "work out the thing", "improve it", or "investigate".
human would need to approve later. Ask at least three short, concrete questions that test whether you
understand the requested outcome, boundary, and how success will be judged. Inspect files or search the
web before asking when either can answer a fact. If the human does not answer a question, record that
point as unknown; do not silently replace it with an inference. Do not present the review menu with a
placeholder goal such as "work out the thing", "improve it", or "investigate".
3. For independent high-impact questions, build a decision tree and ask the whole frontier in one
round. Ask only questions worth the human's time, where the answer materially reduces uncertainty
while discovering the right plan. Each question must be short and self-contained: state the relevant
context, use the human's language and ASD-STE100
Simple Technical English, and give a recommended answer. Record each answer in ## Interview. Do not
make the plan final while material user decisions remain open.
Simple Technical English, and give a recommended answer. Record each answer, or the unanswered
unknown, in ## Interview. Do not make the plan final while material user decisions remain open.
4. State the user-visible result before the goals: one concrete sentence naming what the human will
inspect when this plan is done. Take it from the original request, not from your implementation plan.
Every requested artifact and action must survive into this sentence. An agent-inferred constraint may
+24 -2
View File
@@ -6,6 +6,7 @@ import { approvalPath, goalBlock, hashGoalBlock, repositoryState, verifyOutputPa
import { pairWithPiSupervise } from "./supervise.js";
const BOOTSTRAPPED = "pi-goals-visible-supervisor-v1";
const INITIAL_COMPACT_AT_TOKENS = 20_000;
const COMPACT_AT_TOKENS = 100_000;
interface SupervisorConfig {
@@ -66,7 +67,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, the complete plan, and pi-supervise worker views.
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 pi-supervise worker views. The complete plan at ${settings.planPath} is the source of truth; read it directly after every compaction.
Use pi-supervise to inspect and steer the worker. 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 steer and tell the worker to call CompleteGoal with the exact goal text. Do not call done until every plan goal is [x]. -- PI[gpt-5.6-sol]`;
}
@@ -94,8 +95,29 @@ 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) {
void bootstrap(ctx);
return;
}
compacting = true;
ctx.compact({
customInstructions: `Preserve the user's high-level intent, decisions, unresolved risks, and the supervisor's remit. The canonical plan is ${settings.planPath}; it remains available directly and must not be replaced by this summary.`,
onComplete: () => {
compacting = false;
ctx.ui.notify("Supervisor planning context compacted before work started.", "info");
void bootstrap(ctx);
},
onError: (error) => {
compacting = false;
ctx.ui.notify(`Supervisor startup compaction failed: ${error.message}`, "error");
},
});
};
pi.on("session_start", async (_event, ctx) => {
setImmediate(() => { void bootstrap(ctx); });
setImmediate(() => { bootstrapAfterInitialCompaction(ctx); });
});
pi.on("before_agent_start", async (_event, ctx) => {
+1
View File
@@ -55,6 +55,7 @@ function setup(selectChoices: Array<string | undefined>, editorChoices: Array<st
on: (name: string, handler: any) => hooks.set(name, handler),
appendEntry: (customType: string, data: unknown) => entries.push({ type: "custom", customType, data }),
registerTool: (tool: any) => tools.set(tool.name, tool),
getAllTools: () => [],
sendMessage: (message: { content: string; display?: boolean }) => messages.push(message),
sendUserMessage: (content: string) => messages.push({ content }),
};
+6 -3
View File
@@ -13,6 +13,7 @@ function input() {
planPath: "/repo/.pi/plan/worker-v1.md",
approvalId: "approval-1",
extensionPath: "/repo/src/index.ts",
superviseExtensionPath: null,
model: "provider/supervisor",
};
}
@@ -31,9 +32,11 @@ describe("supervisor pane command", () => {
expect(command).not.toContain("pi-subagents");
});
it("uses a local pi-supervise extension only when explicitly requested", () => {
vi.stubEnv("PI_GOALS_SUPERVISE_EXTENSION", "/repo/vendor/pi-supervise/src/index.ts");
expect(supervisorCommand(input())).toContain("'-e' '/repo/vendor/pi-supervise/src/index.ts'");
it("uses the loaded pi-supervise extension before the npm fallback", () => {
const loaded = { ...input(), superviseExtensionPath: "/repo/vendor/pi-supervise/src/index.ts" };
expect(supervisorCommand(loaded)).toContain("'-e' '/repo/vendor/pi-supervise/src/index.ts'");
vi.stubEnv("PI_GOALS_SUPERVISE_EXTENSION", "/repo/override/pi-supervise/src/index.ts");
expect(supervisorCommand(loaded)).toContain("'-e' '/repo/override/pi-supervise/src/index.ts'");
});
it("accepts Herdr's text version output and stale pane cleanup", async () => {
+4 -3
View File
@@ -4,12 +4,13 @@ import { completeGoalDescription, planDrafting, planningState, resync } from "..
describe("planning prompt", () => {
it("requires fact finding or a focused question before a goal", () => {
expect(planDrafting).toContain("Use read-only repository tools or web search when either can\nresolve a fact.");
expect(planDrafting).toContain("ask the human to confirm your interpretation");
expect(planDrafting).toContain("approve an editorial or other preference choice");
expect(planDrafting).toContain("Ask at least three short, concrete questions");
expect(planDrafting).toContain("understand the requested outcome, boundary, and how success will be judged");
expect(planDrafting).toContain("record that\npoint as unknown; do not silently replace it with an inference");
expect(planDrafting).toContain("answer materially reduces uncertainty\nwhile discovering the right plan");
expect(planDrafting).toContain("self-contained: state the relevant\ncontext, use the human's language and ASD-STE100");
expect(planDrafting).toContain("placeholder goal such as \"work out the thing\"");
expect(planDrafting).toContain("object, observable result, settled scope, and required approval");
expect(planDrafting).toContain("material user decisions remain open");
});
it("restores the same rule after compaction", () => {
+35 -3
View File
@@ -7,7 +7,7 @@ import { afterEach, describe, expect, it, vi } from "vitest";
import { approvalPath } from "../src/approval.js";
import { registerVisibleSupervisor } from "../src/supervisor-session.js";
function setup(cwd: string, planPath: string) {
function setup(cwd: string, planPath: string, tokens: number | null = 10, onCompact: (options: any) => void = (options) => options.onComplete()) {
vi.stubEnv("PI_GOALS_WORKER_ID", "worker-session");
vi.stubEnv("PI_GOALS_WORKER_INTERCOM_ID", "worker-intercom");
vi.stubEnv("PI_GOALS_OWNER_SESSION_ID", "worker-session");
@@ -22,8 +22,8 @@ function setup(cwd: string, planPath: string) {
const ctx = {
cwd,
getSystemPrompt: () => "base",
getContextUsage: () => ({ tokens: 10 }),
compact: vi.fn((options: any) => options.onComplete()),
getContextUsage: () => tokens === null ? undefined : ({ tokens }),
compact: vi.fn(onCompact),
sessionManager: {
getEntries: () => entries,
getBranch: () => branch,
@@ -67,6 +67,38 @@ describe("visible supervisor session", () => {
}
});
it("compacts a large planning fork before pairing", async () => {
const cwd = mkdtempSync(join(tmpdir(), "pi-goals-supervisor-"));
try {
let complete: (() => void) | undefined;
const runtime = setup(cwd, join(cwd, ".pi/plan/worker-v1.md"), 20_001, (options) => { complete = options.onComplete; });
await runtime.hooks.get("session_start")({}, runtime.ctx);
await new Promise((resolve) => setImmediate(resolve));
expect(runtime.ctx.compact).toHaveBeenCalledOnce();
expect(runtime.paired).toHaveLength(0);
complete!();
await new Promise((resolve) => setImmediate(resolve));
expect(runtime.paired).toHaveLength(1);
expect(runtime.messages).toEqual(["Supervision is paired. Inspect the worker and give its next concrete instruction."]);
} finally {
rmSync(cwd, { recursive: true, force: true });
}
});
it("does not start work when initial compaction fails", async () => {
const cwd = mkdtempSync(join(tmpdir(), "pi-goals-supervisor-"));
try {
const runtime = setup(cwd, join(cwd, ".pi/plan/worker-v1.md"), null, (options) => options.onError(new Error("offline")));
await runtime.hooks.get("session_start")({}, runtime.ctx);
await new Promise((resolve) => setImmediate(resolve));
expect(runtime.ctx.compact).toHaveBeenCalledOnce();
expect(runtime.paired).toHaveLength(0);
expect(runtime.ctx.ui.notify).toHaveBeenCalledWith("Supervisor startup compaction failed: offline", "error");
} finally {
rmSync(cwd, { recursive: true, force: true });
}
});
it("does not pair twice across session startup and later turns", async () => {
const cwd = mkdtempSync(join(tmpdir(), "pi-goals-supervisor-"));
try {