mirror of
https://github.com/wassname/pi-goals.git
synced 2026-09-21 13:20:15 +08:00
Start supervisors from loaded extension
Co-Authored-By: PI[Kimi K3] <288921227+claudypoo@users.noreply.github.com>
This commit is contained in:
+2
-1
@@ -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)}`,
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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
@@ -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,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", () => {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user