@selesai/code 0.9.10 → 0.9.11

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.
Files changed (302) hide show
  1. package/CHANGELOG.md +260 -255
  2. package/dist/cli/args.js +13 -2
  3. package/dist/cli/args.test.js +8 -0
  4. package/dist/cli/credential-print.js +4 -4
  5. package/dist/cli.js +0 -0
  6. package/dist/core/agent-session-auto-handoff.test.js +24 -1
  7. package/dist/core/agent-session.d.ts +11 -4
  8. package/dist/core/agent-session.js +96 -35
  9. package/dist/core/compaction/branch-summarization.js +30 -30
  10. package/dist/core/compaction/compaction.js +81 -81
  11. package/dist/core/compaction/utils.js +2 -2
  12. package/dist/core/defaults.d.ts +1 -0
  13. package/dist/core/defaults.js +9 -0
  14. package/dist/core/export-html/template.css +1066 -1066
  15. package/dist/core/export-html/template.html +55 -55
  16. package/dist/core/export-html/template.js +1864 -1864
  17. package/dist/core/export-html/vendor/highlight.min.js +1212 -1212
  18. package/dist/core/export-html/vendor/marked.min.js +78 -78
  19. package/dist/core/extensions/loader.js +108 -35
  20. package/dist/core/extensions/loader.test.d.ts +1 -0
  21. package/dist/core/extensions/loader.test.js +21 -0
  22. package/dist/core/extensions/types.d.ts +12 -2
  23. package/dist/core/keybindings.d.ts +2 -2
  24. package/dist/core/messages.js +7 -7
  25. package/dist/core/model-resolver.d.ts +1 -0
  26. package/dist/core/model-resolver.js +10 -4
  27. package/dist/core/package-manager.js +4 -4
  28. package/dist/core/sdk.d.ts +5 -5
  29. package/dist/core/sdk.js +16 -4
  30. package/dist/core/settings-manager.d.ts +6 -0
  31. package/dist/core/settings-manager.js +22 -0
  32. package/dist/core/tools/bash.d.ts +18 -1
  33. package/dist/core/tools/bash.js +41 -23
  34. package/dist/core/tools/index.d.ts +4 -1
  35. package/dist/core/tools/index.js +9 -1
  36. package/dist/core/tools/powershell.d.ts +15 -0
  37. package/dist/core/tools/powershell.js +38 -0
  38. package/dist/core/tools/powershell.test.d.ts +1 -0
  39. package/dist/core/tools/powershell.test.js +20 -0
  40. package/dist/extensions/context-compaction-reminder.test.ts +82 -82
  41. package/dist/extensions/context-compaction-reminder.ts +28 -28
  42. package/dist/extensions/pi-intercom/LICENSE +21 -21
  43. package/dist/extensions/pi-intercom/broker/client.test.ts +83 -83
  44. package/dist/extensions/pi-intercom/broker/extension.test.ts +387 -387
  45. package/dist/extensions/pi-intercom/broker/framing.test.ts +114 -114
  46. package/dist/extensions/pi-intercom/broker/paths.test.ts +153 -153
  47. package/dist/extensions/pi-intercom/broker/paths.ts +134 -134
  48. package/dist/extensions/pi-intercom/broker/runtime-claim.test.ts +34 -34
  49. package/dist/extensions/pi-intercom/broker/runtime-claim.ts +21 -21
  50. package/dist/extensions/pi-intercom/cwd.test.ts +40 -40
  51. package/dist/extensions/pi-intercom/cwd.ts +31 -31
  52. package/dist/extensions/pi-intercom/extension-api.ts +44 -44
  53. package/dist/extensions/pi-intercom/format-context.test.ts +31 -31
  54. package/dist/extensions/pi-intercom/format-context.ts +32 -32
  55. package/dist/extensions/pi-intercom/test/overlay-width.test.ts +66 -66
  56. package/dist/extensions/pi-intercom/ui/compose.ts +143 -143
  57. package/dist/extensions/pi-intercom/ui/session-list.ts +166 -166
  58. package/dist/extensions/pi-powerline-footer/bash-mode/shell-session.ts +286 -286
  59. package/dist/extensions/pi-powerline-footer/bash-mode/transcript.ts +108 -108
  60. package/dist/extensions/pi-powerline-footer/separators.ts +57 -57
  61. package/dist/extensions/pi-powerline-footer/tests/session-usage.test.ts +47 -47
  62. package/dist/extensions/pi-powerline-footer/tests/tps.test.ts +39 -39
  63. package/dist/extensions/pi-powerline-footer/theme.example.json +24 -24
  64. package/dist/extensions/pi-powerline-footer/theme.json +12 -12
  65. package/dist/extensions/pi-powerline-footer/tps.ts +345 -345
  66. package/dist/extensions/pi-rewind-hook/README.md +245 -245
  67. package/dist/extensions/pi-rewind-hook/index.ts +1445 -1445
  68. package/dist/extensions/pi-rewind-hook/package.json +29 -29
  69. package/dist/extensions/pi-subagents/install.mjs +0 -0
  70. package/dist/extensions/pi-web-agent/package.json +31 -31
  71. package/dist/extensions/pi-web-agent/src/backends/config.ts +205 -205
  72. package/dist/extensions/pi-web-agent/src/backends/doctor.ts +136 -136
  73. package/dist/extensions/pi-web-agent/src/backends/factory.ts +152 -152
  74. package/dist/extensions/pi-web-agent/src/backends/settings-reader.ts +25 -25
  75. package/dist/extensions/pi-web-agent/src/cache/ttl-cache.ts +28 -28
  76. package/dist/extensions/pi-web-agent/src/changelog-notice.ts +136 -136
  77. package/dist/extensions/pi-web-agent/src/commands/web-agent-config.ts +946 -946
  78. package/dist/extensions/pi-web-agent/src/extension.ts +126 -126
  79. package/dist/extensions/pi-web-agent/src/extract/readability.ts +118 -118
  80. package/dist/extensions/pi-web-agent/src/fetch/browser-resolution.ts +199 -199
  81. package/dist/extensions/pi-web-agent/src/fetch/firecrawl-fetch.ts +100 -100
  82. package/dist/extensions/pi-web-agent/src/fetch/headless-fetch.ts +117 -117
  83. package/dist/extensions/pi-web-agent/src/fetch/http-fetch.ts +67 -67
  84. package/dist/extensions/pi-web-agent/src/orchestration/answer-synthesizer.ts +60 -60
  85. package/dist/extensions/pi-web-agent/src/orchestration/candidate-selector.ts +50 -50
  86. package/dist/extensions/pi-web-agent/src/orchestration/direct-url.ts +52 -52
  87. package/dist/extensions/pi-web-agent/src/orchestration/evidence-quality.ts +105 -105
  88. package/dist/extensions/pi-web-agent/src/orchestration/evidence-ranker.ts +45 -45
  89. package/dist/extensions/pi-web-agent/src/orchestration/index.ts +28 -28
  90. package/dist/extensions/pi-web-agent/src/orchestration/query-planner.ts +47 -47
  91. package/dist/extensions/pi-web-agent/src/orchestration/research-orchestrator.ts +376 -376
  92. package/dist/extensions/pi-web-agent/src/orchestration/research-types.ts +64 -64
  93. package/dist/extensions/pi-web-agent/src/orchestration/research-worker.ts +181 -181
  94. package/dist/extensions/pi-web-agent/src/orchestration/source-profile.ts +101 -101
  95. package/dist/extensions/pi-web-agent/src/orchestration/stop-decider.ts +81 -81
  96. package/dist/extensions/pi-web-agent/src/presentation/config-store.ts +210 -210
  97. package/dist/extensions/pi-web-agent/src/presentation/config.ts +75 -75
  98. package/dist/extensions/pi-web-agent/src/presentation/explore-presentation.ts +61 -61
  99. package/dist/extensions/pi-web-agent/src/presentation/fetch-presentation.ts +54 -54
  100. package/dist/extensions/pi-web-agent/src/presentation/search-presentation.ts +41 -41
  101. package/dist/extensions/pi-web-agent/src/presentation/select-view.ts +20 -20
  102. package/dist/extensions/pi-web-agent/src/presentation/types.ts +63 -63
  103. package/dist/extensions/pi-web-agent/src/search/brave.ts +114 -114
  104. package/dist/extensions/pi-web-agent/src/search/duckduckgo.ts +72 -72
  105. package/dist/extensions/pi-web-agent/src/search/searxng.ts +96 -96
  106. package/dist/extensions/pi-web-agent/src/tools/web-explore.ts +71 -71
  107. package/dist/extensions/pi-web-agent/src/tools/web-fetch-headless.ts +31 -31
  108. package/dist/extensions/pi-web-agent/src/tools/web-fetch.ts +31 -31
  109. package/dist/extensions/pi-web-agent/src/tools/web-search.ts +157 -157
  110. package/dist/extensions/pi-web-agent/src/types.ts +88 -88
  111. package/dist/extensions/ponytail/package.json +8 -8
  112. package/dist/extensions/question/batch.ts +103 -103
  113. package/dist/extensions/question/constants.ts +30 -30
  114. package/dist/extensions/question/helpers.ts +58 -58
  115. package/dist/extensions/question/navigation.ts +14 -14
  116. package/dist/extensions/question/package.json +19 -19
  117. package/dist/extensions/question/schemas.ts +43 -43
  118. package/dist/extensions/question/selection-mode.ts +46 -46
  119. package/dist/extensions/question/shortcuts.ts +44 -44
  120. package/dist/extensions/question/types.ts +132 -132
  121. package/dist/extensions/test-resolve-hook-impl.mjs +6 -6
  122. package/dist/extensions/test-resolve-hook.mjs +6 -6
  123. package/dist/extensions/web-agent-onboarding.ts +222 -222
  124. package/dist/extensions/workflow/package.json +17 -17
  125. package/dist/package-manager-cli.js +71 -71
  126. package/dist/rpc-entry.js +0 -0
  127. package/dist/skills/agent-browser/SKILL.md +52 -52
  128. package/dist/skills/batch-grill-me/SKILL.md +19 -19
  129. package/dist/skills/grill-me/SKILL.md +10 -10
  130. package/dist/skills/handoff/SKILL.md +16 -16
  131. package/dist/skills/handoff-text/SKILL.md +14 -14
  132. package/dist/skills/implanger/SKILL.md +69 -69
  133. package/dist/skills/improve-codebase/REFERENCE.md +78 -78
  134. package/dist/skills/improve-codebase/SKILL.md +178 -178
  135. package/dist/skills/planger/SKILL.md +166 -166
  136. package/dist/skills/ponytail/SKILL.md +116 -116
  137. package/dist/skills/ponytail-audit/SKILL.md +42 -42
  138. package/dist/skills/ponytail-debt/SKILL.md +45 -45
  139. package/dist/skills/ponytail-gain/SKILL.md +51 -51
  140. package/dist/skills/ponytail-help/SKILL.md +70 -70
  141. package/dist/skills/ponytail-review/SKILL.md +58 -58
  142. package/dist/skills/selesai-handoff/SKILL.md +20 -20
  143. package/dist/skills/workflow-creation/SKILL.md +73 -73
  144. package/dist/themes/powerline-footer/theme.json +33 -33
  145. package/dist/utils/shell.d.ts +3 -0
  146. package/dist/utils/shell.js +17 -5
  147. package/docs/compaction.md +396 -396
  148. package/docs/containerization.md +111 -111
  149. package/docs/development.md +71 -71
  150. package/docs/docs.json +164 -164
  151. package/docs/environment-variables.md +86 -86
  152. package/docs/index.md +83 -83
  153. package/docs/json.md +82 -82
  154. package/docs/models.md +502 -502
  155. package/docs/packages.md +227 -227
  156. package/docs/plans/subagent-delegation/phase-0-correctness.md +264 -264
  157. package/docs/plans/subagent-delegation/phase-1-behavioral-contract.md +485 -485
  158. package/docs/plans/subagent-delegation/phase-2-context-controls.md +281 -281
  159. package/docs/plans/subagent-delegation/phase-3-advisory-routing.md +361 -361
  160. package/docs/plans/subagent-delegation/phase-4-optional-enforcement.md +380 -380
  161. package/docs/prompt-templates.md +95 -95
  162. package/docs/providers.md +293 -293
  163. package/docs/sdk.md +1144 -1143
  164. package/docs/security.md +59 -59
  165. package/docs/session-format.md +414 -414
  166. package/docs/sessions.md +145 -145
  167. package/docs/shared-host-extensions.md +109 -109
  168. package/docs/shell-aliases.md +13 -13
  169. package/docs/skills.md +231 -231
  170. package/docs/terminal-setup.md +142 -142
  171. package/docs/termux.md +127 -127
  172. package/docs/themes.md +295 -295
  173. package/docs/tmux.md +63 -63
  174. package/docs/tui.md +927 -927
  175. package/docs/windows.md +17 -17
  176. package/examples/README.md +25 -25
  177. package/examples/extensions/README.md +211 -211
  178. package/examples/extensions/auto-commit-on-exit.ts +49 -49
  179. package/examples/extensions/bash-spawn-hook.ts +30 -30
  180. package/examples/extensions/bookmark.ts +50 -50
  181. package/examples/extensions/border-status-editor.ts +150 -150
  182. package/examples/extensions/built-in-tool-renderer.ts +249 -249
  183. package/examples/extensions/claude-rules.ts +86 -86
  184. package/examples/extensions/commands.ts +72 -72
  185. package/examples/extensions/confirm-destructive.ts +59 -59
  186. package/examples/extensions/custom-compaction.ts +130 -130
  187. package/examples/extensions/custom-footer.ts +64 -64
  188. package/examples/extensions/custom-header.ts +73 -73
  189. package/examples/extensions/custom-provider-anthropic/index.ts +610 -610
  190. package/examples/extensions/custom-provider-anthropic/package-lock.json +24 -24
  191. package/examples/extensions/custom-provider-anthropic/package.json +19 -19
  192. package/examples/extensions/custom-provider-gitlab-duo/index.ts +404 -404
  193. package/examples/extensions/custom-provider-gitlab-duo/package.json +16 -16
  194. package/examples/extensions/custom-provider-gitlab-duo/test.ts +82 -82
  195. package/examples/extensions/dirty-repo-guard.ts +56 -56
  196. package/examples/extensions/doom-overlay/README.md +46 -46
  197. package/examples/extensions/doom-overlay/doom/build/doom.js +21 -21
  198. package/examples/extensions/doom-overlay/doom/build/doom.wasm +0 -0
  199. package/examples/extensions/doom-overlay/doom/build.sh +152 -152
  200. package/examples/extensions/doom-overlay/doom/doomgeneric_pi.c +72 -72
  201. package/examples/extensions/doom-overlay/doom-component.ts +132 -132
  202. package/examples/extensions/doom-overlay/doom-engine.ts +173 -173
  203. package/examples/extensions/doom-overlay/doom-keys.ts +104 -104
  204. package/examples/extensions/doom-overlay/index.ts +74 -74
  205. package/examples/extensions/doom-overlay/wad-finder.ts +51 -51
  206. package/examples/extensions/dynamic-resources/SKILL.md +8 -8
  207. package/examples/extensions/dynamic-resources/dynamic.json +79 -79
  208. package/examples/extensions/dynamic-resources/dynamic.md +5 -5
  209. package/examples/extensions/dynamic-resources/index.ts +15 -15
  210. package/examples/extensions/dynamic-tools.ts +74 -74
  211. package/examples/extensions/event-bus.ts +43 -43
  212. package/examples/extensions/file-trigger.ts +41 -41
  213. package/examples/extensions/git-checkpoint.ts +53 -53
  214. package/examples/extensions/git-merge-and-resolve.ts +115 -115
  215. package/examples/extensions/github-issue-autocomplete.ts +185 -185
  216. package/examples/extensions/gondolin/index.ts +531 -531
  217. package/examples/extensions/gondolin/package-lock.json +185 -185
  218. package/examples/extensions/gondolin/package.json +19 -19
  219. package/examples/extensions/handoff.ts +199 -199
  220. package/examples/extensions/hello.ts +26 -26
  221. package/examples/extensions/hidden-thinking-label.ts +53 -53
  222. package/examples/extensions/inline-bash.ts +94 -94
  223. package/examples/extensions/input-transform-streaming.ts +39 -39
  224. package/examples/extensions/input-transform.ts +43 -43
  225. package/examples/extensions/interactive-shell.ts +196 -196
  226. package/examples/extensions/mac-system-theme.ts +47 -47
  227. package/examples/extensions/message-renderer.ts +59 -59
  228. package/examples/extensions/minimal-mode.ts +426 -426
  229. package/examples/extensions/modal-editor.ts +85 -85
  230. package/examples/extensions/model-status.ts +31 -31
  231. package/examples/extensions/notify.ts +55 -55
  232. package/examples/extensions/overlay-qa-tests.ts +1450 -1450
  233. package/examples/extensions/overlay-test.ts +153 -153
  234. package/examples/extensions/permission-gate.ts +34 -34
  235. package/examples/extensions/pirate.ts +47 -47
  236. package/examples/extensions/plan-mode/README.md +66 -66
  237. package/examples/extensions/plan-mode/index.ts +390 -390
  238. package/examples/extensions/plan-mode/utils.ts +168 -168
  239. package/examples/extensions/preset.ts +436 -436
  240. package/examples/extensions/project-trust.ts +64 -64
  241. package/examples/extensions/prompt-customizer.ts +97 -97
  242. package/examples/extensions/protected-paths.ts +30 -30
  243. package/examples/extensions/provider-payload.ts +18 -18
  244. package/examples/extensions/qna.ts +122 -122
  245. package/examples/extensions/question.ts +285 -285
  246. package/examples/extensions/questionnaire.ts +448 -448
  247. package/examples/extensions/rainbow-editor.ts +88 -88
  248. package/examples/extensions/reload-runtime.ts +37 -37
  249. package/examples/extensions/rpc-demo.ts +118 -118
  250. package/examples/extensions/sandbox/index.ts +321 -321
  251. package/examples/extensions/sandbox/package-lock.json +92 -92
  252. package/examples/extensions/sandbox/package.json +19 -19
  253. package/examples/extensions/send-user-message.ts +97 -97
  254. package/examples/extensions/session-name.ts +27 -27
  255. package/examples/extensions/shutdown-command.ts +63 -63
  256. package/examples/extensions/snake.ts +343 -343
  257. package/examples/extensions/space-invaders.ts +560 -560
  258. package/examples/extensions/ssh.ts +220 -220
  259. package/examples/extensions/status-line.ts +32 -32
  260. package/examples/extensions/structured-output.ts +65 -65
  261. package/examples/extensions/subagent/README.md +175 -175
  262. package/examples/extensions/subagent/agents/planner.md +37 -37
  263. package/examples/extensions/subagent/agents/reviewer.md +35 -35
  264. package/examples/extensions/subagent/agents/scout.md +50 -50
  265. package/examples/extensions/subagent/agents/worker.md +24 -24
  266. package/examples/extensions/subagent/agents.ts +126 -126
  267. package/examples/extensions/subagent/index.ts +1015 -1015
  268. package/examples/extensions/subagent/prompts/implement-and-review.md +10 -10
  269. package/examples/extensions/subagent/prompts/implement.md +10 -10
  270. package/examples/extensions/subagent/prompts/scout-and-plan.md +9 -9
  271. package/examples/extensions/summarize.ts +209 -209
  272. package/examples/extensions/system-prompt-header.ts +17 -17
  273. package/examples/extensions/tic-tac-toe.ts +1008 -1008
  274. package/examples/extensions/timed-confirm.ts +70 -70
  275. package/examples/extensions/titlebar-spinner.ts +58 -58
  276. package/examples/extensions/todo.ts +297 -297
  277. package/examples/extensions/tool-override.ts +144 -144
  278. package/examples/extensions/tools.ts +146 -146
  279. package/examples/extensions/trigger-compact.ts +50 -50
  280. package/examples/extensions/truncated-tool.ts +195 -195
  281. package/examples/extensions/widget-placement.ts +9 -9
  282. package/examples/extensions/with-deps/index.ts +32 -32
  283. package/examples/extensions/with-deps/package-lock.json +31 -31
  284. package/examples/extensions/with-deps/package.json +22 -22
  285. package/examples/extensions/working-indicator.ts +123 -123
  286. package/examples/extensions/working-message-test.ts +25 -25
  287. package/examples/rpc-extension-ui.ts +632 -632
  288. package/examples/sdk/01-minimal.ts +26 -26
  289. package/examples/sdk/02-custom-model.ts +53 -53
  290. package/examples/sdk/03-custom-prompt.ts +75 -75
  291. package/examples/sdk/04-skills.ts +55 -55
  292. package/examples/sdk/05-tools.ts +48 -48
  293. package/examples/sdk/06-extensions.ts +99 -99
  294. package/examples/sdk/07-context-files.ts +47 -47
  295. package/examples/sdk/08-prompt-templates.ts +51 -51
  296. package/examples/sdk/09-api-keys-and-oauth.ts +52 -52
  297. package/examples/sdk/10-settings.ts +53 -53
  298. package/examples/sdk/11-sessions.ts +52 -52
  299. package/examples/sdk/12-full-control.ts +79 -79
  300. package/examples/sdk/13-session-runtime.ts +67 -67
  301. package/examples/sdk/README.md +144 -144
  302. package/package.json +4 -4
