@gavin-hu/my-pi-agent 0.21.0 → 0.22.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.
Files changed (254) hide show
  1. package/README.md +46 -31
  2. package/README.zh-CN.md +40 -26
  3. package/assets/screens/board.png +0 -0
  4. package/assets/screens/chrome.png +0 -0
  5. package/assets/screens/file-browse.png +0 -0
  6. package/assets/screens/plan-review.png +0 -0
  7. package/assets/screens/plan.png +0 -0
  8. package/docs/extensions/README.md +22 -24
  9. package/docs/extensions/board.md +361 -147
  10. package/docs/extensions/bytefly.md +152 -111
  11. package/docs/extensions/check.md +15 -17
  12. package/docs/extensions/context.md +35 -15
  13. package/docs/extensions/file-browse.md +30 -31
  14. package/docs/extensions/file-open.md +9 -11
  15. package/docs/extensions/file-read.md +12 -14
  16. package/docs/extensions/goal.md +15 -17
  17. package/docs/extensions/job.md +54 -42
  18. package/docs/extensions/memory.md +17 -19
  19. package/docs/extensions/plan.md +166 -126
  20. package/docs/extensions/question.md +16 -18
  21. package/docs/extensions/rewind.md +17 -19
  22. package/docs/extensions/serve.md +34 -18
  23. package/docs/extensions/session-bus.md +38 -16
  24. package/docs/extensions/subagent.md +23 -25
  25. package/docs/extensions/team.md +72 -33
  26. package/docs/extensions/todo.md +18 -18
  27. package/docs/extensions/watch.md +31 -31
  28. package/docs/extensions/web-access.md +2 -4
  29. package/docs/extensions/wechat.md +34 -35
  30. package/docs/extensions/worktree.md +56 -50
  31. package/extensions/board/commands.ts +56 -15
  32. package/extensions/board/format.ts +43 -24
  33. package/extensions/board/index.ts +5 -4
  34. package/extensions/board/input.ts +4 -9
  35. package/extensions/board/join.ts +4 -40
  36. package/extensions/board/lazy-page.ts +40 -0
  37. package/extensions/board/page.ts +13 -11
  38. package/extensions/board/result.ts +30 -16
  39. package/extensions/board/rows.ts +62 -25
  40. package/extensions/board/schema.ts +42 -10
  41. package/extensions/board/snapshot.ts +2 -1
  42. package/extensions/board/tone.ts +22 -0
  43. package/extensions/board/tools.ts +131 -44
  44. package/extensions/board/types.ts +36 -32
  45. package/extensions/board/view/card.ts +229 -0
  46. package/extensions/board/view/client.ts +33 -15
  47. package/extensions/board/view/dialog.ts +169 -6
  48. package/extensions/board/view/empty.ts +47 -0
  49. package/extensions/board/view/keys.ts +13 -2
  50. package/extensions/board/view/live.ts +89 -21
  51. package/extensions/board/view/names.ts +13 -4
  52. package/extensions/board/view/page.ts +23 -9
  53. package/extensions/board/view/render.ts +208 -271
  54. package/extensions/board/view/styles.ts +123 -31
  55. package/extensions/board/view/team.ts +297 -76
  56. package/extensions/bytefly/block/fallback.ts +1 -1
  57. package/extensions/bytefly/doctor/runtime.ts +5 -2
  58. package/extensions/bytefly/footer/collect/label.ts +60 -0
  59. package/extensions/bytefly/footer/collect/snapshot.ts +8 -1
  60. package/extensions/bytefly/footer/config.ts +4 -2
  61. package/extensions/bytefly/footer/register.ts +33 -4
  62. package/extensions/bytefly/footer/render/lines.ts +7 -4
  63. package/extensions/bytefly/footer/render/segments.ts +37 -10
  64. package/extensions/bytefly/footer/render/status.ts +0 -11
  65. package/extensions/bytefly/footer/types.ts +7 -0
  66. package/extensions/bytefly/header/config.ts +1 -1
  67. package/extensions/bytefly/index.ts +32 -6
  68. package/extensions/bytefly/mode/editor.ts +115 -0
  69. package/extensions/bytefly/panel/commands.ts +210 -120
  70. package/extensions/bytefly/panel/format.ts +4 -4
  71. package/extensions/bytefly/panel/settings.ts +73 -6
  72. package/extensions/bytefly/panel/tui/keys.ts +2 -6
  73. package/extensions/bytefly/update/cache.ts +1 -1
  74. package/extensions/bytefly/update/register.ts +4 -4
  75. package/extensions/bytefly/working/config.ts +5 -7
  76. package/extensions/bytefly/working/format.ts +10 -13
  77. package/extensions/bytefly/working/register.ts +22 -27
  78. package/extensions/check/commands.ts +3 -2
  79. package/extensions/check/{tui.ts → view/check-screens.ts} +14 -19
  80. package/extensions/context/commands.ts +2 -2
  81. package/extensions/context/{tui.ts → view/context-report.ts} +51 -19
  82. package/extensions/file-browse/index.ts +67 -36
  83. package/extensions/file-read/render.ts +4 -4
  84. package/extensions/goal/plan-route.ts +2 -2
  85. package/extensions/job/child.ts +527 -0
  86. package/extensions/job/clock.ts +142 -0
  87. package/extensions/job/commands.ts +4 -2
  88. package/extensions/job/config.ts +8 -1
  89. package/extensions/job/format.ts +22 -1
  90. package/extensions/job/log-writer.ts +13 -2
  91. package/extensions/job/registry.ts +63 -7
  92. package/extensions/job/render.ts +34 -0
  93. package/extensions/job/runtime.ts +296 -655
  94. package/extensions/job/session.ts +132 -19
  95. package/extensions/job/store.ts +7 -7
  96. package/extensions/job/tools.ts +3 -3
  97. package/extensions/job/types.ts +47 -0
  98. package/extensions/job/{tui.ts → view/jobs-screen.ts} +19 -73
  99. package/extensions/job/waiters.ts +44 -1
  100. package/extensions/memory/commands.ts +4 -2
  101. package/extensions/memory/{tui.ts → view/memory-picker.ts} +12 -12
  102. package/extensions/plan/commands.ts +12 -9
  103. package/extensions/plan/format.ts +8 -2
  104. package/extensions/plan/git-bash.ts +115 -16
  105. package/extensions/plan/index.ts +53 -52
  106. package/extensions/plan/plan-index.ts +4 -2
  107. package/extensions/plan/plans.ts +5 -54
  108. package/extensions/plan/policy.ts +8 -10
  109. package/extensions/plan/review-lazy.ts +85 -0
  110. package/extensions/plan/review.ts +2 -2
  111. package/extensions/plan/runtime.ts +49 -39
  112. package/extensions/plan/sections.ts +1 -1
  113. package/extensions/plan/steps.ts +3 -3
  114. package/extensions/plan/todos.ts +1 -1
  115. package/extensions/plan/tools/cancel.ts +27 -62
  116. package/extensions/plan/tools/enter.ts +55 -81
  117. package/extensions/plan/tools/exit.ts +221 -238
  118. package/extensions/plan/tools/refusal.ts +18 -0
  119. package/extensions/plan/tools/steps.ts +75 -92
  120. package/extensions/plan/tools/write.ts +77 -120
  121. package/extensions/plan/tools.ts +188 -30
  122. package/extensions/plan/tui.ts +5 -5
  123. package/extensions/plan/types.ts +48 -5
  124. package/extensions/plan/web-client.ts +3 -0
  125. package/extensions/plan/web-lazy.ts +53 -0
  126. package/extensions/plan/web-markdown.ts +9 -67
  127. package/extensions/plan/web-payload.ts +3 -3
  128. package/extensions/plan/web-router.ts +18 -9
  129. package/extensions/plan/web.ts +1 -12
  130. package/extensions/question/tools.ts +6 -2
  131. package/extensions/question/{tui.ts → view/question-screen.ts} +11 -11
  132. package/extensions/rewind/commands/commands.ts +6 -3
  133. package/extensions/rewind/state/runtime.ts +1 -1
  134. package/extensions/rewind/{view/status.ts → status.ts} +4 -4
  135. package/extensions/rewind/view/review-tui.ts +4 -4
  136. package/extensions/serve/config.ts +10 -8
  137. package/extensions/serve/index.ts +17 -3
  138. package/extensions/serve/registry.ts +26 -17
  139. package/extensions/session-bus/commands.ts +11 -6
  140. package/extensions/session-bus/join-request.ts +2 -1
  141. package/extensions/session-bus/mail-watch.ts +26 -17
  142. package/extensions/session-bus/protocol.ts +15 -3
  143. package/extensions/session-bus/resolve.ts +5 -7
  144. package/extensions/session-bus/runtime.ts +40 -12
  145. package/extensions/session-bus/store.ts +101 -19
  146. package/extensions/session-bus/types.ts +19 -0
  147. package/extensions/session-bus/{tui.ts → view/session-list.ts} +16 -16
  148. package/extensions/subagent/agents.ts +1 -1
  149. package/extensions/subagent/commands.ts +4 -2
  150. package/extensions/subagent/format.ts +15 -15
  151. package/extensions/subagent/index.ts +1 -1
  152. package/extensions/subagent/render.ts +3 -3
  153. package/extensions/subagent/{background-tui.ts → view/agents-board.ts} +11 -11
  154. package/extensions/team/commands.ts +24 -7
  155. package/extensions/team/dispatch.ts +18 -4
  156. package/extensions/team/format.ts +17 -2
  157. package/extensions/team/index.ts +9 -8
  158. package/extensions/team/schema.ts +6 -5
  159. package/extensions/team/tools.ts +107 -45
  160. package/extensions/team/types.ts +3 -2
  161. package/extensions/todo/commands.ts +4 -2
  162. package/extensions/todo/format.ts +11 -0
  163. package/extensions/todo/render.ts +57 -0
  164. package/extensions/todo/runtime.ts +1 -1
  165. package/extensions/todo/tools.ts +1 -1
  166. package/extensions/todo/view/todo-screen.ts +122 -0
  167. package/extensions/todo/widget.ts +47 -0
  168. package/extensions/watch/commands.ts +4 -2
  169. package/extensions/watch/format.ts +36 -7
  170. package/extensions/watch/render.ts +57 -0
  171. package/extensions/watch/runtime.ts +27 -22
  172. package/extensions/watch/tools.ts +4 -4
  173. package/extensions/watch/{tui.ts → view/watch-screen.ts} +18 -80
  174. package/extensions/web-access/fetch/tool.ts +12 -4
  175. package/extensions/wechat/commands/commands.ts +18 -14
  176. package/extensions/wechat/index.ts +72 -37
  177. package/extensions/wechat/state/credentials.ts +1 -1
  178. package/extensions/wechat/state/state.ts +1 -1
  179. package/extensions/wechat/text/status.ts +112 -0
  180. package/extensions/wechat/types.ts +2 -2
  181. package/extensions/wechat/view/status-screen.ts +4 -103
  182. package/extensions/worktree/commands.ts +11 -9
  183. package/extensions/worktree/enter.ts +2 -2
  184. package/extensions/worktree/exit.ts +2 -2
  185. package/extensions/worktree/index.ts +5 -5
  186. package/extensions/worktree/screen.ts +3 -3
  187. package/extensions/worktree/status.ts +2 -2
  188. package/extensions/worktree/tools.ts +113 -116
  189. package/lib/README.md +27 -13
  190. package/lib/board/actions.ts +111 -0
  191. package/lib/board/artifact.ts +149 -0
  192. package/lib/board/card.ts +163 -52
  193. package/lib/board/config.ts +58 -0
  194. package/lib/board/contract.ts +113 -0
  195. package/lib/board/join.ts +38 -0
  196. package/lib/board/lanes.ts +182 -0
  197. package/lib/board/lead.ts +130 -0
  198. package/lib/board/limits.ts +78 -0
  199. package/lib/board/links.ts +31 -10
  200. package/lib/board/page-pointer.ts +1 -1
  201. package/lib/board/read.ts +83 -0
  202. package/lib/board/reason.ts +102 -0
  203. package/lib/board/round-text.ts +68 -6
  204. package/lib/board/round.ts +31 -15
  205. package/lib/board/rules.ts +151 -307
  206. package/lib/board/seat.ts +35 -0
  207. package/lib/board/session.ts +136 -54
  208. package/lib/board/skipped.ts +22 -0
  209. package/lib/board/steps.ts +487 -0
  210. package/lib/board/store.ts +87 -30
  211. package/lib/board/tone.ts +92 -0
  212. package/lib/board/view.ts +70 -58
  213. package/{extensions/watch → lib}/child.ts +40 -21
  214. package/lib/env.ts +65 -7
  215. package/lib/format.ts +13 -0
  216. package/lib/http/index.ts +14 -12
  217. package/lib/http/port.ts +15 -10
  218. package/lib/lazy.ts +45 -0
  219. package/lib/plan-entry.ts +39 -10
  220. package/lib/plans/paths.ts +82 -0
  221. package/lib/policy.ts +9 -6
  222. package/lib/report.ts +3 -2
  223. package/lib/requirements.ts +8 -8
  224. package/lib/session-label.ts +111 -5
  225. package/lib/session-roster.ts +43 -1
  226. package/lib/session-tool.ts +5 -4
  227. package/lib/stray-fields.ts +7 -2
  228. package/lib/tool-names.ts +5 -10
  229. package/lib/tui/README.md +23 -8
  230. package/lib/tui/chrome.ts +16 -15
  231. package/lib/tui/cursor.ts +18 -3
  232. package/lib/tui/glyphs.ts +9 -7
  233. package/lib/tui/tokens.ts +33 -2
  234. package/lib/tui/tool.ts +3 -3
  235. package/lib/web/README.md +21 -0
  236. package/lib/web/components.ts +5 -1
  237. package/lib/web/index.ts +1 -0
  238. package/lib/web/markdown.ts +83 -0
  239. package/lib/web/markup.ts +2 -1
  240. package/lib/web/runtime.ts +9 -10
  241. package/package.json +6 -1
  242. package/skills/init/SKILL.md +5 -5
  243. package/skills/next/SKILL.md +3 -5
  244. package/extensions/board/seat.ts +0 -28
  245. package/extensions/todo/tui.ts +0 -212
  246. package/lib/tui/index.ts +0 -34
  247. package/skills/project-docs/SKILL.md +0 -133
  248. package/skills/project-docs/references/audit.md +0 -131
  249. package/skills/project-docs/references/changelog.md +0 -105
  250. package/skills/project-docs/references/discovery.md +0 -127
  251. package/skills/project-docs/references/readme.md +0 -80
  252. package/skills/project-docs/references/release.md +0 -84
  253. package/skills/project-docs/references/roadmap.md +0 -93
  254. package/skills/project-docs/scripts/state.ts +0 -316
