pi-plan: finish stale-pi crash fix, print plan on start, todo->task

- The Ready->fresh-context crash was a stale pi.* call inside
  withSession. Prior commit moved sendUserMessage to sessionCtx but
  left pi.setSessionName inside withSession (also stale -> crash).
  Drop it (cosmetic) and use only sessionCtx in the swap window.
- Print plan.md on execution start (both fresh and in-place) so the
  user sees what's being worked on after a context switch. Plan text
  captured before newSession since ctx goes stale.
- Widget: "(N todo)" -> "(N task[s])"

Co-Authored-By: Claudypoo <288921227+claudypoo@users.noreply.github.com>
This commit is contained in:
wassname
2026-06-15 20:49:57 +08:00
parent 3134adf203
commit f2f9e6a1b9
+7 -3
View File
@@ -77,7 +77,7 @@ export default function piPlanExtension(pi: ExtensionAPI): void {
for (const g of doc.goals) { for (const g of doc.goals) {
if (g.status === "done") continue; // hide finished goals; they stay in the file if (g.status === "done") continue; // hide finished goals; they stay in the file
const open = g.subtasks.filter((s) => !s.done).length; const open = g.subtasks.filter((s) => !s.done).length;
lines.push(`${mark[g.status]} ${g.subject}${open ? ` (${open} todo)` : ""}`); lines.push(`${mark[g.status]} ${g.subject}${open ? ` (${open} task${open === 1 ? "" : "s"})` : ""}`);
} }
const c = counts(doc); const c = counts(doc);
if (c.done) lines.push(`(${c.done} done, hidden)`); if (c.done) lines.push(`(${c.done} done, hidden)`);
@@ -201,17 +201,20 @@ export default function piPlanExtension(pi: ExtensionAPI): void {
} }
const doc = parse(readPlan(ctx)); const doc = parse(readPlan(ctx));
const planFile = planPath(ctx); const planFile = planPath(ctx);
const planContent = readPlan(ctx); // captured now: ctx is stale after newSession below
const parentSession = ctx.sessionManager.getSessionFile(); const parentSession = ctx.sessionManager.getSessionFile();
const startMsg = `Work the plan in ${planFile}. Pick an open goal, set it active, work its subtasks, and when its done_when is met call CompleteGoal with the evidence. Keep plan.md current as you go.`; const startMsg = `Work the plan in ${planFile}. Pick an open goal, set it active, work its subtasks, and when its done_when is met call CompleteGoal with the evidence. Keep plan.md current as you go.`;
exitPlanMode(ctx); exitPlanMode(ctx);
if (fresh && savedCmdCtx) { if (fresh && savedCmdCtx) {
// After newSession, `ctx`/`pi` bound to the old session are stale do post-swap work // After newSession, `ctx`/`pi` bound to the old session are stale; do post-swap work
// through the ReplacedSessionContext passed to withSession (see runner.assertActive). // through the ReplacedSessionContext passed to withSession (see runner.assertActive).
const result = await savedCmdCtx.newSession({ const result = await savedCmdCtx.newSession({
parentSession, parentSession,
withSession: async (sessionCtx) => { withSession: async (sessionCtx) => {
if (doc.objective) pi.setSessionName(`Plan: ${doc.objective}`); // pi.* and the outer ctx are invalidated by newSession; use the fresh sessionCtx only.
// (No setSessionName here: it lives on pi/the outer ctx, both stale now. Cosmetic, skip it.)
sessionCtx.ui.notify(planContent, "info");
await sessionCtx.sendUserMessage(startMsg, { deliverAs: "followUp" }); await sessionCtx.sendUserMessage(startMsg, { deliverAs: "followUp" });
}, },
}); });
@@ -221,6 +224,7 @@ export default function piPlanExtension(pi: ExtensionAPI): void {
return; return;
} }
if (doc.objective) pi.setSessionName(`Plan: ${doc.objective}`); if (doc.objective) pi.setSessionName(`Plan: ${doc.objective}`);
ctx.ui.notify(planContent, "info");
pi.sendUserMessage(startMsg, { deliverAs: "followUp" }); pi.sendUserMessage(startMsg, { deliverAs: "followUp" });
} }