@narumitw/pi-plan-mode 0.18.0 → 0.24.0

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@narumitw/pi-plan-mode",
3
- "version": "0.18.0",
3
+ "version": "0.24.0",
4
4
  "description": "Pi extension that adds a Codex-like read-only /plan collaboration mode.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -29,8 +29,8 @@
29
29
  },
30
30
  "devDependencies": {
31
31
  "@biomejs/biome": "2.5.3",
32
- "@earendil-works/pi-coding-agent": "0.80.6",
33
- "typescript": "6.0.3"
32
+ "@earendil-works/pi-coding-agent": "0.80.10",
33
+ "typescript": "7.0.2"
34
34
  },
35
35
  "repository": {
36
36
  "type": "git",
@@ -2,8 +2,12 @@ import { PLAN_MODE_COMPLETE_TOOL_NAME } from "./completion-tool.js";
2
2
 
3
3
  const PLAN_CONTEXT_MESSAGE_TYPE = "plan-mode-context";
4
4
  const PROPOSED_PLAN_MESSAGE_TYPE = "proposed-plan";
5
- const PROPOSED_PLAN_PATTERN = /^<proposed_plan>[\t ]*\r?\n([\s\S]*?)\r?\n<\/proposed_plan>[\t ]*$/gm;
6
- const PROPOSED_PLAN_BLOCK_PATTERN = /^<proposed_plan>[\t ]*\r?\n[\s\S]*?\r?\n<\/proposed_plan>[\t ]*$/gm;
5
+ const PLAN_IMPLEMENTATION_HANDOFF_PREFIX =
6
+ "Plan mode is now disabled. Full tool access is restored. Implement this proposed plan now:";
7
+ const PROPOSED_PLAN_PATTERN =
8
+ /^<proposed_plan>[\t ]*\r?\n([\s\S]*?)\r?\n<\/proposed_plan>[\t ]*$/gm;
9
+ const PROPOSED_PLAN_BLOCK_PATTERN =
10
+ /^<proposed_plan>[\t ]*\r?\n[\s\S]*?\r?\n<\/proposed_plan>[\t ]*$/gm;
7
11
 
8
12
  export type ProposedPlanParseResult =
9
13
  | { kind: "absent" }
@@ -65,6 +69,14 @@ export function messageContainsInactivePlanModeArtifact(message: unknown) {
65
69
  );
66
70
  }
67
71
 
72
+ export function messageContainsPlanModeImplementationHandoff(message: unknown) {
73
+ const candidate = unwrapSessionMessage(message);
74
+ return (
75
+ candidate.role === "user" &&
76
+ contentText(candidate.content).trimStart().startsWith(PLAN_IMPLEMENTATION_HANDOFF_PREFIX)
77
+ );
78
+ }
79
+
68
80
  export function stripProposedPlanBlocksFromMessage<T>(message: T): T {
69
81
  return replaceAssistantContent(message, stripProposedPlanBlocksFromContent);
70
82
  }
@@ -74,9 +86,7 @@ export function stripPlanModeCompletionCallsFromMessage<T>(message: T): T {
74
86
  if (!Array.isArray(content)) return content;
75
87
  const nextContent = content.filter((block) => {
76
88
  const candidate = block as { type?: string; name?: string };
77
- return !(
78
- candidate.type === "toolCall" && candidate.name === PLAN_MODE_COMPLETE_TOOL_NAME
79
- );
89
+ return !(candidate.type === "toolCall" && candidate.name === PLAN_MODE_COMPLETE_TOOL_NAME);
80
90
  });
81
91
  return nextContent.length === content.length ? content : nextContent;
82
92
  });
@@ -91,10 +101,7 @@ export function isEmptyAssistantMessage(message: unknown) {
91
101
  );
92
102
  }
93
103
 