@@ -2,18 +2,18 @@
2
2
  * Session-scoped state and tool gating for plan mode.
3
3
  *
4
4
  * Enabling plan mode removes every tool the policy does not consider read-only
5
- * from the active set and activates the plan-mode control tools: `exit_plan_mode`
6
- * (present the plan for approval) and `write_plan` (save the plan artifact under
7
- * the plans directory). Disabling restores exactly what it hid. The enabled flag is
8
- * persisted as a custom session entry so `/resume` and `/tree` follow the
9
- * branch, and it is also mirrored into the footer status. The review baseline
10
- * travels the same way, as one digest per heading section, so the changed-section
11
- * markers survive a resume too.
5
+ * from the active set, and keeps the one `plan` tool active in both modes: its
6
+ * actions are what ask to enter, save the artifact under the plans directory,
7
+ * present it, tick it, and leave. Disabling restores exactly what it hid. The
8
+ * enabled flag is persisted as a custom session entry so `/resume` and `/tree`
9
+ * follow the branch, and it is also mirrored into the footer status. The review
10
+ * baseline travels the same way, as one digest per heading section, so the
11
+ * changed-section markers survive a resume too.
12
12
  *
13
- * `write_plan` is the one sanctioned write while planning. The read-only guard
14
- * in `index.ts` permits plan's own tools only when the registered tool is this
15
- * extension's (matched by source path), so a same-named tool from another
16
- * extension cannot borrow the exemption.
13
+ * The `plan` tool's `write` action is the one sanctioned write while planning. The
14
+ * read-only guard in `index.ts` permits that tool only when the registered one is
15
+ * this extension's (matched by source path), so a same-named tool from another
16
+ * extension cannot borrow the exemption, and each action refuses outside its mode.
17
17
  */