@@ -1,390 +1,390 @@
1
- /**
2
- * Plan Mode Extension
3
- *
4
- * Read-only exploration mode for safe code analysis.
5
- * When enabled, built-in write tools are disabled.
6
- *
7
- * Features:
8
- * - /plan command or Ctrl+Alt+P to toggle
9
- * - Bash restricted to allowlisted read-only commands
10
- * - Extracts numbered plan steps from "Plan:" sections
11
- * - [DONE:n] markers to complete steps during execution
12
- * - Progress tracking widget during execution
13
- */
14
-
15
- import type { AgentMessage } from "@earendil-works/pi-agent-core";
16
- import type { AssistantMessage, TextContent } from "@earendil-works/pi-ai";
17
- import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
18
- import { Key } from "@earendil-works/pi-tui";
19
- import { extractTodoItems, isSafeCommand, markCompletedSteps, type TodoItem } from "./utils.ts";
20
-
21
- // Tools
22
- const PLAN_MODE_TOOLS = ["read", "bash", "grep", "find", "ls", "questionnaire"];
23
- const NORMAL_MODE_TOOLS = ["read", "bash", "edit", "write"];
24
- const PLAN_MODE_DISABLED_TOOLS = new Set<string>(["edit", "write"]);
25
- const PLAN_MANAGED_TOOLS = new Set<string>([...PLAN_MODE_TOOLS, ...NORMAL_MODE_TOOLS]);
26
-
27
- interface PlanModeState {
28
- enabled: boolean;
29
- todos?: TodoItem[];
30
- executing?: boolean;
31
- toolsBeforePlanMode?: string[];
32
- }
33
-
34
- // Type guard for assistant messages
35
- function isAssistantMessage(m: AgentMessage): m is AssistantMessage {
36
- return m.role === "assistant" && Array.isArray(m.content);
37
- }
38
-
39
- // Extract text content from an assistant message
40
- function getTextContent(message: AssistantMessage): string {
41
- return message.content
42
- .filter((block): block is TextContent => block.type === "text")
43
- .map((block) => block.text)
44
- .join("\n");
45
- }
46
-
47
- export default function planModeExtension(pi: ExtensionAPI): void {
48
- let planModeEnabled = false;
49
- let executionMode = false;
50
- let todoItems: TodoItem[] = [];
51
- let toolsBeforePlanMode: string[] | undefined;
52
-
53
- pi.registerFlag("plan", {
54
- description: "Start in plan mode (read-only exploration)",
55
- type: "boolean",
56
- default: false,
57
- });
58
-
59
- function updateStatus(ctx: ExtensionContext): void {
60
- // Footer status
61
- if (executionMode && todoItems.length > 0) {
62
- const completed = todoItems.filter((t) => t.completed).length;
63
- ctx.ui.setStatus("plan-mode", ctx.ui.theme.fg("accent", `📋 ${completed}/${todoItems.length}`));
64
- } else if (planModeEnabled) {
65
- ctx.ui.setStatus("plan-mode", ctx.ui.theme.fg("warning", "⏸ plan"));
66
- } else {
67
- ctx.ui.setStatus("plan-mode", undefined);
68
- }
69
-
70
- // Widget showing todo list
71
- if (executionMode && todoItems.length > 0) {
72
- const lines = todoItems.map((item) => {
73
- if (item.completed) {
74
- return (
75
- ctx.ui.theme.fg("success", "☑ ") + ctx.ui.theme.fg("muted", ctx.ui.theme.strikethrough(item.text))
76
- );
77
- }
78
- return `${ctx.ui.theme.fg("muted", "☐ ")}${item.text}`;
79
- });
80
- ctx.ui.setWidget("plan-todos", lines);
81
- } else {
82
- ctx.ui.setWidget("plan-todos", undefined);
83
- }
84
- }
85
-
86
- function uniqueToolNames(toolNames: string[]): string[] {
87
- return [...new Set(toolNames)];
88
- }
89
-
90
- function getPlanModeTools(activeToolNames: string[]): string[] {
91
- return uniqueToolNames([
92
- ...activeToolNames.filter((name) => !PLAN_MODE_DISABLED_TOOLS.has(name)),
93
- ...PLAN_MODE_TOOLS,
94
- ]);
95
- }
96
-
97
- function getNormalModeTools(activeToolNames: string[]): string[] {
98
- return uniqueToolNames([
99
- ...NORMAL_MODE_TOOLS,
100
- ...activeToolNames.filter((name) => !PLAN_MANAGED_TOOLS.has(name)),
101
- ]);
102
- }
103
-
104
- function enablePlanModeTools(): void {
105
- if (toolsBeforePlanMode === undefined) {
106
- toolsBeforePlanMode = pi.getActiveTools();
107
- }
108
- pi.setActiveTools(getPlanModeTools(toolsBeforePlanMode));
109
- }
110
-
111
- function restoreNormalModeTools(): void {
112
- pi.setActiveTools(toolsBeforePlanMode ?? getNormalModeTools(pi.getActiveTools()));
113
- toolsBeforePlanMode = undefined;
114
- }
115
-
116
- function persistState(): void {
117
- pi.appendEntry("plan-mode", {
118
- enabled: planModeEnabled,
119
- todos: todoItems,
120
- executing: executionMode,
121
- toolsBeforePlanMode,
122
- });
123
- }
124
-
125
- function togglePlanMode(ctx: ExtensionContext): void {
126
- planModeEnabled = !planModeEnabled;
127
- executionMode = false;
128
- todoItems = [];
129
-
130
- if (planModeEnabled) {
131
- enablePlanModeTools();
132
- ctx.ui.notify("Plan mode enabled. Built-in write tools disabled.");
133
- } else {
134
- restoreNormalModeTools();
135
- ctx.ui.notify("Plan mode disabled. Full access restored.");
136
- }
137
- updateStatus(ctx);
138
- persistState();
139
- }
140
-
141
- pi.registerCommand("plan", {
142
- description: "Toggle plan mode (read-only exploration)",
143
- handler: async (_args, ctx) => togglePlanMode(ctx),
144
- });
145
-
146
- pi.registerCommand("todos", {
147
- description: "Show current plan todo list",
148
- handler: async (_args, ctx) => {
149
- if (todoItems.length === 0) {
150
- ctx.ui.notify("No todos. Create a plan first with /plan", "info");
151
- return;
152
- }
153
- const list = todoItems.map((item, i) => `${i + 1}. ${item.completed ? "✓" : "○"} ${item.text}`).join("\n");
154
- ctx.ui.notify(`Plan Progress:\n${list}`, "info");
155
- },
156
- });
157
-
158
- pi.registerShortcut(Key.ctrlAlt("p"), {
159
- description: "Toggle plan mode",
160
- handler: async (ctx) => togglePlanMode(ctx),
161
- });
162
-
163
- // Block destructive bash commands in plan mode
164
- pi.on("tool_call", async (event) => {
165
- if (!planModeEnabled || event.toolName !== "bash") return;
166
-
167
- const command = event.input.command as string;
168
- if (!isSafeCommand(command)) {
169
- return {
170
- block: true,
171
- reason: `Plan mode: command blocked (not allowlisted). Use /plan to disable plan mode first.\nCommand: ${command}`,
172
- };
173
- }
174
- });
175
-
176
- // Filter out stale plan mode context when not in plan mode
177
- pi.on("context", async (event) => {
178
- if (planModeEnabled) return;
179
-
180
- return {
181
- messages: event.messages.filter((m) => {
182
- const msg = m as AgentMessage & { customType?: string };
183
- if (msg.customType === "plan-mode-context") return false;
184
- if (msg.role !== "user") return true;
185
-
186
- const content = msg.content;
187
- if (typeof content === "string") {
188
- return !content.includes("[PLAN MODE ACTIVE]");
189
- }
190
- if (Array.isArray(content)) {
191
- return !content.some(
192
- (c) => c.type === "text" && (c as TextContent).text?.includes("[PLAN MODE ACTIVE]"),
193
- );
194
- }
195
- return true;
196
- }),
197
- };
198
- });
199
-
200
- // Inject plan/execution context before agent starts
201
- pi.on("before_agent_start", async () => {
202
- if (planModeEnabled) {
203
- return {
204
- message: {
205
- customType: "plan-mode-context",
206
- content: `[PLAN MODE ACTIVE]
207
- You are in plan mode - a read-only exploration mode for safe code analysis.
208
-
209
- Restrictions:
210
- - Built-in edit and write tools are disabled
211
- - Other currently active tools remain available
212
- - Bash is restricted to an allowlist of read-only commands
213
-
214
- Ask clarifying questions using the questionnaire tool.
215
- Use brave-search skill via bash for web research.
216
-
217
- Create a detailed numbered plan under a "Plan:" header:
218
-
219
- Plan:
220
- 1. First step description
221
- 2. Second step description
222
- ...
223
-
224
- Do NOT attempt to make changes - just describe what you would do.`,
225
- display: false,
226
- },
227
- };
228
- }
229
-
230
- if (executionMode && todoItems.length > 0) {
231
- const remaining = todoItems.filter((t) => !t.completed);
232
- const todoList = remaining.map((t) => `${t.step}. ${t.text}`).join("\n");
233
- return {
234
- message: {
235
- customType: "plan-execution-context",
236
- content: `[EXECUTING PLAN - Full tool access enabled]
237
-
238
- Remaining steps:
239
- ${todoList}
240
-
241
- Execute each step in order.
242
- After completing a step, include a [DONE:n] tag in your response.`,
243
- display: false,
244
- },
245
- };
246
- }
247
- });
248
-
249
- // Track progress after each turn
250
- pi.on("turn_end", async (event, ctx) => {
251
- if (!executionMode || todoItems.length === 0) return;
252
- if (!isAssistantMessage(event.message)) return;
253
-
254
- const text = getTextContent(event.message);
255
- if (markCompletedSteps(text, todoItems) > 0) {
256
- updateStatus(ctx);
257
- }
258
- persistState();
259
- });
260
-
261
- // Handle plan completion and plan mode UI
262
- pi.on("agent_end", async (event, ctx) => {
263
- // Check if execution is complete
264
- if (executionMode && todoItems.length > 0) {
265
- if (todoItems.every((t) => t.completed)) {
266
- const completedList = todoItems.map((t) => `~~${t.text}~~`).join("\n");
267
- pi.sendMessage(
268
- { customType: "plan-complete", content: `**Plan Complete!** ✓\n\n${completedList}`, display: true },
269
- { triggerTurn: false },
270
- );
271
- executionMode = false;
272
- todoItems = [];
273
- updateStatus(ctx);
274
- persistState(); // Save cleared state so resume doesn't restore old execution mode
275
- }
276
- return;
277
- }
278
-
279
- if (!planModeEnabled || !ctx.hasUI) return;
280
-
281
- // Extract todos from last assistant message
282
- const lastAssistant = [...event.messages].reverse().find(isAssistantMessage);
283
- if (lastAssistant) {
284
- const extracted = extractTodoItems(getTextContent(lastAssistant));
285
- if (extracted.length > 0) {
286
- todoItems = extracted;
287
- }
288
- }
289
-
290
- if (todoItems.length === 0) return;
291
- persistState();
292
-
293
- // Show plan steps and prompt for next action
294
- const todoListText = todoItems.map((t, i) => `${i + 1}. ☐ ${t.text}`).join("\n");
295
- const planTodoListMessage = {
296
- customType: "plan-todo-list",
297
- content: `**Plan Steps (${todoItems.length}):**\n\n${todoListText}`,
298
- display: true,
299
- };
300
-
301
- const choice = await ctx.ui.select("Plan mode - what next?", [
302
- "Execute the plan (track progress)",
303
- "Stay in plan mode",
304
- "Refine the plan",
305
- ]);
306
-
307
- if (choice?.startsWith("Execute")) {
308
- const firstTodoItem = todoItems[0];
309
- if (!firstTodoItem) return;
310
-
311
- planModeEnabled = false;
312
- executionMode = true;
313
- restoreNormalModeTools();
314
- updateStatus(ctx);
315
- persistState();
316
-
317
- const remainingList = todoItems.map((t) => `${t.step}. ${t.text}`).join("\n");
318
- const execMessage = `Execute the plan.
319
-
320
- Remaining steps:
321
- ${remainingList}
322
-
323
- Start with: ${firstTodoItem.text}
324
- After completing a step, include a [DONE:n] tag in your response.`;
325
- pi.sendMessage(planTodoListMessage, { deliverAs: "followUp" });
326
- pi.sendMessage(
327
- { customType: "plan-mode-execute", content: execMessage, display: true },
328
- { triggerTurn: true, deliverAs: "followUp" },
329
- );
330
- } else if (choice === "Refine the plan") {
331
- const refinement = await ctx.ui.editor("Refine the plan:", "");
332
- if (refinement?.trim()) {
333
- pi.sendMessage(planTodoListMessage, { deliverAs: "followUp" });
334
- pi.sendUserMessage(refinement.trim(), { deliverAs: "followUp" });
335
- }
336
- }
337
- });
338
-
339
- // Restore state on session start/resume
340
- pi.on("session_start", async (_event, ctx) => {
341
- if (pi.getFlag("plan") === true) {
342
- planModeEnabled = true;
343
- }
344
-
345
- const entries = ctx.sessionManager.getEntries();
346
-
347
- // Restore persisted state
348
- const planModeEntry = entries
349
- .filter((e: { type: string; customType?: string }) => e.type === "custom" && e.customType === "plan-mode")
350
- .pop() as { data?: PlanModeState } | undefined;
351
-
352
- if (planModeEntry?.data) {
353
- planModeEnabled = planModeEntry.data.enabled ?? planModeEnabled;
354
- todoItems = planModeEntry.data.todos ?? todoItems;
355
- executionMode = planModeEntry.data.executing ?? executionMode;
356
- toolsBeforePlanMode = planModeEntry.data.toolsBeforePlanMode ?? toolsBeforePlanMode;
357
- }
358
-
359
- // On resume: re-scan messages to rebuild completion state
360
- // Only scan messages AFTER the last "plan-mode-execute" to avoid picking up [DONE:n] from previous plans
361
- const isResume = planModeEntry !== undefined;
362
- if (isResume && executionMode && todoItems.length > 0) {
363
- // Find the index of the last plan-mode-execute entry (marks when current execution started)
364
- let executeIndex = -1;
365
- for (let i = entries.length - 1; i >= 0; i--) {
366
- const entry = entries[i] as { type: string; customType?: string };
367
- if (entry.customType === "plan-mode-execute") {
368
- executeIndex = i;
369
- break;
370
- }
371
- }
372
-
373
- // Only scan messages after the execute marker
374
- const messages: AssistantMessage[] = [];
375
- for (let i = executeIndex + 1; i < entries.length; i++) {
376
- const entry = entries[i];
377
- if (entry.type === "message" && "message" in entry && isAssistantMessage(entry.message as AgentMessage)) {
378
- messages.push(entry.message as AssistantMessage);
379
- }
380
- }
381
- const allText = messages.map(getTextContent).join("\n");
382
- markCompletedSteps(allText, todoItems);
383
- }
384
-
385
- if (planModeEnabled) {
386
- enablePlanModeTools();
387
- }
388
- updateStatus(ctx);
389
- });
390
- }
1
+ /**
2
+ * Plan Mode Extension
3
+ *
4
+ * Read-only exploration mode for safe code analysis.
5
+ * When enabled, built-in write tools are disabled.
6
+ *
7
+ * Features:
8
+ * - /plan command or Ctrl+Alt+P to toggle
9
+ * - Bash restricted to allowlisted read-only commands
10
+ * - Extracts numbered plan steps from "Plan:" sections
11
+ * - [DONE:n] markers to complete steps during execution
12
+ * - Progress tracking widget during execution
13
+ */
14
+
15
+ import type { AgentMessage } from "@earendil-works/pi-agent-core";
16
+ import type { AssistantMessage, TextContent } from "@earendil-works/pi-ai";
17
+ import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
18
+ import { Key } from "@earendil-works/pi-tui";
19
+ import { extractTodoItems, isSafeCommand, markCompletedSteps, type TodoItem } from "./utils.ts";
20
+
21
+ // Tools
22
+ const PLAN_MODE_TOOLS = ["read", "bash", "grep", "find", "ls", "questionnaire"];
23
+ const NORMAL_MODE_TOOLS = ["read", "bash", "edit", "write"];
24
+ const PLAN_MODE_DISABLED_TOOLS = new Set<string>(["edit", "write"]);
25
+ const PLAN_MANAGED_TOOLS = new Set<string>([...PLAN_MODE_TOOLS, ...NORMAL_MODE_TOOLS]);
26
+
27
+ interface PlanModeState {
28
+ enabled: boolean;
29
+ todos?: TodoItem[];
30
+ executing?: boolean;
31
+ toolsBeforePlanMode?: string[];
32
+ }
33
+
34
+ // Type guard for assistant messages
35
+ function isAssistantMessage(m: AgentMessage): m is AssistantMessage {
36
+ return m.role === "assistant" && Array.isArray(m.content);
37
+ }
38
+
39
+ // Extract text content from an assistant message
40
+ function getTextContent(message: AssistantMessage): string {
41
+ return message.content
42
+ .filter((block): block is TextContent => block.type === "text")
43
+ .map((block) => block.text)
44
+ .join("\n");
45
+ }
46
+
47
+ export default function planModeExtension(pi: ExtensionAPI): void {
48
+ let planModeEnabled = false;
49
+ let executionMode = false;
50
+ let todoItems: TodoItem[] = [];
51
+ let toolsBeforePlanMode: string[] | undefined;
52
+
53
+ pi.registerFlag("plan", {
54
+ description: "Start in plan mode (read-only exploration)",
55
+ type: "boolean",
56
+ default: false,
57
+ });
58
+
59
+ function updateStatus(ctx: ExtensionContext): void {
60
+ // Footer status
61
+ if (executionMode && todoItems.length > 0) {
62
+ const completed = todoItems.filter((t) => t.completed).length;
63
+ ctx.ui.setStatus("plan-mode", ctx.ui.theme.fg("accent", `📋 ${completed}/${todoItems.length}`));
64
+ } else if (planModeEnabled) {
65
+ ctx.ui.setStatus("plan-mode", ctx.ui.theme.fg("warning", "⏸ plan"));
66
+ } else {
67
+ ctx.ui.setStatus("plan-mode", undefined);
68
+ }
69
+
70
+ // Widget showing todo list
71
+ if (executionMode && todoItems.length > 0) {
72
+ const lines = todoItems.map((item) => {
73
+ if (item.completed) {
74
+ return (
75
+ ctx.ui.theme.fg("success", "☑ ") + ctx.ui.theme.fg("muted", ctx.ui.theme.strikethrough(item.text))
76
+ );
77
+ }
78
+ return `${ctx.ui.theme.fg("muted", "☐ ")}${item.text}`;
79
+ });
80
+ ctx.ui.setWidget("plan-todos", lines);
81
+ } else {
82
+ ctx.ui.setWidget("plan-todos", undefined);
83
+ }
84
+ }
85
+
86
+ function uniqueToolNames(toolNames: string[]): string[] {
87
+ return [...new Set(toolNames)];
88
+ }
89
+
90
+ function getPlanModeTools(activeToolNames: string[]): string[] {
91
+ return uniqueToolNames([
92
+ ...activeToolNames.filter((name) => !PLAN_MODE_DISABLED_TOOLS.has(name)),
93
+ ...PLAN_MODE_TOOLS,
94
+ ]);
95
+ }
96
+
97
+ function getNormalModeTools(activeToolNames: string[]): string[] {
98
+ return uniqueToolNames([
99
+ ...NORMAL_MODE_TOOLS,
100
+ ...activeToolNames.filter((name) => !PLAN_MANAGED_TOOLS.has(name)),
101
+ ]);
102
+ }
103
+
104
+ function enablePlanModeTools(): void {
105
+ if (toolsBeforePlanMode === undefined) {
106
+ toolsBeforePlanMode = pi.getActiveTools();
107
+ }
108
+ pi.setActiveTools(getPlanModeTools(toolsBeforePlanMode));
109
+ }
110
+
111
+ function restoreNormalModeTools(): void {
112
+ pi.setActiveTools(toolsBeforePlanMode ?? getNormalModeTools(pi.getActiveTools()));
113
+ toolsBeforePlanMode = undefined;
114
+ }
115
+
116
+ function persistState(): void {
117
+ pi.appendEntry("plan-mode", {
118
+ enabled: planModeEnabled,
119
+ todos: todoItems,
120
+ executing: executionMode,
121
+ toolsBeforePlanMode,
122
+ });
123
+ }
124
+
125
+ function togglePlanMode(ctx: ExtensionContext): void {
126
+ planModeEnabled = !planModeEnabled;
127
+ executionMode = false;
128
+ todoItems = [];
129
+
130
+ if (planModeEnabled) {
131
+ enablePlanModeTools();
132
+ ctx.ui.notify("Plan mode enabled. Built-in write tools disabled.");
133
+ } else {
134
+ restoreNormalModeTools();
135
+ ctx.ui.notify("Plan mode disabled. Full access restored.");
136
+ }
137
+ updateStatus(ctx);
138
+ persistState();
139
+ }
140
+
141
+ pi.registerCommand("plan", {
142
+ description: "Toggle plan mode (read-only exploration)",
143
+ handler: async (_args, ctx) => togglePlanMode(ctx),
144
+ });
145
+
146
+ pi.registerCommand("todos", {
147
+ description: "Show current plan todo list",
148
+ handler: async (_args, ctx) => {
149
+ if (todoItems.length === 0) {
150
+ ctx.ui.notify("No todos. Create a plan first with /plan", "info");
151
+ return;
152
+ }
153
+ const list = todoItems.map((item, i) => `${i + 1}. ${item.completed ? "✓" : "○"} ${item.text}`).join("\n");
154
+ ctx.ui.notify(`Plan Progress:\n${list}`, "info");
155
+ },
156
+ });
157
+
158
+ pi.registerShortcut(Key.ctrlAlt("p"), {
159
+ description: "Toggle plan mode",
160
+ handler: async (ctx) => togglePlanMode(ctx),
161
+ });
162
+
163
+ // Block destructive bash commands in plan mode
164
+ pi.on("tool_call", async (event) => {
165
+ if (!planModeEnabled || event.toolName !== "bash") return;
166
+
167
+ const command = event.input.command as string;
168
+ if (!isSafeCommand(command)) {
169
+ return {
170
+ block: true,
171
+ reason: `Plan mode: command blocked (not allowlisted). Use /plan to disable plan mode first.\nCommand: ${command}`,
172
+ };
173
+ }
174
+ });
175
+
176
+ // Filter out stale plan mode context when not in plan mode
177
+ pi.on("context", async (event) => {
178
+ if (planModeEnabled) return;
179
+
180
+ return {
181
+ messages: event.messages.filter((m) => {
182
+ const msg = m as AgentMessage & { customType?: string };
183
+ if (msg.customType === "plan-mode-context") return false;
184
+ if (msg.role !== "user") return true;
185
+
186
+ const content = msg.content;
187
+ if (typeof content === "string") {
188
+ return !content.includes("[PLAN MODE ACTIVE]");
189
+ }
190
+ if (Array.isArray(content)) {
191
+ return !content.some(
192
+ (c) => c.type === "text" && (c as TextContent).text?.includes("[PLAN MODE ACTIVE]"),
193
+ );
194
+ }
195
+ return true;
196
+ }),
197
+ };
198
+ });
199
+
200
+ // Inject plan/execution context before agent starts
201
+ pi.on("before_agent_start", async () => {
202
+ if (planModeEnabled) {
203
+ return {
204
+ message: {
205
+ customType: "plan-mode-context",
206
+ content: `[PLAN MODE ACTIVE]
207
+ You are in plan mode - a read-only exploration mode for safe code analysis.
208
+
209
+ Restrictions:
210
+ - Built-in edit and write tools are disabled
211
+ - Other currently active tools remain available
212
+ - Bash is restricted to an allowlist of read-only commands
213
+
214
+ Ask clarifying questions using the questionnaire tool.
215
+ Use brave-search skill via bash for web research.
216
+
217
+ Create a detailed numbered plan under a "Plan:" header:
218
+
219
+ Plan:
220
+ 1. First step description
221
+ 2. Second step description
222
+ ...
223
+
224
+ Do NOT attempt to make changes - just describe what you would do.`,
225
+ display: false,
226
+ },
227
+ };
228
+ }
229
+
230
+ if (executionMode && todoItems.length > 0) {
231
+ const remaining = todoItems.filter((t) => !t.completed);
232
+ const todoList = remaining.map((t) => `${t.step}. ${t.text}`).join("\n");
233
+ return {
234
+ message: {
235
+ customType: "plan-execution-context",
236
+ content: `[EXECUTING PLAN - Full tool access enabled]
237
+
238
+ Remaining steps:
239
+ ${todoList}
240
+
241
+ Execute each step in order.
242
+ After completing a step, include a [DONE:n] tag in your response.`,
243
+ display: false,
244
+ },
245
+ };
246
+ }
247
+ });
248
+
249
+ // Track progress after each turn
250
+ pi.on("turn_end", async (event, ctx) => {
251
+ if (!executionMode || todoItems.length === 0) return;
252
+ if (!isAssistantMessage(event.message)) return;
253
+
254
+ const text = getTextContent(event.message);
255
+ if (markCompletedSteps(text, todoItems) > 0) {
256
+ updateStatus(ctx);
257
+ }
258
+ persistState();
259
+ });
260
+
261
+ // Handle plan completion and plan mode UI
262
+ pi.on("agent_end", async (event, ctx) => {
263
+ // Check if execution is complete
264
+ if (executionMode && todoItems.length > 0) {
265
+ if (todoItems.every((t) => t.completed)) {
266
+ const completedList = todoItems.map((t) => `~~${t.text}~~`).join("\n");
267
+ pi.sendMessage(
268
+ { customType: "plan-complete", content: `**Plan Complete!** ✓\n\n${completedList}`, display: true },
269
+ { triggerTurn: false },
270
+ );
271
+ executionMode = false;
272
+ todoItems = [];
273
+ updateStatus(ctx);
274
+ persistState(); // Save cleared state so resume doesn't restore old execution mode
275
+ }
276
+ return;
277
+ }
278
+
279
+ if (!planModeEnabled || !ctx.hasUI) return;
280
+
281
+ // Extract todos from last assistant message
282
+ const lastAssistant = [...event.messages].reverse().find(isAssistantMessage);
283
+ if (lastAssistant) {
284
+ const extracted = extractTodoItems(getTextContent(lastAssistant));
285
+ if (extracted.length > 0) {
286
+ todoItems = extracted;
287
+ }
288
+ }
289
+
290
+ if (todoItems.length === 0) return;
291
+ persistState();
292
+
293
+ // Show plan steps and prompt for next action
294
+ const todoListText = todoItems.map((t, i) => `${i + 1}. ☐ ${t.text}`).join("\n");
295
+ const planTodoListMessage = {
296
+ customType: "plan-todo-list",
297
+ content: `**Plan Steps (${todoItems.length}):**\n\n${todoListText}`,
298
+ display: true,
299
+ };
300
+
301
+ const choice = await ctx.ui.select("Plan mode - what next?", [
302
+ "Execute the plan (track progress)",
303
+ "Stay in plan mode",
304
+ "Refine the plan",
305
+ ]);
306
+
307
+ if (choice?.startsWith("Execute")) {
308
+ const firstTodoItem = todoItems[0];
309
+ if (!firstTodoItem) return;
310
+
311
+ planModeEnabled = false;
312
+ executionMode = true;
313
+ restoreNormalModeTools();
314
+ updateStatus(ctx);
315
+ persistState();
316
+
317
+ const remainingList = todoItems.map((t) => `${t.step}. ${t.text}`).join("\n");
318
+ const execMessage = `Execute the plan.
319
+
320
+ Remaining steps:
321
+ ${remainingList}
322
+
323
+ Start with: ${firstTodoItem.text}
324
+ After completing a step, include a [DONE:n] tag in your response.`;
325
+ pi.sendMessage(planTodoListMessage, { deliverAs: "followUp" });
326
+ pi.sendMessage(
327
+ { customType: "plan-mode-execute", content: execMessage, display: true },
328
+ { triggerTurn: true, deliverAs: "followUp" },
329
+ );
330
+ } else if (choice === "Refine the plan") {
331
+ const refinement = await ctx.ui.editor("Refine the plan:", "");
332
+ if (refinement?.trim()) {
333
+ pi.sendMessage(planTodoListMessage, { deliverAs: "followUp" });
334
+ pi.sendUserMessage(refinement.trim(), { deliverAs: "followUp" });
335
+ }
336
+ }
337
+ });
338
+
339
+ // Restore state on session start/resume
340
+ pi.on("session_start", async (_event, ctx) => {
341
+ if (pi.getFlag("plan") === true) {
342
+ planModeEnabled = true;
343
+ }
344
+
345
+ const entries = ctx.sessionManager.getEntries();
346
+
347
+ // Restore persisted state
348
+ const planModeEntry = entries
349
+ .filter((e: { type: string; customType?: string }) => e.type === "custom" && e.customType === "plan-mode")
350
+ .pop() as { data?: PlanModeState } | undefined;
351
+
352
+ if (planModeEntry?.data) {
353
+ planModeEnabled = planModeEntry.data.enabled ?? planModeEnabled;
354
+ todoItems = planModeEntry.data.todos ?? todoItems;
355
+ executionMode = planModeEntry.data.executing ?? executionMode;
356
+ toolsBeforePlanMode = planModeEntry.data.toolsBeforePlanMode ?? toolsBeforePlanMode;
357
+ }
358
+
359
+ // On resume: re-scan messages to rebuild completion state
360
+ // Only scan messages AFTER the last "plan-mode-execute" to avoid picking up [DONE:n] from previous plans
361
+ const isResume = planModeEntry !== undefined;
362
+ if (isResume && executionMode && todoItems.length > 0) {
363
+ // Find the index of the last plan-mode-execute entry (marks when current execution started)
364
+ let executeIndex = -1;
365
+ for (let i = entries.length - 1; i >= 0; i--) {
366
+ const entry = entries[i] as { type: string; customType?: string };
367
+ if (entry.customType === "plan-mode-execute") {
368
+ executeIndex = i;
369
+ break;
370
+ }
371
+ }
372
+
373
+ // Only scan messages after the execute marker
374
+ const messages: AssistantMessage[] = [];
375
+ for (let i = executeIndex + 1; i < entries.length; i++) {
376
+ const entry = entries[i];
377
+ if (entry.type === "message" && "message" in entry && isAssistantMessage(entry.message as AgentMessage)) {
378
+ messages.push(entry.message as AssistantMessage);
379
+ }
380
+ }
381
+ const allText = messages.map(getTextContent).join("\n");
382
+ markCompletedSteps(allText, todoItems);
383
+ }
384
+
385
+ if (planModeEnabled) {
386
+ enablePlanModeTools();
387
+ }
388
+ updateStatus(ctx);
389
+ });
390
+ }