94
- function replaceAssistantContent<T>(
95
- message: T,
96
- transform: (content: unknown) => unknown,
97
- ): T {
104
+ function replaceAssistantContent<T>(message: T, transform: (content: unknown) => unknown): T {
98
105
  const candidate = unwrapSessionMessage(message);
99
106
  if (candidate.role !== "assistant") return message;
100
107
 
package/src/plan-mode.ts CHANGED
@@ -10,6 +10,7 @@ import {
10
10
  latestAssistantText,
11
11
  messageContainsInactivePlanModeArtifact,
12
12
  messageContainsLegacyPlanModeContextArtifact,
13
+ messageContainsPlanModeImplementationHandoff,
13
14
  parseProposedPlan,
14
15
  stripPlanModeCompletionCallsFromMessage,
15
16
  stripProposedPlanBlocksFromMessage,
@@ -26,6 +27,7 @@ import {
26
27
  import { showPersistentSelector } from "./selector-ui.js";
27
28
  import {
28
29
  configuredThinkingLevel,
30
+ type PlanModeFixedThinkingLevel,
29
31
  type PlanModeSettings,
30
32
  readPlanModeSettings,
31
33
  } from "./settings.js";
@@ -47,6 +49,20 @@ const BLOCKED_BUILTIN_TOOLS = new Set(["edit", "write"]);
47
49
  const DEFAULT_TOOLS = ["read", "bash", "edit", "write"];
48
50
  const TOOL_SELECTOR_PAGE_SIZE = 10;
49
51
 
52
+ type AgentSettledHandler = (event: unknown, ctx: ExtensionContext) => unknown;
53
+
54
+ function onAgentSettled(pi: ExtensionAPI, handler: AgentSettledHandler) {
55
+ (
56
+ pi as unknown as {
57
+ on(event: "agent_settled", callback: AgentSettledHandler): void;
58
+ }
59
+ ).on("agent_settled", handler);
60
+ }
61
+
62
+ function setPlanThinkingLevel(pi: ExtensionAPI, level: PlanModeFixedThinkingLevel) {
63
+ (pi.setThinkingLevel as unknown as (level: PlanModeFixedThinkingLevel) => void)(level);
64
+ }
65
+
50
66
  interface CommandArgumentCompletion {
51
67
  value: string;
52
68
  label: string;
@@ -277,7 +293,13 @@ export default function planMode(pi: ExtensionAPI) {
277
293
  const messagesWithoutLegacyPlanContext = event.messages.filter(
278
294
  (message: unknown) => !messageContainsLegacyPlanModeContextArtifact(message),
279
295
  );
280
- if (state.enabled) return { messages: messagesWithoutLegacyPlanContext };
296
+ if (state.enabled) {
297
+ return {
298
+ messages: messagesWithoutLegacyPlanContext.filter(
299
+ (message: unknown) => !messageContainsPlanModeImplementationHandoff(message),
300
+ ),
301
+ };
302
+ }
281
303
  return {
282
304
  messages: messagesWithoutLegacyPlanContext
283
305
  .filter((message: unknown) => !messageContainsInactivePlanModeArtifact(message))
@@ -322,7 +344,7 @@ export default function planMode(pi: ExtensionAPI) {
322
344
  acceptCompletedPlan(parsedPlan.plan, "legacy_proposed_plan", ctx);
323
345
  });
324
346
 
325
- pi.on("agent_settled", async (_event, ctx) => {
347
+ onAgentSettled(pi, async (_event, ctx) => {
326
348
  const intent = readyPresentationIntent;
327
349
  if (!intent || !readyPresentationIsCurrent(intent)) return;
328
350
  if (!ctx.isIdle() || ctx.hasPendingMessages()) return;
@@ -749,7 +771,7 @@ export default function planMode(pi: ExtensionAPI) {
749
771
  function applyPlanThinkingLevel() {
750
772
  if (state.manualThinkingLevel) {
751
773
  if (pi.getThinkingLevel() !== state.manualThinkingLevel) {
752
- pi.setThinkingLevel(state.manualThinkingLevel);
774
+ setPlanThinkingLevel(pi, state.manualThinkingLevel);
753
775
  }
754
776
  return;
755
777
  }
@@ -764,7 +786,7 @@ export default function planMode(pi: ExtensionAPI) {
764
786
  }
765
787
  const current = pi.getThinkingLevel();
766
788
  if (!state.appliedThinkingLevel) state.previousThinkingLevel = current;
767
- if (current !== configured) pi.setThinkingLevel(configured);
789
+ if (current !== configured) setPlanThinkingLevel(pi, configured);
768
790
  state.appliedThinkingLevel = pi.getThinkingLevel();
769
791
  }
770
792
 
@@ -788,7 +810,7 @@ export default function planMode(pi: ExtensionAPI) {
788
810
  previousThinkingLevel &&
789
811
  pi.getThinkingLevel() === appliedThinkingLevel
790
812
  ) {
791
- pi.setThinkingLevel(previousThinkingLevel);
813
+ setPlanThinkingLevel(pi, previousThinkingLevel);
792
814
  }
793
815
  state = { ...state, appliedThinkingLevel: undefined, previousThinkingLevel: undefined };
794
816
  }