18
18
 
19
19
  import { basename, resolve } from "node:path";
@@ -23,23 +23,25 @@ import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-a
23
23
  import { sanitizeLine } from "../../lib/format.ts";
24
24
  import { PLAN_MODE_ENTRY_TYPE } from "../../lib/plan-state.ts";
25
25
  import type { ReadOnlyPolicy } from "../../lib/policy.ts";
26
- import { PLAN_ENTER_TOOL } from "../../lib/tool-names.ts";
26
+ import { PLAN_TOOL as PLAN_TOOL_NAME } from "../../lib/tool-names.ts";
27
27
  import { GLYPHS, STATUS_KEYS } from "../../lib/tui/glyphs.ts";
28
- import { paint } from "../../lib/tui/tokens.ts";
29
28
  import { createPlanStore, type PlanStore } from "./plans.ts";
30
29
  import { sameSections, sectionDigests, type SectionDigest } from "./plan-text.ts";
31
30
  import type { PlanModeEntry, ReviewBaselineEntry, SeededStepsEntry } from "./types.ts";
32
31
 
33
- /** Tool the model calls to leave plan mode after approval. */
34
- export const EXIT_TOOL = "exit_plan_mode";
35
- /** Tool the model calls to leave plan mode without approving. */
36
- export const CANCEL_TOOL = "cancel_plan_mode";
37
- /** Tool the model calls to ask to enter plan mode. */
38
- export const ENTER_TOOL = PLAN_ENTER_TOOL;
39
- /** Tool the model calls to save the plan artifact. */
40
- export const WRITE_PLAN_TOOL = "write_plan";
41
- /** Tool the model calls to keep an approved plan's steps current. */
42
- export const SET_PLAN_STEPS_TOOL = "set_plan_steps";
32
+ /** The one model-facing tool: every planning verb is one of its actions. */
33
+ export const PLAN_TOOL = PLAN_TOOL_NAME;
34
+
35
+ /**
36
+ * The verbs of the one `plan` tool, in the order the schema offers them.
37
+ *
38
+ * One action tool rather than five tools, so every verb shares a name and a
39
+ * registration; the order is the one the description walks them in.
40
+ */
41
+ export const PLAN_ACTIONS = ["enter", "write", "exit", "steps", "cancel"] as const;
42
+
43
+ /** One verb of the `plan` tool. */
44
+ export type PlanAction = (typeof PLAN_ACTIONS)[number];
43
45
  /** Custom-entry type used for plan-mode state. */
