mirror of
https://github.com/wassname/pi-goals.git
synced 2026-09-11 12:43:57 +08:00
Use TUI-style goals subcommands
Stop steward checkpoints after all goals close. Co-Authored-By: Pi Codex <288921227+claudypoo@users.noreply.github.com>
This commit is contained in:
@@ -82,11 +82,11 @@ pi -e npm:pi-subagents -e ./src/index.ts
|
||||
turns without a change above `## Log`, the worker gets a reminder and the steward gets a progress
|
||||
checkpoint.
|
||||
|
||||
Other commands: `/goals --clear` disconnects this session from its active plan, preserving the
|
||||
versioned file on disk; `/goals --auto [minutes|off]` continues active goals after the agent settles
|
||||
Other commands: `/goals clear` disconnects this session from its active plan, preserving the
|
||||
versioned file on disk; `/goals auto [minutes|off]` continues active goals after the agent settles
|
||||
and then on that interval. It pauses after two automatic wakes with no working-plan change; `/goals
|
||||
--steward-model <model-ref>` picks the steward model (default: the pi-subagents agent model). The `--` prefix
|
||||
keeps ordinary objectives such as `judge model quality` from being parsed as commands.
|
||||
model <model-ref>` picks the steward model (default: the pi-subagents agent model). These are TUI
|
||||
subcommands, not CLI flags.
|
||||
|
||||
## Prompts
|
||||
|
||||
|
||||
+8
-8
@@ -280,10 +280,10 @@ export default function piGoalsExtension(pi: ExtensionAPI): void {
|
||||
// --- /goals: enter plan mode (or clear / configure the steward) — Pi/Codex ---------------------
|
||||
|
||||
pi.registerCommand("goals", {
|
||||
description: `Plan mode: draft goals into ${PLAN_SHAPE}, review, then work them. /goals <objective> | /goals --clear | /goals --auto [minutes|off] | /goals --steward-model <model>`,
|
||||
description: `Plan mode: draft goals into ${PLAN_SHAPE}, review, then work them. /goals <objective> | /goals clear | /goals auto [minutes|off] | /goals model <model>`,
|
||||
handler: async (args, ctx) => {
|
||||
const arg = args.trim();
|
||||
if (arg === "--clear") {
|
||||
if (arg === "clear") {
|
||||
if (state.planVersion === null) {
|
||||
ctx.ui.notify("No active plan to disconnect.", "info");
|
||||
return;
|
||||
@@ -296,8 +296,8 @@ export default function piGoalsExtension(pi: ExtensionAPI): void {
|
||||
ctx.ui.notify(`Disconnected from ${currentPlan}; the file remains on disk.`, "info");
|
||||
return;
|
||||
}
|
||||
if (arg === "--auto" || arg.startsWith("--auto ")) {
|
||||
const value = arg.slice("--auto".length).trim();
|
||||
if (arg === "auto" || arg.startsWith("auto ")) {
|
||||
const value = arg.slice("auto".length).trim();
|
||||
if (value === "off") {
|
||||
clearAutoTimer();
|
||||
state = { ...state, autoIntervalMs: null, autoPaused: false };
|
||||
@@ -312,7 +312,7 @@ export default function piGoalsExtension(pi: ExtensionAPI): void {
|
||||
}
|
||||
const minutes = value ? Number(value) : AUTO_DEFAULT_INTERVAL_MS / 60_000;
|
||||
if (!Number.isInteger(minutes) || minutes < 1) {
|
||||
ctx.ui.notify("Use /goals --auto [whole minutes], or /goals --auto off.", "warning");
|
||||
ctx.ui.notify("Use /goals auto [whole minutes], or /goals auto off.", "warning");
|
||||
return;
|
||||
}
|
||||
autoWakeInFlight = false;
|
||||
@@ -325,8 +325,8 @@ export default function piGoalsExtension(pi: ExtensionAPI): void {
|
||||
ctx.ui.notify(`Goal auto-continue enabled every ${minutes}m.`, "info");
|
||||
return;
|
||||
}
|
||||
if (arg === "--steward-model" || arg.startsWith("--steward-model ")) {
|
||||
const ref = arg.slice("--steward-model".length).trim();
|
||||
if (arg === "model" || arg.startsWith("model ")) {
|
||||
const ref = arg.slice("model".length).trim();
|
||||
state = { ...state, stewardModel: ref || null, stewardRunId: null, stewardPending: false };
|
||||
persist();
|
||||
setupSteward(ctx);
|
||||
@@ -448,7 +448,7 @@ export default function piGoalsExtension(pi: ExtensionAPI): void {
|
||||
// PI: Print after Pi settles. agent_end is still streaming, so its message queues behind the menu.
|
||||
pi.on("agent_settled", async (_event, ctx) => {
|
||||
if (state.phase === "working") {
|
||||
if (turnsStale >= STALE_TURNS) await reviewInBackground(ctx, checkpointReview(planRel(ctx), turnsStale));
|
||||
if (turnsStale >= STALE_TURNS && activeGoals(ctx)) await reviewInBackground(ctx, checkpointReview(planRel(ctx), turnsStale));
|
||||
settleAuto(ctx);
|
||||
return;
|
||||
}
|
||||
|
||||
+24
-3
@@ -124,7 +124,7 @@ describe("/goals draft flow", () => {
|
||||
const planPath = join(flow.cwd, ".pi/plan/session-a-v1.md");
|
||||
writeFileSync(planPath, "# Plan\n\n## Goals\n\n1. [ ] goal: preserve this\n");
|
||||
|
||||
await flow.commands.get("goals").handler("--clear", flow.ctx);
|
||||
await flow.commands.get("goals").handler("clear", flow.ctx);
|
||||
|
||||
expect(readFileSync(planPath, "utf-8")).toContain("goal: preserve this");
|
||||
expect(flow.entries.at(-1)?.data).toMatchObject({ phase: null, planVersion: null });
|
||||
@@ -232,7 +232,7 @@ describe("/goals draft flow", () => {
|
||||
const planPath = join(flow.cwd, ".pi/plan/session-a-v1.md");
|
||||
writeFileSync(planPath, "# Plan\n\n## Goals\n\n1. [/] goal: make the output\n");
|
||||
await flow.hooks.get("agent_settled")({}, flow.ctx);
|
||||
await flow.commands.get("goals").handler("--auto 1", flow.ctx);
|
||||
await flow.commands.get("goals").handler("auto 1", flow.ctx);
|
||||
|
||||
await flow.hooks.get("agent_settled")({}, flow.ctx);
|
||||
await vi.advanceTimersByTimeAsync(0);
|
||||
@@ -259,7 +259,7 @@ describe("/goals draft flow", () => {
|
||||
const planPath = join(flow.cwd, ".pi/plan/session-a-v1.md");
|
||||
writeFileSync(planPath, "# Plan\n\n## Goals\n\n1. [/] goal: make the output\n");
|
||||
await flow.hooks.get("agent_settled")({}, flow.ctx);
|
||||
await flow.commands.get("goals").handler("--auto 1", flow.ctx);
|
||||
await flow.commands.get("goals").handler("auto 1", flow.ctx);
|
||||
await flow.hooks.get("agent_start")({}, flow.ctx);
|
||||
await flow.hooks.get("tool_call")({ toolName: "process", input: { action: "start" } }, flow.ctx);
|
||||
await flow.hooks.get("agent_settled")({}, flow.ctx);
|
||||
@@ -274,6 +274,27 @@ describe("/goals draft flow", () => {
|
||||
}
|
||||
});
|
||||
|
||||
it("does not checkpoint after every goal is closed", async () => {
|
||||
const flow = setup(["Ready"]);
|
||||
try {
|
||||
await flow.hooks.get("session_start")({}, flow.ctx);
|
||||
await flow.commands.get("goals").handler("objective", flow.ctx);
|
||||
const planPath = join(flow.cwd, ".pi/plan/session-a-v1.md");
|
||||
writeFileSync(planPath, "# Plan\n\n## Goals\n\n1. [/] goal: produce report\n");
|
||||
await flow.hooks.get("agent_settled")({}, flow.ctx);
|
||||
flow.eventBus.emit("subagent:async-complete", { runId: "steward-1" });
|
||||
|
||||
writeFileSync(planPath, "# Plan\n\n## Goals\n\n1. [x] goal: produce report\n");
|
||||
await flow.hooks.get("turn_end")({}, flow.ctx);
|
||||
for (let turn = 0; turn < 8; turn++) await flow.hooks.get("turn_end")({}, flow.ctx);
|
||||
await flow.hooks.get("agent_settled")({}, flow.ctx);
|
||||
|
||||
expect(flow.rpcRequests).toHaveLength(1);
|
||||
} finally {
|
||||
rmSync(flow.cwd, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
it("resumes the same steward lineage for sign-off and persists the latest run", async () => {
|
||||
const flow = setup(["Ready"]);
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user