@narumitw/pi-plan-mode 0.1.33 → 0.1.35

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -14,7 +14,7 @@ Pi core intentionally does not ship a built-in plan mode; this package provides
14
14
  - Disables extension and custom tools by default, with a `/plan tools` selector for explicit user-risk opt-in.
15
15
  - Blocks mutating built-in tools and bash commands such as `rm`, `git commit`, dependency installs, redirects, and editor launches.
16
16
  - Injects Codex-like Plan mode instructions: explore first, ask only non-discoverable questions, do not mutate files, and finish with `<proposed_plan>`.
17
- - Detects proposed plan blocks and prompts you to implement, revise, stay in Plan mode, or exit and discard the plan.
17
+ - Detects proposed plan blocks and prompts you to implement, stay in Plan mode, or exit and discard the plan.
18
18
  - Shows Plan mode state in Pi's statusline as `📝 plan active` or `📝 plan ready`.
19
19
  - Persists Plan mode state in the Pi session so resume restores the mode.
20
20
 
@@ -72,12 +72,12 @@ A complete Plan mode answer should include exactly one block like this:
72
72
  </proposed_plan>
73
73
  ```
74
74
 
75
- After a proposed plan is detected, `/plan` lets you choose whether to implement the plan, revise it, stay in Plan mode, or exit Plan mode. Choosing implementation disables Plan mode, restores full tool access, and immediately starts an implementation turn with the proposed plan. Choosing exit/off disables Plan mode and discards the proposed plan so it is not carried into later non-plan turns.
75
+ After a proposed plan is detected, `/plan` lets you choose whether to implement the plan, stay in Plan mode, or exit Plan mode. Choosing implementation disables Plan mode, restores full tool access, and immediately starts an implementation turn with the proposed plan. Choosing Stay keeps the plan ready while you decide what to do next; to revise the plan, choose Stay and type your revision feedback in the normal prompt. When that next Plan-mode turn starts, the previous plan is no longer treated as the latest implementable plan unless the agent produces an updated `<proposed_plan>`. Choosing exit/off disables Plan mode and discards the proposed plan so it is not carried into later non-plan turns.
76
76
 
77
77
  While Plan mode is enabled, the extension also publishes a compact status for Pi statuslines. With `@narumitw/pi-statusline`, this appears in the extension status area:
78
78
 
79
79
  - `📝 plan active`: Plan mode is enabled and still gathering context or drafting a plan.
80
- - `📝 plan ready`: A `<proposed_plan>` was detected and is waiting for your next `/plan` action.
80
+ - `📝 plan ready`: A `<proposed_plan>` was detected and remains ready until you implement it, continue planning, or exit Plan mode.
81
81
 
82
82
  You can also exit directly. Direct exit discards the latest proposed plan instead of treating it as an implementation request:
83
83
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@narumitw/pi-plan-mode",
3
- "version": "0.1.33",
3
+ "version": "0.1.35",
4
4
  "description": "Pi extension that adds a Codex-like read-only /plan collaboration mode.",
5
5
  "type": "module",
6
6
  "license": "MIT",
package/src/plan-mode.ts CHANGED
@@ -159,8 +159,13 @@ export default function planMode(pi: ExtensionAPI) {
159
159
  };
160
160
  });
161
161
 
162
- pi.on("before_agent_start", () => {
162
+ pi.on("before_agent_start", (_event, ctx) => {
163
163
  if (!state.enabled) return;
164
+ if (state.latestPlan || state.awaitingAction) {
165
+ state = { ...state, latestPlan: undefined, awaitingAction: false };
166
+ persistState();
167
+ updateUi(ctx);
168
+ }
164
169
  applyPlanModeTools();
165
170
  return {
166
171
  message: {
@@ -186,17 +191,20 @@ export default function planMode(pi: ExtensionAPI) {
186
191
  persistState();
187
192
  updateUi(ctx);
188
193
 
189
- if (ctx.hasUI) await showPlanReadyMenu(ctx);
190
- if (!state.enabled || !state.latestPlan) return;
191
-
192
- pi.sendMessage(
193
- {
194
- customType: PROPOSED_PLAN_MESSAGE_TYPE,
195
- content: `**Proposed Plan**\n\n${proposedPlan}`,
196
- display: true,
197
- },
198
- { triggerTurn: false },
199
- );
194
+ scheduleAfterCurrentAgentRun(async () => {
195
+ if (!state.enabled || state.latestPlan !== proposedPlan) return;
196
+ if (ctx.hasUI) await showPlanReadyMenu(ctx);
197
+ if (!state.enabled || state.latestPlan !== proposedPlan) return;
198
+
199
+ pi.sendMessage(
200
+ {
201
+ customType: PROPOSED_PLAN_MESSAGE_TYPE,
202
+ content: `**Proposed Plan**\n\n${proposedPlan}`,
203
+ display: true,
204
+ },
205
+ { triggerTurn: false },
206
+ );
207
+ });
200
208
  });
201
209
 
202
210
  function enterPlanMode(ctx: ExtensionContext) {
@@ -229,6 +237,15 @@ export default function planMode(pi: ExtensionAPI) {
229
237
  else pi.sendUserMessage(message, { deliverAs: "followUp" });
230
238
  }
231
239
 
240
+ function scheduleAfterCurrentAgentRun(task: () => Promise<void> | void) {
241
+ setTimeout(() => {
242
+ void Promise.resolve(task()).catch((error: unknown) => {
243
+ const message = error instanceof Error ? error.message : String(error);
244
+ console.error(`Plan mode follow-up failed: ${message}`);
245
+ });
246
+ }, 0);
247
+ }
248
+
232
249
  function startImplementation(ctx: ExtensionContext) {
233
250
  const plan = state.latestPlan?.trim();
234
251
  exitPlanMode(ctx);
@@ -283,8 +300,6 @@ export default function planMode(pi: ExtensionAPI) {
283
300
  async function showPlanReadyMenu(ctx: ExtensionContext) {
284
301
  const choice = await ctx.ui.select("Proposed plan ready. What next?", [
285
302
  "Implement this plan",
286
- "Revise plan",
287
- "Configure Plan-mode tools",
288
303
  "Stay in Plan mode",
289
304
  "Exit Plan mode",
290
305
  ]);
@@ -292,15 +307,6 @@ export default function planMode(pi: ExtensionAPI) {
292
307
  startImplementation(ctx);
293
308
  return;
294
309
  }
295
- if (choice === "Configure Plan-mode tools") {
296
- await showToolSelector(ctx);
297
- return;
298
- }
299
- if (choice === "Revise plan") {
300
- const refinement = await ctx.ui.editor("Revise the plan", "");
301
- if (refinement?.trim()) sendPlanModeUserMessage(refinement.trim(), ctx);
302
- return;
303
- }
304
310
  if (choice === "Exit Plan mode") {
305
311
  exitPlanMode(ctx);
306
312
  ctx.ui.notify("Plan mode disabled. Proposed plan discarded.", "info");
@@ -635,8 +641,7 @@ function messageContainsInactivePlanModeArtifact(message: unknown) {
635
641
  const candidate = unwrapSessionMessage(message);
636
642
  return (
637
643
  candidate.customType === PLAN_CONTEXT_MESSAGE_TYPE ||
638
- candidate.customType === PROPOSED_PLAN_MESSAGE_TYPE ||
639
- contentText(candidate.content).includes(PLAN_CONTEXT_MARKER)
644
+ candidate.customType === PROPOSED_PLAN_MESSAGE_TYPE
640
645
  );
641
646
  }
642
647