44
46
  export const STATE_TYPE = PLAN_MODE_ENTRY_TYPE;
45
47
  /** Custom-entry type used for a plan's review baseline. */
@@ -50,16 +52,13 @@ export const SEEDED_TYPE = "plan-seeded";
50
52
  /** Longest plan-file basename shown in the footer chip. */
51
53
  const STATUS_NAME_LENGTH = 28;
52
54
 
53
- /** Tools plan mode activates itself. */
54
- const CONTROL_TOOLS = [EXIT_TOOL, CANCEL_TOOL, WRITE_PLAN_TOOL];
55
-
56
55
  /**
57
- * Tools the read-only guard exempts when this extension registered them.
58
- * `enter_plan_mode` is here only so a call already in flight when plan mode came on
59
- * is answered by the tool ("already in plan mode"); the policy still denies it, so
60
- * it stays hidden while planning.
56
+ * The one tool plan mode keeps in the active set in both modes. Every other tool
57
+ * follows the policy, which is about writing files and cannot answer for a tool
58
+ * whose actions are the mode's own verbs: hiding it while planning would hide the
59
+ * way to write the plan and the way out.
61
60
  */
62
- const OWNED_TOOLS = [...CONTROL_TOOLS, ENTER_TOOL];
61
+ const MODE_TOOLS = [PLAN_TOOL];
63
62
 
64
63
  export interface PlanRuntime {
65
64
  /** Whether plan mode is currently on. */
@@ -69,9 +68,9 @@ export interface PlanRuntime {
69
68
  toggle(ctx: ExtensionContext): void;
70
69
  /** Restore state from the branch and the `--plan` flag. */
71
70
  restore(ctx: ExtensionContext): void;
72
- /** Plan-file storage for `write_plan` and `exit_plan_mode`. */
71
+ /** Plan-file storage for the `write` and `exit` actions. */
73
72
  plans: PlanStore;
74
- /** Whether `toolName` is one of plan's own tools (not a same-named impostor) the read-only guard exempts. */
73
+ /** Whether `toolName` is the `plan` tool (not a same-named impostor) the read-only guard exempts. */
75
74
  isOwnTool(toolName: string): boolean;
76
75
  /** Path of the most recently written plan, for the footer chip. */
77
76
  lastPlanPath(): string | undefined;
@@ -195,6 +194,9 @@ export function createPlanRuntime(
195
194
  // Tools plan mode deactivated, so disabling restores exactly those and does
196
195
  // not re-activate a tool another extension intentionally hid.
197
196
  let removedForPlan: string[] = [];
197
+ // Mode tools this enable had to activate, so disabling puts the user's own
198
+ // active set back instead of widening it.
199
+ let addedForPlan: string[] = [];
198
200
 
199
201
  const plans = createPlanStore(pi);
200
202
 
@@ -204,7 +206,7 @@ export function createPlanRuntime(
204
206
  * is rejected.
205
207
  */
206
208
  const isOwnTool = (toolName: string): boolean => {
207
- if (!OWNED_TOOLS.includes(toolName)) return false;
209
+ if (toolName !== PLAN_TOOL) return false;
208
210
  const info = pi.getAllTools().find((tool) => tool.name === toolName);
209
211
  const source = info?.sourceInfo?.path;
210
212
  return source === undefined || options.entryPath === undefined || samePath(source, options.entryPath);
@@ -212,7 +214,7 @@ export function createPlanRuntime(
212
214
 
213
215
  /** Tools plan mode would hide from the current active set. */
214
216
  const captureHidden = (): string[] => [
215
- ...new Set(pi.getActiveTools().filter((name) => !policy.isAllowed(name) && !CONTROL_TOOLS.includes(name))),
217
+ ...new Set(pi.getActiveTools().filter((name) => !policy.isAllowed(name) && !MODE_TOOLS.includes(name))),
216
218
  ];
217
219
 
218
220
  /**
@@ -224,12 +226,18 @@ export function createPlanRuntime(
224
226
  const active = pi.getActiveTools();
225
227
  if (enabled) {
226
228
  // Keep only tools the policy considers safe to expose while read-only
227
- // (structured readers and the plan/goal trackers) plus the control tools.
228
- pi.setActiveTools([...new Set([...active.filter((name) => policy.isAllowed(name)), ...CONTROL_TOOLS])]);
229
+ // (structured readers and the plan/goal trackers), plus the `plan` tool
230
+ // itself, whose actions are the mode's own vocabulary.
231
+ addedForPlan = MODE_TOOLS.filter((name) => !active.includes(name));
232
+ pi.setActiveTools([...new Set([...active.filter((name) => policy.isAllowed(name)), ...MODE_TOOLS])]);
229
233
  return;
230
234
  }
235
+ // A mode tool this enable had to activate goes back to how the user left it,
236
+ // rather than staying active because the mode needed it for a while.
237
+ const kept = active.filter((name) => !addedForPlan.includes(name));
238
+ addedForPlan = [];
231
239
  const restored = addHidden ? removedForPlan : [];
232
- pi.setActiveTools([...new Set([...active.filter((name) => !CONTROL_TOOLS.includes(name)), ...restored])]);
240
+ pi.setActiveTools([...new Set([...kept, ...restored])]);
233
241
  removedForPlan = [];
234
242
  };
235
243
 
@@ -237,7 +245,9 @@ export function createPlanRuntime(
237
245
  lastPlanPath ? `${GLYPHS.plan} plan · ${statusName(lastPlanPath)}` : `${GLYPHS.plan} plan`;
238
246
 
239
247
  const setStatus = (ctx: ExtensionContext): void => {
240
- ctx.ui.setStatus(STATUS_KEYS.planMode, enabled ? paint(ctx.ui.theme, "warn", statusText()) : undefined);
248
+ // Published unstyled: the chip's text is what a footer without bytefly shows, and
249
+ // bytefly's mode segment paints the mode by role instead of reading this pill.
250
+ ctx.ui.setStatus(STATUS_KEYS.planMode, enabled ? statusText() : undefined);
241
251
  };
242
252
 
243
253
  const setEnabled = (next: boolean, ctx: ExtensionContext | undefined, persist: boolean): void => {
@@ -2,7 +2,7 @@
2
2
  * Plan-document sections (pure).
3
3
  *
4
4
  * One fence-aware scan of a plan's markdown: the review screen navigates by
5
- * section, `set_plan_steps` looks up `## Steps`, a carried comment finds the
5
+ * section, the `plan` tool's `steps` action looks up `## Steps`, a carried comment finds the
6
6
  * block it belongs to, and the browser outline lists the same headings. A second
7
7
  * scanner would disagree about a heading inside a fence, so every reader goes
8
8
  * through this module.
@@ -31,7 +31,7 @@ export const MAX_STEP_LENGTH = 200;
31
31
  * section that carries the end state — the same surface before and after when the
32
32
  * change alters one that already exists, one frame when it is new — in a fence
33
33
  * that stays untagged unless every line is code, so the reviewer sees the result
34
- * before the steps that build it. Guidance only: `write_plan` never rejects or
34
+ * before the steps that build it. Guidance only: the `write` action never rejects or
35
35
  * rewrites a plan that omits a section, and a change with no visible surface drops
36
36
  * `Visual`.
37
37
  */
@@ -268,7 +268,7 @@ const LIST_PREFIX = /^\s*(?:[-*+]|\d+[.)])\s+(?:\[[ xX]\]\s*)?/;
268
268
  * list markers and inline emphasis, and the lines are joined with `; `, prefixed
269
269
  * with `Verify:` and elided like a long step. The item is deliberately *not* a
270
270
  * plan step: it is not in the `Steps` section, so it must not count against the
271
- * step cap or appear in `set_plan_steps` numbering.
271
+ * step cap or appear in the `steps` action's numbering.
272
272
  *
273
273
  * A plan with no `Steps` heading feeds its shallowest list as steps (the older
274
274
  * rule), so that list already covers the verification bullets; appending a
@@ -297,7 +297,7 @@ export function extractPlanSteps(plan: string): PlanStep[] {
297
297
  return topLevelItems(plan).map((item) => ({ content: elide(item.step.content), status: item.step.status }));
298
298
  }
299
299
 
300
- /** The full cost of a plan's step list, for the warning `write_plan` reports. */
300
+ /** The full cost of a plan's step list, for the warning the `write` action reports. */
301
301
  export interface PlanStepSummary {
302
302
  /** Top-level items in the step list, before the {@link MAX_STEPS} cap. */
303
303
  total: number;
@@ -44,7 +44,7 @@ export interface TodoMerge {
44
44
  /** Fallback list cap when the registered todo tool does not advertise one. */
45
45
  export const DEFAULT_TODO_MAX = 50;
46
46
 
47
- /** What the tick-time mirror did for one `set_plan_steps` batch. */
47
+ /** What the tick-time mirror did for one `steps` batch. */
48
48
  export interface TodoMirror {
49
49
  /** The list after the matching items were brought in line with the plan. */
50
50
  todos: ExistingTodo[];
@@ -1,5 +1,5 @@
1
1
  /**
2
- * The `cancel_plan_mode` tool registration.
2
+ * The `cancel` action of the `plan` tool.
3
3
  *
4
4
  * This is the escape hatch that does not need a plan file or a review dialog:
5
5
  * when the user asks to leave planning, the model can restore write access
@@ -7,73 +7,38 @@
7
7
  * still review the saved artifact.
8
8
  */
9
9
 
10
- import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
11
- import { Type } from "typebox";
12
- import { reuseText, toolDetails } from "../../../lib/tui/chrome.ts";
10
+ import type { AgentToolResult, ExtensionToolContext, Theme } from "@earendil-works/pi-coding-agent";
13
11
  import { paint } from "../../../lib/tui/tokens.ts";
14
- import { callLine, detailsOrLine } from "../../../lib/tui/tool.ts";
15
- import { failedResult } from "../../../lib/tool-result.ts";
16
- import { CANCEL_TOOL, type PlanRuntime } from "../runtime.ts";
17
- import type { CancelPlanModeDetails } from "../types.ts";
12
+ import { callLine, type ToolCallText } from "../../../lib/tui/tool.ts";
13
+ import { planRefusal } from "./refusal.ts";
14
+ import { PLAN_TOOL, type PlanRuntime } from "../runtime.ts";
15
+ import type { CancelPlanModeDetails, PlanDetails } from "../types.ts";
18
16
 
19
17
  const CANCELLED_TEXT =
20
18
  "Plan mode is off and write access is restored. The plan was not approved; no todo items or goal were recorded. " +
21
19
  "Any saved plan file remains for a later review.";
22
20
 
23
- export function registerCancelTool(pi: ExtensionAPI, runtime: PlanRuntime): void {
24
- pi.registerTool({
25
- name: CANCEL_TOOL,
26
- label: "Cancel plan mode",
27
- description:
28
- "Leave plan mode without approving a plan. Restores write access; no todo items or goal are recorded. " +
29
- "Use when the user asks to exit, cancel, or abandon planning.",
30
- promptSnippet: "Leave plan mode without approving the plan.",
31
- promptGuidelines: [
32
- "Call cancel_plan_mode when the user asks to leave or abandon planning without approving a plan.",
33
- ],
34
- parameters: Type.Object({}),
35
- defaultActive: false,
36
- executionMode: "sequential",
37
- // The mode change touches no files; like enter_plan_mode, it needs no
38
- // destructive annotation.
39
- annotations: { readOnlyHint: true, openWorldHint: false },
40
-
41
- async execute(_toolCallId, _params, _signal, _onUpdate, ctx) {
42
- if (!runtime.isEnabled()) {
43
- return failedResult("Not in plan mode; there is nothing to cancel.", {
44
- cancelled: false,
45
- } satisfies CancelPlanModeDetails);
46
- }
47
- runtime.disable(ctx);
48
- ctx.ui.notify("Plan mode off — full access restored. The plan was not approved.", "info");
49
- return {
50
- content: [{ type: "text", text: CANCELLED_TEXT }],
51
- details: { cancelled: true } satisfies CancelPlanModeDetails,
52
- };
53
- },
21
+ export async function executeCancel(
22
+ runtime: PlanRuntime,
23
+ ctx: ExtensionToolContext,
24
+ ): Promise<AgentToolResult<PlanDetails>> {
25
+ if (!runtime.isEnabled()) {
26
+ return planRefusal("cancel", "Not in plan mode; there is nothing to cancel.");
27
+ }
28
+ runtime.disable(ctx);
29
+ ctx.ui.notify("Plan mode off — full access restored. The plan was not approved.", "info");
30
+ return {
31
+ content: [{ type: "text", text: CANCELLED_TEXT }],
32
+ details: { action: "cancel", cancelled: true } satisfies CancelPlanModeDetails,
33
+ };
34
+ }
54
35
 
55
- renderCall(_args, theme, context) {
56
- return callLine(theme, context?.lastComponent, CANCEL_TOOL, "left plan mode");
57
- },
36
+ export function callCancel(theme: Theme, last: unknown): ToolCallText {
37
+ return callLine(theme, last, PLAN_TOOL, "cancel");
38
+ }
58
39
 
59
- renderResult(result, options, theme, context) {
60
- const text = reuseText(context?.lastComponent);
61
- const outcome = detailsOrLine(
62
- theme,
63
- result,
64
- options.expanded,
65
- toolDetails<CancelPlanModeDetails>(result.details),
66
- );
67
- if ("line" in outcome) {
68
- text.setText(outcome.line);
69
- return text;
70
- }
71
- text.setText(
72
- outcome.details.cancelled
73
- ? paint(theme, "warn", "Plan mode off — plan not approved")
74
- : paint(theme, "meta", "Not in plan mode"),
75
- );
76
- return text;
77
- },
78
- });
40
+ export function textCancel(details: CancelPlanModeDetails, theme: Theme): string {
41
+ return details.cancelled
42
+ ? paint(theme, "warn", "Plan mode off — plan not approved")
43
+ : paint(theme, "meta", "Not in plan mode");
79
44
  }
@@ -1,98 +1,72 @@
1
1
  /**
2
- * The `enter_plan_mode` tool registration.
2
+ * The `enter` action of the `plan` tool.
3
3
  *
4
4
  * It asks for confirmation, then turns the runtime's enabled flag on; the tool
5
5
  * gating and persistence belong to `runtime.ts`.
6
6
  */
7
7
 
8
- import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
9
- import { Type } from "typebox";
10
- import { reuseText, toolDetails } from "../../../lib/tui/chrome.ts";
11
- import { GLYPHS } from "../../../lib/tui/glyphs.ts";
12
- import { paint } from "../../../lib/tui/tokens.ts";
13
- import { callLine, detailsOrLine } from "../../../lib/tui/tool.ts";
14
- import { failedResult } from "../../../lib/tool-result.ts";
8
+ import type { AgentToolResult, ExtensionToolContext, Theme } from "@earendil-works/pi-coding-agent";
15
9
  import { isRemoteTurn, remoteLanguage } from "../../../lib/interaction.ts";
16
10
  import { PLAN_CONSENT, PLAN_CONSENT_SHORT, PLAN_CONSENT_SHORT_ZH, READ_ONLY_SUMMARY } from "../../../lib/plan-entry.ts";
17
- import { ENTER_TOOL, type PlanRuntime } from "../runtime.ts";
11
+ import { failedResult } from "../../../lib/tool-result.ts";
12
+ import { GLYPHS } from "../../../lib/tui/glyphs.ts";
13
+ import { paint } from "../../../lib/tui/tokens.ts";
14
+ import { callLine, type ToolCallText } from "../../../lib/tui/tool.ts";
15
+ import { PLAN_TOOL, type PlanRuntime } from "../runtime.ts";
18
16
  import type { EnterPlanModeDetails } from "../types.ts";
19
17
 
20
18
  const ENTERED_TEXT =
21
- "You are now in plan mode. Explore read-only, then save the full plan with write_plan (a short title and the markdown) " +
22
- "and call exit_plan_mode with the returned path so the user can read the file and choose. " +
19
+ "You are now in plan mode. Explore read-only, then save the full plan with `plan` action `write` (a short title and the " +
20
+ "markdown) and call `plan` action `exit` with the returned plan_path so the user can read the file and choose. " +
23
21
  `While planning, ${READ_ONLY_SUMMARY}.`;
24
22
 
25
- export function registerEnterTool(pi: ExtensionAPI, runtime: PlanRuntime): void {
26
- pi.registerTool({
27
- name: ENTER_TOOL,
28
- label: "Enter plan mode",
29
- description:
30
- "Enter read-only plan mode: investigate and design, then save the plan with write_plan and present it with " +
31
- "exit_plan_mode. Entering switches the session's active tool set to read-only and requires the user to confirm.",
32
- promptSnippet: "Ask the user to enter read-only plan mode.",
33
- promptGuidelines: [
34
- "Call enter_plan_mode before a non-trivial implementation task; skip it for small, obvious changes.",
35
- ],
36
- parameters: Type.Object({}),
37
- executionMode: "sequential",
38
- // readOnlyHint stays true: entering plan mode changes only the session's
39
- // tool set, never files or system state, so it needs no approval.
40
- annotations: { readOnlyHint: true, openWorldHint: false },
41
-
42
- async execute(_toolCallId, _params, _signal, _onUpdate, ctx) {
43
- if (runtime.isEnabled()) {
44
- return {
45
- content: [{ type: "text", text: "Already in plan mode." }],
46
- details: { entered: true, already: true } satisfies EnterPlanModeDetails,
47
- };
48
- }
49
- if (!ctx.hasUI) {
50
- return failedResult(
51
- "No interactive UI is available to confirm entering plan mode. Ask the user whether they want a plan.",
52
- { entered: false, unavailable: true } satisfies EnterPlanModeDetails,
53
- );
54
- }
23
+ export async function executeEnter(
24
+ runtime: PlanRuntime,
25
+ ctx: ExtensionToolContext,
26
+ ): Promise<AgentToolResult<EnterPlanModeDetails>> {
27
+ if (runtime.isEnabled()) {
28
+ return {
29
+ content: [{ type: "text", text: "Already in plan mode." }],
30
+ details: { action: "enter", entered: true, already: true } satisfies EnterPlanModeDetails,
31
+ };
32
+ }
33
+ if (!ctx.hasUI) {
34
+ return failedResult(
35
+ "No interactive UI is available to confirm entering plan mode. Ask the user whether they want a plan.",
36
+ { action: "enter", entered: false, unavailable: true } satisfies EnterPlanModeDetails,
37
+ );
38
+ }
55
39
 
56
- const consent = isRemoteTurn(ctx)
57
- ? remoteLanguage(ctx) === "zh"
58
- ? PLAN_CONSENT_SHORT_ZH
59
- : PLAN_CONSENT_SHORT
60
- : PLAN_CONSENT;
61
- const approved = await ctx.ui.confirm("Enter plan mode?", consent);
62
- if (!approved) {
63
- return {
64
- content: [{ type: "text", text: "Stayed in normal mode. Proceed, or ask the user how to continue." }],
65
- details: { entered: false } satisfies EnterPlanModeDetails,
66
- };
67
- }
68
- runtime.enable(ctx);
69
- if (isRemoteTurn(ctx)) {
70
- // The peer UI has no footer chip; say the mode and its exit in the chat.
71
- ctx.ui.notify("Plan mode on — write access is off. Say “exit plan mode” to leave without approving.", "info");
72
- }
73
- return {
74
- content: [{ type: "text", text: ENTERED_TEXT }],
75
- details: { entered: true } satisfies EnterPlanModeDetails,
76
- };
77
- },
40
+ const consent = isRemoteTurn(ctx)
41
+ ? remoteLanguage(ctx) === "zh"
42
+ ? PLAN_CONSENT_SHORT_ZH
43
+ : PLAN_CONSENT_SHORT
44
+ : PLAN_CONSENT;
45
+ const approved = await ctx.ui.confirm("Enter plan mode?", consent);
46
+ if (!approved) {
47
+ return {
48
+ content: [{ type: "text", text: "Stayed in normal mode. Proceed, or ask the user how to continue." }],
49
+ details: { action: "enter", entered: false } satisfies EnterPlanModeDetails,
50
+ };
51
+ }
52
+ runtime.enable(ctx);
53
+ if (isRemoteTurn(ctx)) {
54
+ // The peer UI has no footer chip; say the mode and its exit in the chat.
55
+ ctx.ui.notify("Plan mode on — write access is off. Say “exit plan mode” to leave without approving.", "info");
56
+ }
57
+ return {
58
+ content: [{ type: "text", text: ENTERED_TEXT }],
59
+ details: { action: "enter", entered: true } satisfies EnterPlanModeDetails,
60
+ };
61
+ }
78
62
 
79
- renderCall(_args, theme, context) {
80
- return callLine(theme, context?.lastComponent, ENTER_TOOL, "requested plan mode");
81
- },
63
+ export function callEnter(theme: Theme, last: unknown): ToolCallText {
64
+ return callLine(theme, last, PLAN_TOOL, "enter");
65
+ }
82
66
 
83
- renderResult(result, options, theme, context) {
84
- const text = reuseText(context?.lastComponent);
85
- const outcome = detailsOrLine(theme, result, options.expanded, toolDetails<EnterPlanModeDetails>(result.details));
86
- if ("line" in outcome) {
87
- text.setText(outcome.line);
88
- return text;
89
- }
90
- const details = outcome.details;
91
- if (details.unavailable) text.setText(paint(theme, "warn", "No UI to confirm plan mode"));
92
- else if (details.already) text.setText(paint(theme, "meta", "Already in plan mode"));
93
- else if (details.entered) text.setText(paint(theme, "ok", `${GLYPHS.check} Plan mode enabled`));
94
- else text.setText(paint(theme, "meta", "Stayed in normal mode"));
95
- return text;
96
- },
97
- });
67
+ export function textEnter(details: EnterPlanModeDetails, theme: Theme): string {
68
+ if (details.unavailable) return paint(theme, "warn", "No UI to confirm plan mode");
69
+ if (details.already) return paint(theme, "meta", "Already in plan mode");
70
+ if (details.entered) return paint(theme, "ok", `${GLYPHS.check} Plan mode enabled`);
71
+ return paint(theme, "meta", "Stayed in normal mode");
98
72
  }