@astralyn/pi 0.82.6 → 0.82.7

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 (183) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/README.md +1 -1
  3. package/dist/cli/session-picker.d.ts.map +1 -1
  4. package/dist/cli/session-picker.js +11 -13
  5. package/dist/cli/session-picker.js.map +1 -1
  6. package/dist/core/extensions/types.d.ts +4 -0
  7. package/dist/core/extensions/types.d.ts.map +1 -1
  8. package/dist/core/extensions/types.js.map +1 -1
  9. package/dist/core/session-manager.d.ts.map +1 -1
  10. package/dist/core/session-manager.js +2 -2
  11. package/dist/core/session-manager.js.map +1 -1
  12. package/dist/core/tools/bash.d.ts +1 -5
  13. package/dist/core/tools/bash.d.ts.map +1 -1
  14. package/dist/core/tools/bash.js +16 -41
  15. package/dist/core/tools/bash.js.map +1 -1
  16. package/dist/core/tools/edit.d.ts.map +1 -1
  17. package/dist/core/tools/edit.js +42 -11
  18. package/dist/core/tools/edit.js.map +1 -1
  19. package/dist/core/tools/find.d.ts.map +1 -1
  20. package/dist/core/tools/find.js +6 -7
  21. package/dist/core/tools/find.js.map +1 -1
  22. package/dist/core/tools/grep.d.ts.map +1 -1
  23. package/dist/core/tools/grep.js +17 -8
  24. package/dist/core/tools/grep.js.map +1 -1
  25. package/dist/core/tools/ls.d.ts.map +1 -1
  26. package/dist/core/tools/ls.js +3 -3
  27. package/dist/core/tools/ls.js.map +1 -1
  28. package/dist/core/tools/read.d.ts.map +1 -1
  29. package/dist/core/tools/read.js +4 -4
  30. package/dist/core/tools/read.js.map +1 -1
  31. package/dist/core/tools/render-utils.d.ts +7 -0
  32. package/dist/core/tools/render-utils.d.ts.map +1 -1
  33. package/dist/core/tools/render-utils.js +11 -0
  34. package/dist/core/tools/render-utils.js.map +1 -1
  35. package/dist/core/tools/write.d.ts.map +1 -1
  36. package/dist/core/tools/write.js +2 -3
  37. package/dist/core/tools/write.js.map +1 -1
  38. package/dist/extensions/deepwiki/render.d.ts.map +1 -1
  39. package/dist/extensions/deepwiki/render.js +34 -22
  40. package/dist/extensions/deepwiki/render.js.map +1 -1
  41. package/dist/extensions/plan/constants.d.ts +32 -4
  42. package/dist/extensions/plan/constants.d.ts.map +1 -1
  43. package/dist/extensions/plan/constants.js +37 -6
  44. package/dist/extensions/plan/constants.js.map +1 -1
  45. package/dist/extensions/plan/index.d.ts +27 -0
  46. package/dist/extensions/plan/index.d.ts.map +1 -1
  47. package/dist/extensions/plan/index.js +150 -44
  48. package/dist/extensions/plan/index.js.map +1 -1
  49. package/dist/extensions/plan/schema.d.ts +5 -0
  50. package/dist/extensions/plan/schema.d.ts.map +1 -1
  51. package/dist/extensions/plan/schema.js.map +1 -1
  52. package/dist/extensions/plan/state.d.ts +11 -2
  53. package/dist/extensions/plan/state.d.ts.map +1 -1
  54. package/dist/extensions/plan/state.js +31 -9
  55. package/dist/extensions/plan/state.js.map +1 -1
  56. package/dist/extensions/plan/storage.d.ts +11 -3
  57. package/dist/extensions/plan/storage.d.ts.map +1 -1
  58. package/dist/extensions/plan/storage.js +45 -20
  59. package/dist/extensions/plan/storage.js.map +1 -1
  60. package/dist/extensions/plan/view.d.ts +15 -7
  61. package/dist/extensions/plan/view.d.ts.map +1 -1
  62. package/dist/extensions/plan/view.js +91 -16
  63. package/dist/extensions/plan/view.js.map +1 -1
  64. package/dist/extensions/question/dialog.d.ts.map +1 -1
  65. package/dist/extensions/question/dialog.js +29 -10
  66. package/dist/extensions/question/dialog.js.map +1 -1
  67. package/dist/extensions/question/index.d.ts.map +1 -1
  68. package/dist/extensions/question/index.js +7 -2
  69. package/dist/extensions/question/index.js.map +1 -1
  70. package/dist/extensions/question/render.d.ts +4 -8
  71. package/dist/extensions/question/render.d.ts.map +1 -1
  72. package/dist/extensions/question/render.js +202 -29
  73. package/dist/extensions/question/render.js.map +1 -1
  74. package/dist/extensions/question/results.d.ts.map +1 -1
  75. package/dist/extensions/question/results.js +1 -0
  76. package/dist/extensions/question/results.js.map +1 -1
  77. package/dist/extensions/question/schema.d.ts.map +1 -1
  78. package/dist/extensions/question/schema.js +2 -2
  79. package/dist/extensions/question/schema.js.map +1 -1
  80. package/dist/extensions/question/types.d.ts +1 -0
  81. package/dist/extensions/question/types.d.ts.map +1 -1
  82. package/dist/extensions/question/types.js +1 -1
  83. package/dist/extensions/question/types.js.map +1 -1
  84. package/dist/extensions/router/stream.d.ts +1 -0
  85. package/dist/extensions/router/stream.d.ts.map +1 -1
  86. package/dist/extensions/router/stream.js +44 -13
  87. package/dist/extensions/router/stream.js.map +1 -1
  88. package/dist/extensions/subagent/constants.d.ts +1 -0
  89. package/dist/extensions/subagent/constants.d.ts.map +1 -1
  90. package/dist/extensions/subagent/constants.js +1 -0
  91. package/dist/extensions/subagent/constants.js.map +1 -1
  92. package/dist/extensions/subagent/index.d.ts.map +1 -1
  93. package/dist/extensions/subagent/index.js +2 -0
  94. package/dist/extensions/subagent/index.js.map +1 -1
  95. package/dist/extensions/subagent/render.d.ts.map +1 -1
  96. package/dist/extensions/subagent/render.js +121 -48
  97. package/dist/extensions/subagent/render.js.map +1 -1
  98. package/dist/extensions/subagent/retry.d.ts +17 -0
  99. package/dist/extensions/subagent/retry.d.ts.map +1 -0
  100. package/dist/extensions/subagent/retry.js +41 -0
  101. package/dist/extensions/subagent/retry.js.map +1 -0
  102. package/dist/extensions/subagent/runner.d.ts.map +1 -1
  103. package/dist/extensions/subagent/runner.js +33 -5
  104. package/dist/extensions/subagent/runner.js.map +1 -1
  105. package/dist/extensions/subagent/sdk-runner.d.ts +6 -0
  106. package/dist/extensions/subagent/sdk-runner.d.ts.map +1 -1
  107. package/dist/extensions/subagent/sdk-runner.js +46 -24
  108. package/dist/extensions/subagent/sdk-runner.js.map +1 -1
  109. package/dist/extensions/subagent/types.d.ts +7 -0
  110. package/dist/extensions/subagent/types.d.ts.map +1 -1
  111. package/dist/extensions/subagent/types.js.map +1 -1
  112. package/dist/extensions/todo/index.d.ts.map +1 -1
  113. package/dist/extensions/todo/index.js +1 -1
  114. package/dist/extensions/todo/index.js.map +1 -1
  115. package/dist/extensions/todo/state.d.ts.map +1 -1
  116. package/dist/extensions/todo/state.js +10 -2
  117. package/dist/extensions/todo/state.js.map +1 -1
  118. package/dist/extensions/todo/view.d.ts +1 -1
  119. package/dist/extensions/todo/view.d.ts.map +1 -1
  120. package/dist/extensions/todo/view.js +179 -34
  121. package/dist/extensions/todo/view.js.map +1 -1
  122. package/dist/migrations.d.ts.map +1 -1
  123. package/dist/migrations.js +2 -1
  124. package/dist/migrations.js.map +1 -1
  125. package/dist/modes/interactive/components/armin.d.ts.map +1 -1
  126. package/dist/modes/interactive/components/armin.js +5 -0
  127. package/dist/modes/interactive/components/armin.js.map +1 -1
  128. package/dist/modes/interactive/components/bash-execution.d.ts +1 -0
  129. package/dist/modes/interactive/components/bash-execution.d.ts.map +1 -1
  130. package/dist/modes/interactive/components/bash-execution.js +4 -2
  131. package/dist/modes/interactive/components/bash-execution.js.map +1 -1
  132. package/dist/modes/interactive/components/countdown-timer.d.ts +13 -6
  133. package/dist/modes/interactive/components/countdown-timer.d.ts.map +1 -1
  134. package/dist/modes/interactive/components/countdown-timer.js +54 -17
  135. package/dist/modes/interactive/components/countdown-timer.js.map +1 -1
  136. package/dist/modes/interactive/components/extension-selector.d.ts.map +1 -1
  137. package/dist/modes/interactive/components/extension-selector.js +10 -6
  138. package/dist/modes/interactive/components/extension-selector.js.map +1 -1
  139. package/dist/modes/interactive/components/keybinding-hints.d.ts +11 -1
  140. package/dist/modes/interactive/components/keybinding-hints.d.ts.map +1 -1
  141. package/dist/modes/interactive/components/keybinding-hints.js +56 -0
  142. package/dist/modes/interactive/components/keybinding-hints.js.map +1 -1
  143. package/dist/modes/interactive/components/session-selector.d.ts +3 -0
  144. package/dist/modes/interactive/components/session-selector.d.ts.map +1 -1
  145. package/dist/modes/interactive/components/session-selector.js +52 -2
  146. package/dist/modes/interactive/components/session-selector.js.map +1 -1
  147. package/dist/modes/interactive/components/tool-execution.d.ts +10 -0
  148. package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  149. package/dist/modes/interactive/components/tool-execution.js +133 -18
  150. package/dist/modes/interactive/components/tool-execution.js.map +1 -1
  151. package/dist/modes/interactive/components/tool-group.d.ts +1 -0
  152. package/dist/modes/interactive/components/tool-group.d.ts.map +1 -1
  153. package/dist/modes/interactive/components/tool-group.js +5 -0
  154. package/dist/modes/interactive/components/tool-group.js.map +1 -1
  155. package/dist/modes/interactive/components/tree-selector.d.ts +10 -1
  156. package/dist/modes/interactive/components/tree-selector.d.ts.map +1 -1
  157. package/dist/modes/interactive/components/tree-selector.js +55 -2
  158. package/dist/modes/interactive/components/tree-selector.js.map +1 -1
  159. package/dist/modes/interactive/interactive-mode.d.ts +5 -0
  160. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  161. package/dist/modes/interactive/interactive-mode.js +72 -18
  162. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  163. package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
  164. package/dist/modes/interactive/theme/theme.js +3 -2
  165. package/dist/modes/interactive/theme/theme.js.map +1 -1
  166. package/dist/utils/paths.d.ts +6 -0
  167. package/dist/utils/paths.d.ts.map +1 -1
  168. package/dist/utils/paths.js +8 -0
  169. package/dist/utils/paths.js.map +1 -1
  170. package/docs/bundled/README.md +1 -0
  171. package/docs/bundled/extensions/deepwiki.md +1 -1
  172. package/docs/bundled/extensions/plan.md +10 -9
  173. package/docs/bundled/extensions/question.md +7 -5
  174. package/docs/bundled/extensions/router.md +3 -4
  175. package/docs/bundled/extensions/statusline.md +0 -1
  176. package/docs/bundled/extensions/subagent.md +4 -3
  177. package/docs/bundled/extensions/todo.md +1 -1
  178. package/docs/bundled/tool-presentation.md +17 -16
  179. package/docs/extensions.md +20 -5
  180. package/docs/rpc.md +1 -1
  181. package/docs/usage.md +1 -1
  182. package/npm-shrinkwrap.json +6 -6
  183. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/extensions/plan/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,OAAO,EAAE,IAAI,EAAE,MAAM,wBAAwB,CAAC;AAO9C,OAAO,EACN,mBAAmB,EACnB,qBAAqB,EACrB,sBAAsB,EACtB,2BAA2B,EAC3B,wBAAwB,EACxB,0BAA0B,EAC1B,oBAAoB,EACpB,mBAAmB,EACnB,oBAAoB,EACpB,YAAY,EACZ,kBAAkB,EAClB,kBAAkB,EAClB,iBAAiB,EACjB,oBAAoB,EACpB,eAAe,EACf,cAAc,EACd,eAAe,GACf,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAA6C,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAC9F,OAAO,EACN,cAAc,EACd,kBAAkB,EAClB,gBAAgB,EAChB,YAAY,EACZ,gBAAgB,EAChB,oBAAoB,EACpB,oBAAoB,GACpB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,EAAE,sBAAsB,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,MAAM,WAAW,CAAC;AAQ7F,SAAS,mBAAmB,CAAC,OAAuB,EAAU;IAC7D,MAAM,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,MAAM,GAAG,oBAAoB,CAAC;IAC7D,MAAM,IAAI,GAAG,SAAS;QACrB,CAAC,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,oBAAoB,CAAC,iDAA+C,OAAO,CAAC,QAAQ,GAAG;QAClH,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC;IAChB,OAAO,8BAA8B,OAAO,CAAC,KAAK,eAAe,OAAO,CAAC,QAAQ;;EAEhF,IAAI;;oIAE8H,CAAC;AAAA,CACpI;AAED,MAAM,CAAC,OAAO,UAAU,IAAI,CAAC,EAAgB,EAAQ;IACpD,IAAI,QAAQ,GAAG,KAAK,CAAC;IACrB,IAAI,cAA0C,CAAC;IAE/C,EAAE,CAAC,YAAY,CAAC,cAAc,EAAE;QAC/B,WAAW,EAAE,yCAAyC;QACtD,IAAI,EAAE,SAAS;QACf,OAAO,EAAE,KAAK;KACd,CAAC,CAAC;IAEH,sFAAsF;IACtF,SAAS,cAAc,GAAa;QACnC,MAAM,MAAM,GAAG,EAAE,CAAC,cAAc,EAAE,CAAC;QACnC,MAAM,OAAO,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,kBAAkB,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC;QACtE,EAAE,CAAC,cAAc,CAAC,CAAC,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,kBAAkB,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,EAAE,mBAAmB,CAAC,CAAC,CAAC,CAAC,CAAC;QAClH,OAAO,OAAO,CAAC;IAAA,CACf;IAED,kFAAkF;IAClF,SAAS,YAAY,CAAC,YAAsB,EAAQ;QACnD,MAAM,MAAM,GAAG,EAAE,CAAC,cAAc,EAAE,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,KAAK,mBAAmB,CAAC,CAAC;QAClF,EAAE,CAAC,cAAc,CAAC,CAAC,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,MAAM,EAAE,GAAG,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC;IAAA,CAC9D;IAED,SAAS,OAAO,GAAS;QACxB,EAAE,CAAC,WAAW,CAAC,eAAe,EAAE,YAAY,EAAE,CAAC,CAAC;IAAA,CAChD;IAED,SAAS,YAAY,CAAC,GAAqB,EAAQ;QAClD,MAAM,KAAK,GAAG,YAAY,EAAE,CAAC;QAC7B,GAAG,CAAC,EAAE,CAAC,SAAS,CAAC,eAAe,EAAE,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC,QAAQ,EAAE,WAAW,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;IAAA,CACvG;IAED,SAAS,aAAa,CAAC,GAAqB,EAAQ;QACnD,MAAM,KAAK,GAAG,YAAY,EAAE,CAAC;QAC7B,IAAI,KAAK,CAAC,QAAQ;YAAE,OAAO;QAC3B,MAAM,YAAY,GAAG,cAAc,EAAE,CAAC;QACtC,gBAAgB,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,YAAY,EAAE,eAAe,EAAE,KAAK,EAAE,SAAS,EAAE,KAAK,CAAC,SAAS,EAAE,CAAC,CAAC;QACvG,OAAO,EAAE,CAAC;QACV,YAAY,CAAC,GAAG,CAAC,CAAC;QAClB,GAAG,CAAC,EAAE,CAAC,MAAM,CAAC,kEAAkE,EAAE,MAAM,CAAC,CAAC;IAAA,CAC1F;IAED,SAAS,YAAY,CAAC,GAAqB,EAAE,UAAmB,EAAQ;QACvE,MAAM,KAAK,GAAG,YAAY,EAAE,CAAC;QAC7B,YAAY,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC;QACjC,gBAAgB,CAAC,EAAE,GAAG,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,eAAe,EAAE,KAAK,EAAE,YAAY,EAAE,EAAE,EAAE,CAAC,CAAC;QAC1F,OAAO,EAAE,CAAC;QACV,YAAY,CAAC,GAAG,CAAC,CAAC;QAClB,IAAI,UAAU;YAAE,GAAG,CAAC,EAAE,CAAC,MAAM,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;IAAA,CAClD;IAED,kFAAkF;IAClF,SAAS,gBAAgB,CAAC,GAAqB,EAAE,MAAsB,EAAE,KAAa,EAAU;QAC/F,MAAM,QAAQ,GAAG,YAAY,CAAC;YAC7B,SAAS,EAAE,GAAG,CAAC,cAAc,CAAC,YAAY,EAAE;YAC5C,KAAK;YACL,IAAI,EAAE,MAAM,CAAC,IAAI;YACjB,GAAG,EAAE,GAAG,CAAC,GAAG;YACZ,OAAO,EAAE,MAAM,CAAC,OAAO;SACvB,CAAC,CAAC;QACH,MAAM,KAAK,GAAG,YAAY,EAAE,CAAC;QAC7B,gBAAgB,CAAC,EAAE,GAAG,KAAK,EAAE,SAAS,EAAE,CAAC,GAAG,KAAK,CAAC,SAAS,EAAE,QAAQ,CAAC,EAAE,CAAC,CAAC;QAC1E,IAAI,CAAC,EAAE,CAAC,cAAc,EAAE;YAAE,EAAE,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC;QACnD,OAAO,QAAQ,CAAC;IAAA,CAChB;IAED;;;;OAIG;IACH,SAAS,cAAc,CAAC,GAAqB,EAAE,OAAiC,EAAQ;QACvF,oBAAoB,CAAC,GAAG,CAAC,cAAc,CAAC,YAAY,EAAE,CAAC,CAAC;QACxD,MAAM,MAAM,GAAG,YAAY,EAAE,CAAC;QAC9B,MAAM,QAAQ,GAAG,oBAAoB,CAAC,GAAG,CAAC,CAAC;QAC3C,MAAM,IAAI,GAAG,QAAQ,IAAI,cAAc,CAAC,gBAAgB,CAAC,CAAC;QAC1D,IAAI,CAAC,QAAQ,IAAI,OAAO,EAAE,SAAS,IAAI,EAAE,CAAC,OAAO,CAAC,cAAc,CAAC,KAAK,IAAI,EAAE,CAAC;YAC5E,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC;QACtB,CAAC;QAED,IAAI,kBAAkB,GAAG,KAAK,CAAC;QAC/B,IAAI,IAAI,CAAC,eAAe,EAAE,CAAC;YAC1B,IAAI,CAAC,QAAQ,GAAG,KAAK,CAAC;YACtB,IAAI,CAAC,eAAe,GAAG,KAAK,CAAC;YAC7B,kBAAkB,GAAG,IAAI,CAAC;YAC1B,cAAc,GAAG,SAAS,CAAC;QAC5B,CAAC;QAED,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YACnB,mEAAmE;YACnE,6DAA6D;YAC7D,IAAI,CAAC,YAAY,GAAG,cAAc,EAAE,CAAC;QACtC,CAAC;aAAM,CAAC;YACP,IAAI,EAAE,CAAC,cAAc,EAAE,CAAC,QAAQ,CAAC,mBAAmB,CAAC,EAAE,CAAC;gBACvD,YAAY,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;YACzE,CAAC;YACD,IAAI,CAAC,YAAY,GAAG,EAAE,CAAC;QACxB,CAAC;QAED,gBAAgB,CAAC,IAAI,CAAC,CAAC;QACvB,IAAI,kBAAkB,EAAE,CAAC;YACxB,OAAO,EAAE,CAAC;YACV,GAAG,CAAC,EAAE,CAAC,MAAM,CAAC,yEAAyE,EAAE,SAAS,CAAC,CAAC;QACrG,CAAC;QACD,YAAY,CAAC,GAAG,CAAC,CAAC;IAAA,CAClB;IAED,EAAE,CAAC,YAAY,CAAC;QACf,IAAI,EAAE,mBAAmB;QACzB,KAAK,EAAE,oBAAoB;QAC3B,WAAW,EAAE,0BAA0B;QACvC,aAAa,EAAE,wBAAwB;QACvC,gBAAgB,EAAE,2BAA2B;QAC7C,UAAU,EAAE,oBAAoB;QAChC,aAAa,EAAE,YAAiC;QAEhD,KAAK,CAAC,OAAO,CAAC,WAAW,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,GAAG,EAA6C;YACrG,oBAAoB,CAAC,GAAG,CAAC,cAAc,CAAC,YAAY,EAAE,CAAC,CAAC;YACxD,MAAM,KAAK,GAAG,YAAY,EAAE,CAAC;YAC7B,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,IAAI,EAAE,IAAI,MAAM,CAAC;YAE5C,IAAI,CAAC,KAAK,CAAC,QAAQ,IAAI,KAAK,CAAC,eAAe,EAAE,CAAC;gBAC9C,OAAO;oBACN,OAAO,EAAE;wBACR,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,6EAA6E,EAAE;qBACrG;oBACD,OAAO,EAAE,EAAE,QAAQ,EAAE,WAAW,EAAE,KAAK,EAAE;iBACzC,CAAC;YACH,CAAC;YAED,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC;gBAChB,uEAAqE;gBACrE,kEAAkE;gBAClE,MAAM,QAAQ,GAAG,gBAAgB,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,CAAC,CAAC;gBACtD,YAAY,CAAC,GAAG,CAAC,CAAC;gBAClB,OAAO;oBACN,OAAO,EAAE;wBACR;4BACC,IAAI,EAAE,MAAM;4BACZ,IAAI,EAAE,iBAAiB,QAAQ,qIAAqI;yBACpK;qBACD;oBACD,OAAO,EAAE,EAAE,QAAQ,EAAE,SAAS,EAAE,KAAK,EAAE,QAAQ,EAAE;iBACjD,CAAC;YACH,CAAC;YAED,IAAI,MAA0B,CAAC;YAC/B,QAAQ,GAAG,IAAI,CAAC;YAChB,IAAI,CAAC;gBACJ,MAAM,GAAG,MAAM,GAAG,CAAC,EAAE,CAAC,MAAM,CAAC,iBAAiB,KAAK,IAAI,EAAE,sBAAsB,EAAE;oBAChF,MAAM;oBACN,QAAQ,EAAE,sBAAsB,CAAC,GAAG,CAAC;oBACrC,UAAU,EAAE,qBAAqB;iBACjC,CAAC,CAAC;YACJ,CAAC;oBAAS,CAAC;gBACV,QAAQ,GAAG,KAAK,CAAC;YAClB,CAAC;YAED,IAAI,MAAM,EAAE,OAAO,EAAE,CAAC;gBACrB,8EAA8E;gBAC9E,MAAM,IAAI,KAAK,CAAC,2DAA2D,CAAC,CAAC;YAC9E,CAAC;YAED,IAAI,MAAM,KAAK,YAAY,EAAE,CAAC;gBAC7B,MAAM,QAAQ,GAAG,gBAAgB,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,CAAC,CAAC;gBACtD,YAAY,CAAC,GAAG,EAAE,6CAA6C,CAAC,CAAC;gBACjE,OAAO;oBACN,OAAO,EAAE;wBACR;4BACC,IAAI,EAAE,MAAM;4BACZ,IAAI,EAAE,8BAA8B,QAAQ,6LAA2L;yBACvO;qBACD;oBACD,OAAO,EAAE,EAAE,QAAQ,EAAE,SAAS,EAAE,KAAK,EAAE,QAAQ,EAAE;iBACjD,CAAC;YACH,CAAC;YAED,IAAI,MAAM,KAAK,oBAAoB,EAAE,CAAC;gBACrC,MAAM,QAAQ,GAAG,gBAAgB,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,CAAC,CAAC;gBACtD,mEAAmE;gBACnE,oEAAoE;gBACpE,gBAAgB,CAAC,EAAE,GAAG,YAAY,EAAE,EAAE,eAAe,EAAE,IAAI,EAAE,CAAC,CAAC;gBAC/D,OAAO,EAAE,CAAC;gBACV,cAAc,GAAG,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,QAAQ,EAAE,CAAC;gBACxD,OAAO;oBACN,OAAO,EAAE;wBACR;4BACC,IAAI,EAAE,MAAM;4BACZ,IAAI,EAAE,8BAA8B,QAAQ,uGAAqG;yBACjJ;qBACD;oBACD,OAAO,EAAE,EAAE,QAAQ,EAAE,mBAAmB,EAAE,KAAK,EAAE,QAAQ,EAAE;oBAC3D,SAAS,EAAE,IAAI;iBACf,CAAC;YACH,CAAC;YAED,uEAAuE;YACvE,sCAAsC;YACtC,IAAI,QAA4B,CAAC;YACjC,IAAI,MAAM,KAAK,kBAAkB,EAAE,CAAC;gBACnC,QAAQ;oBACP,GAAG,CAAC,IAAI,KAAK,KAAK;wBACjB,CAAC,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,MAAM,CAAC,mCAAmC,EAAE,EAAE,CAAC;wBAC9D,CAAC,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,KAAK,CAAC,mCAAmC,CAAC,CAAC;YAC7D,CAAC;YACD,MAAM,eAAe,GAAG,QAAQ,EAAE,IAAI,EAAE,CAAC;YACzC,OAAO;gBACN,OAAO,EAAE;oBACR;wBACC,IAAI,EAAE,MAAM;wBACZ,IAAI,EAAE,eAAe;4BACpB,CAAC,CAAC,+CAA+C,eAAe,EAAE;4BAClE,CAAC,CAAC,4FAA4F;qBAC/F;iBACD;gBACD,OAAO,EAAE,EAAE,QAAQ,EAAE,cAAc,EAAE,KAAK,EAAE;aAC5C,CAAC;QAAA,CACF;QAED,UAAU,CAAC,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE;YAChC,MAAM,IAAI,GAAI,OAAO,CAAC,aAAkC,IAAI,IAAI,IAAI,CAAC,EAAE,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;YAC/E,IAAI,CAAC,OAAO,CAAC,kBAAkB,CAAC,IAAI,EAAE,KAAK,EAAE,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC;YAChE,OAAO,IAAI,CAAC;QAAA,CACZ;QAED,YAAY,CAAC,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE,OAAO,EAAE;YAC9C,MAAM,IAAI,GAAI,OAAO,CAAC,aAAkC,IAAI,IAAI,IAAI,CAAC,EAAE,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;YAC/E,IAAI,CAAC,OAAO,CAAC,oBAAoB,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC,CAAC;YAClD,OAAO,IAAI,CAAC;QAAA,CACZ;KACD,CAAC,CAAC;IAEH,EAAE,CAAC,eAAe,CAAC,iBAAiB,EAAE;QACrC,WAAW,EAAE,+DAA+D;QAC5E,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE,CAAC;YAC9B,oBAAoB,CAAC,GAAG,CAAC,cAAc,CAAC,YAAY,EAAE,CAAC,CAAC;YACxD,MAAM,KAAK,GAAG,YAAY,EAAE,CAAC;YAC7B,IAAI,QAAQ,EAAE,CAAC;gBACd,GAAG,CAAC,EAAE,CAAC,MAAM,CAAC,0DAAwD,EAAE,SAAS,CAAC,CAAC;gBACnF,OAAO;YACR,CAAC;YACD,IAAI,KAAK,CAAC,eAAe,EAAE,CAAC;gBAC3B,GAAG,CAAC,EAAE,CAAC,MAAM,CAAC,4DAA4D,EAAE,SAAS,CAAC,CAAC;gBACvF,OAAO;YACR,CAAC;YACD,IAAI,KAAK,CAAC,QAAQ,EAAE,CAAC;gBACpB,YAAY,CAAC,GAAG,EAAE,gDAAgD,CAAC,CAAC;YACrE,CAAC;iBAAM,CAAC;gBACP,aAAa,CAAC,GAAG,CAAC,CAAC;YACpB,CAAC;QAAA,CACD;KACD,CAAC,CAAC;IAEH,2EAA2E;IAC3E,wEAAwE;IACxE,sBAAsB;IACtB,EAAE,CAAC,EAAE,CAAC,WAAW,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE,CAAC;QACxC,oBAAoB,CAAC,GAAG,CAAC,cAAc,CAAC,YAAY,EAAE,CAAC,CAAC;QACxD,MAAM,KAAK,GAAG,YAAY,EAAE,CAAC;QAC7B,IAAI,CAAC,KAAK,CAAC,QAAQ,IAAI,CAAC,kBAAkB,CAAC,GAAG,CAAC,KAAK,CAAC,QAAQ,CAAC;YAAE,OAAO;QACvE,OAAO;YACN,KAAK,EAAE,IAAI;YACX,MAAM,EAAE,cAAc,KAAK,CAAC,QAAQ,8DAA8D,mBAAmB,mCAAmC;SACxJ,CAAC;IAAA,CACF,CAAC,CAAC;IAEH,EAAE,CAAC,EAAE,CAAC,oBAAoB,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE,CAAC;QACjD,oBAAoB,CAAC,GAAG,CAAC,cAAc,CAAC,YAAY,EAAE,CAAC,CAAC;QACxD,MAAM,KAAK,GAAG,YAAY,EAAE,CAAC;QAC7B,IAAI,CAAC,KAAK,CAAC,QAAQ;YAAE,OAAO;QAC5B,OAAO,EAAE,YAAY,EAAE,GAAG,KAAK,CAAC,YAAY,OAAO,mBAAmB,CAAC,KAAK,CAAC,SAAS,CAAC,EAAE,EAAE,CAAC;IAAA,CAC5F,CAAC,CAAC;IAEH,0EAA0E;IAC1E,6EAA6E;IAC7E,EAAE,CAAC,EAAE,CAAC,eAAe,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,EAAE,CAAC;QAC7C,oBAAoB,CAAC,GAAG,CAAC,cAAc,CAAC,YAAY,EAAE,CAAC,CAAC;QACxD,IAAI,CAAC,YAAY,EAAE,CAAC,eAAe,IAAI,CAAC,cAAc;YAAE,OAAO;QAC/D,MAAM,OAAO,GAAG,cAAc,CAAC;QAC/B,cAAc,GAAG,SAAS,CAAC;QAC3B,GAAG,CAAC,OAAO,CAAC;YACX,kBAAkB,EAAE,qDAAqD,OAAO,CAAC,QAAQ,0IAA0I;YACnO,UAAU,EAAE,GAAG,EAAE,CAAC;gBACjB,YAAY,CAAC,GAAG,EAAE,wCAAwC,CAAC,CAAC;gBAC5D,EAAE,CAAC,eAAe,CAAC,mBAAmB,CAAC,OAAO,CAAC,CAAC,CAAC;YAAA,CACjD;YACD,OAAO,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC;gBACnB,2EAA2E;gBAC3E,YAAY,CAAC,GAAG,EAAE,sBAAsB,KAAK,CAAC,OAAO,yCAAyC,CAAC,CAAC;gBAChG,EAAE,CAAC,eAAe,CAAC,mBAAmB,CAAC,OAAO,CAAC,CAAC,CAAC;YAAA,CACjD;SACD,CAAC,CAAC;IAAA,CACH,CAAC,CAAC;IAEH,EAAE,CAAC,EAAE,CAAC,eAAe,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,EAAE,CAAC;QAC7C,cAAc,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAAA,CACzC,CAAC,CAAC;IAEH,EAAE,CAAC,EAAE,CAAC,cAAc,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,EAAE,CAAC;QAC5C,cAAc,CAAC,GAAG,CAAC,CAAC;IAAA,CACpB,CAAC,CAAC;IAEH,EAAE,CAAC,EAAE,CAAC,kBAAkB,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,EAAE,CAAC;QAChD,cAAc,GAAG,SAAS,CAAC;QAC3B,kBAAkB,CAAC,GAAG,CAAC,cAAc,CAAC,YAAY,EAAE,CAAC,CAAC;IAAA,CACtD,CAAC,CAAC;AAAA,CACH","sourcesContent":["/**\n * plan — Claude-Code-style plan mode: read-only exploration and interview,\n * then a user-approved exit that saves the plan and optionally compacts.\n *\n * Shape:\n * - Entry (/plan or --plan) removes write-capable tools (edit/write/bash/\n * subagent) and activates exit_plan; a tool_call guard covers requests\n * already in flight before the tool-set change lands.\n * - The plan text travels as an exit_plan parameter, so the model needs no\n * write access to produce the plan file.\n * - \"Compact, then execute\" keeps plan mode active until compaction completes\n * (tools are restored in onComplete) and terminates the run via the tool\n * result, so there is no window where the model holds full context plus\n * write tools. The kickoff message embeds the plan itself — continuation\n * never depends on the compaction summary preserving a path.\n * - State is conversation-backed via custom entries and branch replay (same\n * pattern as todo): /reload, /tree, resume, and fork all restore.\n */\nimport { Text } from \"@earendil-works/pi-tui\";\nimport type {\n\tAgentToolResult,\n\tExtensionAPI,\n\tExtensionContext,\n\tToolExecutionMode,\n} from \"../../core/extensions/types.ts\";\nimport {\n\tbuildPlanModePrompt,\n\tEXIT_PLAN_CANCEL_HINT,\n\tEXIT_PLAN_MENU_OPTIONS,\n\tEXIT_PLAN_PROMPT_GUIDELINES,\n\tEXIT_PLAN_PROMPT_SNIPPET,\n\tEXIT_PLAN_TOOL_DESCRIPTION,\n\tEXIT_PLAN_TOOL_LABEL,\n\tEXIT_PLAN_TOOL_NAME,\n\tMENU_COMPACT_EXECUTE,\n\tMENU_EXECUTE,\n\tMENU_KEEP_PLANNING,\n\tPLAN_BLOCKED_TOOLS,\n\tPLAN_COMMAND_NAME,\n\tPLAN_EMBED_MAX_CHARS,\n\tPLAN_ENTRY_TYPE,\n\tPLAN_FLAG_NAME,\n\tPLAN_STATUS_KEY,\n} from \"./constants.ts\";\nimport { type ExitPlanDetails, type ExitPlanParams, ExitPlanParamsSchema } from \"./schema.ts\";\nimport {\n\tclonePlanState,\n\tdisposePlanSession,\n\tEMPTY_PLAN_STATE,\n\tgetPlanState,\n\treplacePlanState,\n\treplayPlanFromBranch,\n\tsetActivePlanSession,\n} from \"./state.ts\";\nimport { savePlanFile } from \"./storage.ts\";\nimport { formatApprovalSubtitle, formatExitPlanCall, formatExitPlanResult } from \"./view.ts\";\n\ninterface PendingCompact {\n\ttitle: string;\n\tplan: string;\n\tplanPath: string;\n}\n\nfunction buildKickoffMessage(pending: PendingCompact): string {\n\tconst truncated = pending.plan.length > PLAN_EMBED_MAX_CHARS;\n\tconst body = truncated\n\t\t? `${pending.plan.slice(0, PLAN_EMBED_MAX_CHARS)}\\n\\n[Plan truncated — read the full plan at ${pending.planPath}]`\n\t\t: pending.plan;\n\treturn `Execute the approved plan \"${pending.title}\" (saved at ${pending.planPath}).\n\n${body}\n\nStart now. Track multi-step work with the todo tool, and report back instead of improvising if the plan turns out to need revision.`;\n}\n\nexport default function plan(pi: ExtensionAPI): void {\n\tlet menuOpen = false;\n\tlet pendingCompact: PendingCompact | undefined;\n\n\tpi.registerFlag(PLAN_FLAG_NAME, {\n\t\tdescription: \"Start in plan mode (read-only planning)\",\n\t\ttype: \"boolean\",\n\t\tdefault: false,\n\t});\n\n\t/** Remove blocked tools, activate exit_plan, and return what was actually removed. */\n\tfunction applyPlanTools(): string[] {\n\t\tconst active = pi.getActiveTools();\n\t\tconst removed = active.filter((name) => PLAN_BLOCKED_TOOLS.has(name));\n\t\tpi.setActiveTools([...new Set([...active.filter((name) => !PLAN_BLOCKED_TOOLS.has(name)), EXIT_PLAN_TOOL_NAME])]);\n\t\treturn removed;\n\t}\n\n\t/** Inverse of applyPlanTools: drop exit_plan, re-add exactly what was removed. */\n\tfunction restoreTools(removedTools: string[]): void {\n\t\tconst active = pi.getActiveTools().filter((name) => name !== EXIT_PLAN_TOOL_NAME);\n\t\tpi.setActiveTools([...new Set([...active, ...removedTools])]);\n\t}\n\n\tfunction persist(): void {\n\t\tpi.appendEntry(PLAN_ENTRY_TYPE, getPlanState());\n\t}\n\n\tfunction updateStatus(ctx: ExtensionContext): void {\n\t\tconst state = getPlanState();\n\t\tctx.ui.setStatus(PLAN_STATUS_KEY, state.planning ? ctx.ui.theme.fg(\"accent\", \"Plan Mode\") : undefined);\n\t}\n\n\tfunction enterPlanMode(ctx: ExtensionContext): void {\n\t\tconst state = getPlanState();\n\t\tif (state.planning) return;\n\t\tconst removedTools = applyPlanTools();\n\t\treplacePlanState({ planning: true, removedTools, awaitingCompact: false, planFiles: state.planFiles });\n\t\tpersist();\n\t\tupdateStatus(ctx);\n\t\tctx.ui.notify(\"Plan mode enabled: edit, write, bash, and subagent are disabled.\", \"info\");\n\t}\n\n\tfunction exitPlanMode(ctx: ExtensionContext, notifyText?: string): void {\n\t\tconst state = getPlanState();\n\t\trestoreTools(state.removedTools);\n\t\treplacePlanState({ ...state, planning: false, awaitingCompact: false, removedTools: [] });\n\t\tpersist();\n\t\tupdateStatus(ctx);\n\t\tif (notifyText) ctx.ui.notify(notifyText, \"info\");\n\t}\n\n\t/** Save the approved plan, index it in state, and name the session if unnamed. */\n\tfunction saveApprovedPlan(ctx: ExtensionContext, params: ExitPlanParams, title: string): string {\n\t\tconst planPath = savePlanFile({\n\t\t\tsessionId: ctx.sessionManager.getSessionId(),\n\t\t\ttitle,\n\t\t\tplan: params.plan,\n\t\t\tcwd: ctx.cwd,\n\t\t\trevises: params.revises,\n\t\t});\n\t\tconst state = getPlanState();\n\t\treplacePlanState({ ...state, planFiles: [...state.planFiles, planPath] });\n\t\tif (!pi.getSessionName()) pi.setSessionName(title);\n\t\treturn planPath;\n\t}\n\n\t/**\n\t * Rebuild state from the current branch and reconcile the live tool set.\n\t * An interrupted compact-and-execute approval (restart, resume, branch\n\t * switch) degrades to the keep-context exit so the approval is honored.\n\t */\n\tfunction syncFromBranch(ctx: ExtensionContext, options?: { applyFlag?: boolean }): void {\n\t\tsetActivePlanSession(ctx.sessionManager.getSessionId());\n\t\tconst before = getPlanState();\n\t\tconst replayed = replayPlanFromBranch(ctx);\n\t\tconst next = replayed ?? clonePlanState(EMPTY_PLAN_STATE);\n\t\tif (!replayed && options?.applyFlag && pi.getFlag(PLAN_FLAG_NAME) === true) {\n\t\t\tnext.planning = true;\n\t\t}\n\n\t\tlet interruptedCompact = false;\n\t\tif (next.awaitingCompact) {\n\t\t\tnext.planning = false;\n\t\t\tnext.awaitingCompact = false;\n\t\t\tinterruptedCompact = true;\n\t\t\tpendingCompact = undefined;\n\t\t}\n\n\t\tif (next.planning) {\n\t\t\t// The fresh removal set is authoritative for the eventual restore:\n\t\t\t// the live tool set may differ from what the entry recorded.\n\t\t\tnext.removedTools = applyPlanTools();\n\t\t} else {\n\t\t\tif (pi.getActiveTools().includes(EXIT_PLAN_TOOL_NAME)) {\n\t\t\t\trestoreTools(before.planning ? before.removedTools : next.removedTools);\n\t\t\t}\n\t\t\tnext.removedTools = [];\n\t\t}\n\n\t\treplacePlanState(next);\n\t\tif (interruptedCompact) {\n\t\t\tpersist();\n\t\t\tctx.ui.notify(\"Pending compaction was interrupted; plan mode exited with context kept.\", \"warning\");\n\t\t}\n\t\tupdateStatus(ctx);\n\t}\n\n\tpi.registerTool({\n\t\tname: EXIT_PLAN_TOOL_NAME,\n\t\tlabel: EXIT_PLAN_TOOL_LABEL,\n\t\tdescription: EXIT_PLAN_TOOL_DESCRIPTION,\n\t\tpromptSnippet: EXIT_PLAN_PROMPT_SNIPPET,\n\t\tpromptGuidelines: EXIT_PLAN_PROMPT_GUIDELINES,\n\t\tparameters: ExitPlanParamsSchema,\n\t\texecutionMode: \"sequential\" as ToolExecutionMode,\n\n\t\tasync execute(_toolCallId, params, signal, _onUpdate, ctx): Promise<AgentToolResult<ExitPlanDetails>> {\n\t\t\tsetActivePlanSession(ctx.sessionManager.getSessionId());\n\t\t\tconst state = getPlanState();\n\t\t\tconst title = params.title.trim() || \"plan\";\n\n\t\t\tif (!state.planning || state.awaitingCompact) {\n\t\t\t\treturn {\n\t\t\t\t\tcontent: [\n\t\t\t\t\t\t{ type: \"text\", text: \"Plan mode is not awaiting an exit; continue with your current instructions.\" },\n\t\t\t\t\t],\n\t\t\t\t\tdetails: { decision: \"cancelled\", title },\n\t\t\t\t};\n\t\t\t}\n\n\t\t\tif (!ctx.hasUI) {\n\t\t\t\t// json/print: no way to ask, so honor the least surprising default —\n\t\t\t\t// exit with context kept. Documented as a headless-mode semantic.\n\t\t\t\tconst planPath = saveApprovedPlan(ctx, params, title);\n\t\t\t\texitPlanMode(ctx);\n\t\t\t\treturn {\n\t\t\t\t\tcontent: [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\t\ttext: `Plan saved to ${planPath}. No interactive UI, so plan mode exited with context kept (authoritative: write tools are restored). Begin executing the plan now.`,\n\t\t\t\t\t\t},\n\t\t\t\t\t],\n\t\t\t\t\tdetails: { decision: \"execute\", title, planPath },\n\t\t\t\t};\n\t\t\t}\n\n\t\t\tlet choice: string | undefined;\n\t\t\tmenuOpen = true;\n\t\t\ttry {\n\t\t\t\tchoice = await ctx.ui.select(`Approve plan \"${title}\"?`, EXIT_PLAN_MENU_OPTIONS, {\n\t\t\t\t\tsignal,\n\t\t\t\t\tsubtitle: formatApprovalSubtitle(ctx),\n\t\t\t\t\tcancelHint: EXIT_PLAN_CANCEL_HINT,\n\t\t\t\t});\n\t\t\t} finally {\n\t\t\t\tmenuOpen = false;\n\t\t\t}\n\n\t\t\tif (signal?.aborted) {\n\t\t\t\t// Throwing lets Pi mark the result as an error, matching the todo convention.\n\t\t\t\tthrow new Error(\"Plan approval was interrupted; plan mode is still active.\");\n\t\t\t}\n\n\t\t\tif (choice === MENU_EXECUTE) {\n\t\t\t\tconst planPath = saveApprovedPlan(ctx, params, title);\n\t\t\t\texitPlanMode(ctx, \"Plan approved: executing with full context.\");\n\t\t\t\treturn {\n\t\t\t\t\tcontent: [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\t\ttext: `Plan approved and saved to ${planPath}. Plan mode has ended and write tools are restored (authoritative — overrides the plan-mode system prompt block). Begin executing the plan now; track multi-step work with the todo tool.`,\n\t\t\t\t\t\t},\n\t\t\t\t\t],\n\t\t\t\t\tdetails: { decision: \"execute\", title, planPath },\n\t\t\t\t};\n\t\t\t}\n\n\t\t\tif (choice === MENU_COMPACT_EXECUTE) {\n\t\t\t\tconst planPath = saveApprovedPlan(ctx, params, title);\n\t\t\t\t// Stay in plan mode (tools restricted) until compaction completes;\n\t\t\t\t// terminate ends the run so agent_settled can start the compaction.\n\t\t\t\treplacePlanState({ ...getPlanState(), awaitingCompact: true });\n\t\t\t\tpersist();\n\t\t\t\tpendingCompact = { title, plan: params.plan, planPath };\n\t\t\t\treturn {\n\t\t\t\t\tcontent: [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\t\ttext: `Plan approved and saved to ${planPath}. The context will now be compacted; stop here — execution restarts automatically after compaction.`,\n\t\t\t\t\t\t},\n\t\t\t\t\t],\n\t\t\t\t\tdetails: { decision: \"compactAndExecute\", title, planPath },\n\t\t\t\t\tterminate: true,\n\t\t\t\t};\n\t\t\t}\n\n\t\t\t// Explicit \"Keep planning\", Esc, or timeout: the only side-effect-free\n\t\t\t// choice, so any dismissal maps here.\n\t\t\tlet feedback: string | undefined;\n\t\t\tif (choice === MENU_KEEP_PLANNING) {\n\t\t\t\tfeedback =\n\t\t\t\t\tctx.mode === \"tui\"\n\t\t\t\t\t\t? await ctx.ui.editor(\"Feedback for the plan (optional):\", \"\")\n\t\t\t\t\t\t: await ctx.ui.input(\"Feedback for the plan (optional):\");\n\t\t\t}\n\t\t\tconst trimmedFeedback = feedback?.trim();\n\t\t\treturn {\n\t\t\t\tcontent: [\n\t\t\t\t\t{\n\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\ttext: trimmedFeedback\n\t\t\t\t\t\t\t? `The user wants to keep planning. Feedback:\\n${trimmedFeedback}`\n\t\t\t\t\t\t\t: \"The user wants to keep planning. Ask what should change, or keep refining the open points.\",\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t\tdetails: { decision: \"keepPlanning\", title },\n\t\t\t};\n\t\t},\n\n\t\trenderCall(args, theme, context) {\n\t\t\tconst text = (context.lastComponent as Text | undefined) ?? new Text(\"\", 0, 0);\n\t\t\ttext.setText(formatExitPlanCall(args, theme, context.expanded));\n\t\t\treturn text;\n\t\t},\n\n\t\trenderResult(result, _options, theme, context) {\n\t\t\tconst text = (context.lastComponent as Text | undefined) ?? new Text(\"\", 0, 0);\n\t\t\ttext.setText(formatExitPlanResult(result, theme));\n\t\t\treturn text;\n\t\t},\n\t});\n\n\tpi.registerCommand(PLAN_COMMAND_NAME, {\n\t\tdescription: \"Toggle plan mode (read-only planning with user-approved exit)\",\n\t\thandler: async (_args, ctx) => {\n\t\t\tsetActivePlanSession(ctx.sessionManager.getSessionId());\n\t\t\tconst state = getPlanState();\n\t\t\tif (menuOpen) {\n\t\t\t\tctx.ui.notify(\"The exit_plan approval menu is open — answer it first.\", \"warning\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (state.awaitingCompact) {\n\t\t\t\tctx.ui.notify(\"Waiting for compaction to finish before leaving plan mode.\", \"warning\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (state.planning) {\n\t\t\t\texitPlanMode(ctx, \"Plan mode disabled: full tool access restored.\");\n\t\t\t} else {\n\t\t\t\tenterPlanMode(ctx);\n\t\t\t}\n\t\t},\n\t});\n\n\t// Defense in depth: the active-tools change lands on the next LLM request,\n\t// so a request already in flight when plan mode toggled can still carry\n\t// blocked tool calls.\n\tpi.on(\"tool_call\", async (event, ctx) => {\n\t\tsetActivePlanSession(ctx.sessionManager.getSessionId());\n\t\tconst state = getPlanState();\n\t\tif (!state.planning || !PLAN_BLOCKED_TOOLS.has(event.toolName)) return;\n\t\treturn {\n\t\t\tblock: true,\n\t\t\treason: `Plan mode: ${event.toolName} is disabled. Keep exploring with read-only tools, or call ${EXIT_PLAN_TOOL_NAME} to submit the plan for approval.`,\n\t\t};\n\t});\n\n\tpi.on(\"before_agent_start\", async (event, ctx) => {\n\t\tsetActivePlanSession(ctx.sessionManager.getSessionId());\n\t\tconst state = getPlanState();\n\t\tif (!state.planning) return;\n\t\treturn { systemPrompt: `${event.systemPrompt}\\n\\n${buildPlanModePrompt(state.planFiles)}` };\n\t});\n\n\t// agent_settled fires once per fully settled run (agent_end can repeat on\n\t// retries and queued continuations), so the approved compaction starts here.\n\tpi.on(\"agent_settled\", async (_event, ctx) => {\n\t\tsetActivePlanSession(ctx.sessionManager.getSessionId());\n\t\tif (!getPlanState().awaitingCompact || !pendingCompact) return;\n\t\tconst pending = pendingCompact;\n\t\tpendingCompact = undefined;\n\t\tctx.compact({\n\t\t\tcustomInstructions: `The user approved an implementation plan saved at ${pending.planPath}. Preserve decisions, constraints, and rejected alternatives from the planning discussion that are not already written in the plan file.`,\n\t\t\tonComplete: () => {\n\t\t\t\texitPlanMode(ctx, \"Context compacted; executing the plan.\");\n\t\t\t\tpi.sendUserMessage(buildKickoffMessage(pending));\n\t\t\t},\n\t\t\tonError: (error) => {\n\t\t\t\t// Never leave the user stuck read-only: degrade to keep-context execution.\n\t\t\t\texitPlanMode(ctx, `Compaction failed (${error.message}); executing with full context instead.`);\n\t\t\t\tpi.sendUserMessage(buildKickoffMessage(pending));\n\t\t\t},\n\t\t});\n\t});\n\n\tpi.on(\"session_start\", async (_event, ctx) => {\n\t\tsyncFromBranch(ctx, { applyFlag: true });\n\t});\n\n\tpi.on(\"session_tree\", async (_event, ctx) => {\n\t\tsyncFromBranch(ctx);\n\t});\n\n\tpi.on(\"session_shutdown\", async (_event, ctx) => {\n\t\tpendingCompact = undefined;\n\t\tdisposePlanSession(ctx.sessionManager.getSessionId());\n\t});\n}\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/extensions/plan/index.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * plan — Claude-Code-style plan mode: read-only exploration and interview,\n * then a user-approved exit that saves the plan and optionally compacts.\n *\n * Shape:\n * - Entry (/plan or --plan) snapshots the active tool set and swaps to the\n * exploration set (read/grep/find/ls/bash + exit_plan; edit/write removed,\n * bash prompt-constrained to read-only, subagent guarded to the explorer\n * profile); a tool_call guard covers requests already in flight before the\n * tool-set change lands.\n * - The plan text travels as an exit_plan parameter, so the model needs no\n * write access to produce the plan file.\n * - \"Compact, then execute\" keeps plan mode active until compaction completes\n * (tools are restored in onComplete) and terminates the run via the tool\n * result, so there is no window where the model holds full context plus\n * write tools. The kickoff message embeds the plan itself — continuation\n * never depends on the compaction summary preserving a path.\n * - State is conversation-backed via custom entries and branch replay (same\n * pattern as todo): /reload, /tree, resume, and fork all restore.\n */\n\nimport { basename } from \"node:path\";\nimport { Text } from \"@earendil-works/pi-tui\";\nimport type {\n\tAgentToolResult,\n\tExtensionAPI,\n\tExtensionContext,\n\tToolExecutionMode,\n} from \"../../core/extensions/types.ts\";\nimport {\n\tbuildPlanModePrompt,\n\tEXIT_PLAN_CANCEL_HINT,\n\tEXIT_PLAN_MENU_OPTIONS,\n\tEXIT_PLAN_PROMPT_GUIDELINES,\n\tEXIT_PLAN_PROMPT_SNIPPET,\n\tEXIT_PLAN_TOOL_DESCRIPTION,\n\tEXIT_PLAN_TOOL_LABEL,\n\tEXIT_PLAN_TOOL_NAME,\n\tMENU_COMPACT_EXECUTE,\n\tMENU_EXECUTE,\n\tMENU_KEEP_PLANNING,\n\tPLAN_ALLOWED_SUBAGENT,\n\tPLAN_BLOCKED_TOOLS,\n\tPLAN_COMMAND_NAME,\n\tPLAN_EMBED_MAX_CHARS,\n\tPLAN_ENTRY_TYPE,\n\tPLAN_EXPLORE_TOOLS,\n\tPLAN_FLAG_NAME,\n\tPLAN_KICKOFF_MESSAGE_TYPE,\n\tPLAN_PANEL_CANCEL_HINT,\n\tPLAN_PANEL_EXIT,\n\tPLAN_PANEL_EXIT_DESCRIPTION,\n\tPLAN_PANEL_MAX_FILES,\n\tPLAN_PANEL_TITLE,\n\tPLAN_STATUS_KEY,\n\tPLAN_SUBAGENT_TOOL_NAME,\n} from \"./constants.ts\";\nimport { type ExitPlanDetails, type ExitPlanParams, ExitPlanParamsSchema, type PlanKickoffDetails } from \"./schema.ts\";\nimport {\n\tclonePlanState,\n\tdisposePlanSession,\n\tEMPTY_PLAN_STATE,\n\tgetPlanState,\n\treplacePlanState,\n\treplayPlanFromBranch,\n\tsetActivePlanSession,\n} from \"./state.ts\";\nimport { listProjectPlanFiles, savePlanFile } from \"./storage.ts\";\nimport {\n\tformatApprovalSubtitle,\n\tformatExitPlanResult,\n\trenderExitPlanCall,\n\trenderPlanKickoffMessage,\n\tshortenPlanPath,\n} from \"./view.ts\";\n\ninterface PendingCompact {\n\ttitle: string;\n\tplan: string;\n\tplanPath: string;\n}\n\nfunction buildKickoffMessage(pending: PendingCompact): string {\n\tconst truncated = pending.plan.length > PLAN_EMBED_MAX_CHARS;\n\tconst body = truncated\n\t\t? `${pending.plan.slice(0, PLAN_EMBED_MAX_CHARS)}\\n\\n[Plan truncated — read the full plan at ${pending.planPath}]`\n\t\t: pending.plan;\n\treturn `Execute the approved plan \"${pending.title}\" (saved at ${pending.planPath}).\n\n${body}\n\nStart now. Track multi-step work with the todo tool, and report back instead of improvising if the plan turns out to need revision.`;\n}\n\n/**\n * First subagent profile in the call that is not allowed in plan mode, or\n * undefined when every profile is the read-only explorer. An omitted or null\n * agent resolves to the default \"general\" profile, so it is disallowed too.\n * Args arrive off the wire, so every field is read defensively.\n */\nexport function findDisallowedSubagentProfile(input: unknown): string | undefined {\n\tconst record = input && typeof input === \"object\" ? (input as Record<string, unknown>) : {};\n\tconst requested: unknown[] = Array.isArray(record.tasks)\n\t\t? record.tasks.map((task) =>\n\t\t\t\ttask && typeof task === \"object\" ? (task as Record<string, unknown>).agent : undefined,\n\t\t\t)\n\t\t: [record.agent];\n\tfor (const profile of requested) {\n\t\tconst name = typeof profile === \"string\" && profile.trim() ? profile.trim() : \"general\";\n\t\tif (name !== PLAN_ALLOWED_SUBAGENT) return name;\n\t}\n\treturn undefined;\n}\n\nexport default function plan(pi: ExtensionAPI): void {\n\tlet menuOpen = false;\n\tlet pendingCompact: PendingCompact | undefined;\n\n\tpi.registerMessageRenderer<PlanKickoffDetails>(PLAN_KICKOFF_MESSAGE_TYPE, renderPlanKickoffMessage);\n\n\t/**\n\t * Kickoff as a custom message, not a user message: the LLM still receives\n\t * the full kickoff text (custom messages convert to user-role context),\n\t * while the TUI renders the registered collapsed card instead of echoing\n\t * the whole plan again.\n\t */\n\tfunction sendKickoff(pending: PendingCompact): void {\n\t\tpi.sendMessage(\n\t\t\t{\n\t\t\t\tcustomType: PLAN_KICKOFF_MESSAGE_TYPE,\n\t\t\t\tcontent: buildKickoffMessage(pending),\n\t\t\t\tdisplay: true,\n\t\t\t\tdetails: { title: pending.title, planPath: pending.planPath },\n\t\t\t},\n\t\t\t{ triggerTurn: true },\n\t\t);\n\t}\n\n\tpi.registerFlag(PLAN_FLAG_NAME, {\n\t\tdescription: \"Start in plan mode (read-only planning)\",\n\t\ttype: \"boolean\",\n\t\tdefault: false,\n\t});\n\n\t/**\n\t * Swap to the exploration tool set and return the pre-entry snapshot.\n\t * Snapshot + delta rather than a fixed allowlist: edit/write are dropped,\n\t * the explore tools (grep/find/ls are registered but not active by\n\t * default) and exit_plan are added, and every other active tool — question,\n\t * todo, deepwiki, subagent, ... — stays active.\n\t */\n\tfunction applyPlanTools(): string[] {\n\t\tconst snapshot = pi.getActiveTools();\n\t\tpi.setActiveTools([\n\t\t\t...new Set([\n\t\t\t\t...snapshot.filter((name) => !PLAN_BLOCKED_TOOLS.has(name)),\n\t\t\t\t...PLAN_EXPLORE_TOOLS,\n\t\t\t\tEXIT_PLAN_TOOL_NAME,\n\t\t\t]),\n\t\t]);\n\t\treturn snapshot;\n\t}\n\n\t/**\n\t * Inverse of applyPlanTools: restore the snapshot verbatim. An empty\n\t * snapshot (legacy entries) only drops exit_plan — there is nothing\n\t * recorded to restore to.\n\t */\n\tfunction restoreTools(toolSnapshot: string[]): void {\n\t\tif (toolSnapshot.length > 0) {\n\t\t\tpi.setActiveTools([...new Set(toolSnapshot)].filter((name) => name !== EXIT_PLAN_TOOL_NAME));\n\t\t\treturn;\n\t\t}\n\t\tpi.setActiveTools(pi.getActiveTools().filter((name) => name !== EXIT_PLAN_TOOL_NAME));\n\t}\n\n\tfunction persist(): void {\n\t\tpi.appendEntry(PLAN_ENTRY_TYPE, getPlanState());\n\t}\n\n\tfunction updateStatus(ctx: ExtensionContext): void {\n\t\tconst state = getPlanState();\n\t\tctx.ui.setStatus(PLAN_STATUS_KEY, state.planning ? ctx.ui.theme.fg(\"accent\", \"Plan Mode\") : undefined);\n\t}\n\n\tfunction enterPlanMode(ctx: ExtensionContext): void {\n\t\tconst state = getPlanState();\n\t\tif (state.planning) return;\n\t\tconst toolSnapshot = applyPlanTools();\n\t\treplacePlanState({ planning: true, toolSnapshot, awaitingCompact: false, planFiles: state.planFiles });\n\t\tpersist();\n\t\tupdateStatus(ctx);\n\t\tctx.ui.notify(\n\t\t\t\"Plan mode: read-only exploration — edit/write disabled, bash constrained to read-only, subagent limited to explorer.\",\n\t\t\t\"info\",\n\t\t);\n\t}\n\n\tfunction exitPlanMode(ctx: ExtensionContext, notifyText?: string): void {\n\t\tconst state = getPlanState();\n\t\trestoreTools(state.toolSnapshot);\n\t\treplacePlanState({ ...state, planning: false, awaitingCompact: false, toolSnapshot: [] });\n\t\tpersist();\n\t\tupdateStatus(ctx);\n\t\tif (notifyText) ctx.ui.notify(notifyText, \"info\");\n\t}\n\n\t/** Save the approved plan, index it in state, and name the session if unnamed. */\n\tfunction saveApprovedPlan(ctx: ExtensionContext, params: ExitPlanParams, title: string): string {\n\t\tconst planPath = savePlanFile({\n\t\t\tsessionId: ctx.sessionManager.getSessionId(),\n\t\t\ttitle,\n\t\t\tplan: params.plan,\n\t\t\tcwd: ctx.cwd,\n\t\t\trevises: params.revises,\n\t\t});\n\t\tconst state = getPlanState();\n\t\treplacePlanState({ ...state, planFiles: [...state.planFiles, planPath] });\n\t\tif (!pi.getSessionName()) pi.setSessionName(title);\n\t\treturn planPath;\n\t}\n\n\t/**\n\t * Rebuild state from the current branch and reconcile the live tool set.\n\t * An interrupted compact-and-execute approval (restart, resume, branch\n\t * switch) degrades to the keep-context exit so the approval is honored.\n\t */\n\tfunction syncFromBranch(ctx: ExtensionContext, options?: { applyFlag?: boolean }): void {\n\t\tsetActivePlanSession(ctx.sessionManager.getSessionId());\n\t\tconst before = getPlanState();\n\t\tconst replayed = replayPlanFromBranch(ctx);\n\t\tconst next = replayed ?? clonePlanState(EMPTY_PLAN_STATE);\n\t\tif (!replayed && options?.applyFlag && pi.getFlag(PLAN_FLAG_NAME) === true) {\n\t\t\tnext.planning = true;\n\t\t}\n\n\t\tlet interruptedCompact = false;\n\t\tif (next.awaitingCompact) {\n\t\t\tnext.planning = false;\n\t\t\tnext.awaitingCompact = false;\n\t\t\tinterruptedCompact = true;\n\t\t\tpendingCompact = undefined;\n\t\t}\n\n\t\tif (next.planning) {\n\t\t\t// Re-applying is idempotent, but the returned live snapshot is only\n\t\t\t// authoritative when the live set was NOT already restricted: on a\n\t\t\t// branch switch while planning, re-snapshotting would capture the\n\t\t\t// plan-mode set, so the in-memory snapshot wins there.\n\t\t\tconst liveSnapshot = applyPlanTools();\n\t\t\tnext.toolSnapshot =\n\t\t\t\tbefore.planning && before.toolSnapshot.length > 0 ? [...before.toolSnapshot] : liveSnapshot;\n\t\t} else {\n\t\t\tif (pi.getActiveTools().includes(EXIT_PLAN_TOOL_NAME)) {\n\t\t\t\trestoreTools(before.planning ? before.toolSnapshot : next.toolSnapshot);\n\t\t\t}\n\t\t\tnext.toolSnapshot = [];\n\t\t}\n\n\t\treplacePlanState(next);\n\t\tif (interruptedCompact) {\n\t\t\tpersist();\n\t\t\tctx.ui.notify(\"Pending compaction was interrupted; plan mode exited with context kept.\", \"warning\");\n\t\t}\n\t\tupdateStatus(ctx);\n\t}\n\n\tpi.registerTool<typeof ExitPlanParamsSchema, ExitPlanDetails>({\n\t\tname: EXIT_PLAN_TOOL_NAME,\n\t\tlabel: EXIT_PLAN_TOOL_LABEL,\n\t\tdescription: EXIT_PLAN_TOOL_DESCRIPTION,\n\t\tpromptSnippet: EXIT_PLAN_PROMPT_SNIPPET,\n\t\tpromptGuidelines: EXIT_PLAN_PROMPT_GUIDELINES,\n\t\tparameters: ExitPlanParamsSchema,\n\t\texecutionMode: \"sequential\" as ToolExecutionMode,\n\t\trendersOwnProgress: true,\n\n\t\tasync execute(_toolCallId, params, signal, _onUpdate, ctx): Promise<AgentToolResult<ExitPlanDetails>> {\n\t\t\tsetActivePlanSession(ctx.sessionManager.getSessionId());\n\t\t\tconst state = getPlanState();\n\t\t\tconst title = params.title.trim() || \"plan\";\n\n\t\t\tif (!state.planning || state.awaitingCompact) {\n\t\t\t\treturn {\n\t\t\t\t\tcontent: [\n\t\t\t\t\t\t{ type: \"text\", text: \"Plan mode is not awaiting an exit; continue with your current instructions.\" },\n\t\t\t\t\t],\n\t\t\t\t\tdetails: { decision: \"cancelled\", title },\n\t\t\t\t};\n\t\t\t}\n\n\t\t\tif (!ctx.hasUI) {\n\t\t\t\t// json/print: no way to ask, so honor the least surprising default —\n\t\t\t\t// exit with context kept. Documented as a headless-mode semantic.\n\t\t\t\tconst planPath = saveApprovedPlan(ctx, params, title);\n\t\t\t\texitPlanMode(ctx);\n\t\t\t\treturn {\n\t\t\t\t\tcontent: [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\t\ttext: `Plan saved to ${planPath}. No interactive UI, so plan mode exited with context kept (authoritative: write tools are restored). Begin executing the plan now.`,\n\t\t\t\t\t\t},\n\t\t\t\t\t],\n\t\t\t\t\tdetails: { decision: \"execute\", title, planPath },\n\t\t\t\t};\n\t\t\t}\n\n\t\t\tlet choice: string | undefined;\n\t\t\tmenuOpen = true;\n\t\t\ttry {\n\t\t\t\tchoice = await ctx.ui.select(`Approve plan \"${title}\"?`, EXIT_PLAN_MENU_OPTIONS, {\n\t\t\t\t\tsignal,\n\t\t\t\t\tsubtitle: formatApprovalSubtitle(ctx),\n\t\t\t\t\tcancelHint: EXIT_PLAN_CANCEL_HINT,\n\t\t\t\t});\n\t\t\t} finally {\n\t\t\t\tmenuOpen = false;\n\t\t\t}\n\n\t\t\tif (signal?.aborted) {\n\t\t\t\t// Throwing lets Pi mark the result as an error, matching the todo convention.\n\t\t\t\tthrow new Error(\"Plan approval was interrupted; plan mode is still active.\");\n\t\t\t}\n\n\t\t\tif (choice === MENU_EXECUTE) {\n\t\t\t\tconst planPath = saveApprovedPlan(ctx, params, title);\n\t\t\t\texitPlanMode(ctx, \"Plan approved: executing with full context.\");\n\t\t\t\treturn {\n\t\t\t\t\tcontent: [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\t\ttext: `Plan approved and saved to ${planPath}. Plan mode has ended and write tools are restored (authoritative — overrides the plan-mode system prompt block). Begin executing the plan now; track multi-step work with the todo tool.`,\n\t\t\t\t\t\t},\n\t\t\t\t\t],\n\t\t\t\t\tdetails: { decision: \"execute\", title, planPath },\n\t\t\t\t};\n\t\t\t}\n\n\t\t\tif (choice === MENU_COMPACT_EXECUTE) {\n\t\t\t\tconst planPath = saveApprovedPlan(ctx, params, title);\n\t\t\t\t// Stay in plan mode (tools restricted) until compaction completes;\n\t\t\t\t// terminate ends the run so agent_settled can start the compaction.\n\t\t\t\treplacePlanState({ ...getPlanState(), awaitingCompact: true });\n\t\t\t\tpersist();\n\t\t\t\tpendingCompact = { title, plan: params.plan, planPath };\n\t\t\t\treturn {\n\t\t\t\t\tcontent: [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\t\ttext: `Plan approved and saved to ${planPath}. The context will now be compacted; stop here — execution restarts automatically after compaction.`,\n\t\t\t\t\t\t},\n\t\t\t\t\t],\n\t\t\t\t\tdetails: { decision: \"compactAndExecute\", title, planPath },\n\t\t\t\t\tterminate: true,\n\t\t\t\t};\n\t\t\t}\n\n\t\t\t// Explicit \"Keep planning\", Esc, or timeout: the only side-effect-free\n\t\t\t// choice, so any dismissal maps here.\n\t\t\tlet feedback: string | undefined;\n\t\t\tif (choice === MENU_KEEP_PLANNING) {\n\t\t\t\tfeedback =\n\t\t\t\t\tctx.mode === \"tui\"\n\t\t\t\t\t\t? await ctx.ui.editor(\"Feedback for the plan (optional):\", \"\")\n\t\t\t\t\t\t: await ctx.ui.input(\"Feedback for the plan (optional):\");\n\t\t\t}\n\t\t\tconst trimmedFeedback = feedback?.trim();\n\t\t\treturn {\n\t\t\t\tcontent: [\n\t\t\t\t\t{\n\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\ttext: trimmedFeedback\n\t\t\t\t\t\t\t? `The user wants to keep planning. Feedback:\\n${trimmedFeedback}`\n\t\t\t\t\t\t\t: \"The user wants to keep planning. Ask what should change, or keep refining the open points.\",\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t\tdetails: { decision: \"keepPlanning\", title },\n\t\t\t};\n\t\t},\n\n\t\trenderCall(args, theme, context) {\n\t\t\treturn renderExitPlanCall(args, theme, context.expanded);\n\t\t},\n\n\t\trenderResult(result, _options, theme) {\n\t\t\treturn new Text(formatExitPlanResult(result, theme), 0, 0);\n\t\t},\n\t});\n\n\t/**\n\t * Plans offered in the /plan panel: this branch's plans first (newest\n\t * first), then the rest of the project's, deduped, bounded. Legacy plans\n\t * from other sessions' directories only appear via planFiles entries.\n\t */\n\tfunction collectPanelPlanFiles(ctx: ExtensionContext): string[] {\n\t\tconst state = getPlanState();\n\t\tconst seen = new Set<string>();\n\t\tconst files: string[] = [];\n\t\tfor (const path of [...[...state.planFiles].reverse(), ...listProjectPlanFiles(ctx.cwd)]) {\n\t\t\tconst key = path.replace(/\\\\/g, \"/\").toLowerCase();\n\t\t\tif (seen.has(key)) continue;\n\t\t\tseen.add(key);\n\t\t\tfiles.push(path);\n\t\t\tif (files.length >= PLAN_PANEL_MAX_FILES) break;\n\t\t}\n\t\treturn files;\n\t}\n\n\t/** Panel behind /plan while planning: pick a plan file or exit the mode. */\n\tasync function openPlanPanel(ctx: ExtensionContext): Promise<void> {\n\t\tconst files = collectPanelPlanFiles(ctx);\n\t\tconst fileOptions = files.map((path) => ({ label: basename(path), description: shortenPlanPath(path) }));\n\t\tconst options = [{ label: PLAN_PANEL_EXIT, description: PLAN_PANEL_EXIT_DESCRIPTION }, ...fileOptions];\n\t\tconst choice = await ctx.ui.select(PLAN_PANEL_TITLE, options, { cancelHint: PLAN_PANEL_CANCEL_HINT });\n\t\tif (choice === PLAN_PANEL_EXIT) {\n\t\t\texitPlanMode(ctx, \"Plan mode disabled: full tool access restored.\");\n\t\t\treturn;\n\t\t}\n\t\tif (!choice) return; // Esc: keep planning, no side effects.\n\t\tconst selected = files.find((path) => basename(path) === choice);\n\t\tif (selected) ctx.ui.pasteToEditor(selected);\n\t}\n\n\tpi.registerCommand(PLAN_COMMAND_NAME, {\n\t\tdescription: \"Enter plan mode; while planning, open the plan panel (pick a plan file or exit)\",\n\t\thandler: async (_args, ctx) => {\n\t\t\tsetActivePlanSession(ctx.sessionManager.getSessionId());\n\t\t\tconst state = getPlanState();\n\t\t\tif (menuOpen) {\n\t\t\t\tctx.ui.notify(\"The exit_plan approval menu is open — answer it first.\", \"warning\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (state.awaitingCompact) {\n\t\t\t\tctx.ui.notify(\"Waiting for compaction to finish before leaving plan mode.\", \"warning\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (!state.planning) {\n\t\t\t\tenterPlanMode(ctx);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (ctx.mode !== \"tui\") {\n\t\t\t\t// No panel affordances outside the TUI: keep the toggle semantic.\n\t\t\t\texitPlanMode(ctx, \"Plan mode disabled: full tool access restored.\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tawait openPlanPanel(ctx);\n\t\t},\n\t});\n\n\t// Defense in depth: the active-tools change lands on the next LLM request,\n\t// so a request already in flight when plan mode toggled can still carry\n\t// blocked tool calls.\n\tpi.on(\"tool_call\", async (event, ctx) => {\n\t\tsetActivePlanSession(ctx.sessionManager.getSessionId());\n\t\tconst state = getPlanState();\n\t\tif (!state.planning) return;\n\t\tif (PLAN_BLOCKED_TOOLS.has(event.toolName)) {\n\t\t\treturn {\n\t\t\t\tblock: true,\n\t\t\t\treason: `Plan mode: ${event.toolName} is disabled. Keep exploring with read-only tools, or call ${EXIT_PLAN_TOOL_NAME} to submit the plan for approval.`,\n\t\t\t};\n\t\t}\n\t\tif (event.toolName === PLAN_SUBAGENT_TOOL_NAME) {\n\t\t\tconst disallowed = findDisallowedSubagentProfile(event.input);\n\t\t\tif (disallowed !== undefined) {\n\t\t\t\treturn {\n\t\t\t\t\tblock: true,\n\t\t\t\t\treason: `Plan mode: subagent profile \"${disallowed}\" is blocked while planning. Delegate read-only exploration with agent: \"${PLAN_ALLOWED_SUBAGENT}\" (set it on every task in tasks mode).`,\n\t\t\t\t};\n\t\t\t}\n\t\t}\n\t});\n\n\tpi.on(\"before_agent_start\", async (event, ctx) => {\n\t\tsetActivePlanSession(ctx.sessionManager.getSessionId());\n\t\tconst state = getPlanState();\n\t\tif (!state.planning) return;\n\t\treturn { systemPrompt: `${event.systemPrompt}\\n\\n${buildPlanModePrompt(state.planFiles)}` };\n\t});\n\n\t// agent_settled fires once per fully settled run (agent_end can repeat on\n\t// retries and queued continuations), so the approved compaction starts here.\n\tpi.on(\"agent_settled\", async (_event, ctx) => {\n\t\tsetActivePlanSession(ctx.sessionManager.getSessionId());\n\t\tif (!getPlanState().awaitingCompact || !pendingCompact) return;\n\t\tconst pending = pendingCompact;\n\t\tpendingCompact = undefined;\n\t\tctx.compact({\n\t\t\tcustomInstructions: `The user approved an implementation plan saved at ${pending.planPath}. Preserve decisions, constraints, and rejected alternatives from the planning discussion that are not already written in the plan file.`,\n\t\t\tonComplete: () => {\n\t\t\t\texitPlanMode(ctx, \"Context compacted; executing the plan.\");\n\t\t\t\tsendKickoff(pending);\n\t\t\t},\n\t\t\tonError: (error) => {\n\t\t\t\t// Never leave the user stuck read-only: degrade to keep-context execution.\n\t\t\t\texitPlanMode(ctx, `Compaction failed (${error.message}); executing with full context instead.`);\n\t\t\t\tsendKickoff(pending);\n\t\t\t},\n\t\t});\n\t});\n\n\tpi.on(\"session_start\", async (_event, ctx) => {\n\t\tsyncFromBranch(ctx, { applyFlag: true });\n\t});\n\n\tpi.on(\"session_tree\", async (_event, ctx) => {\n\t\tsyncFromBranch(ctx);\n\t});\n\n\tpi.on(\"session_shutdown\", async (_event, ctx) => {\n\t\tpendingCompact = undefined;\n\t\tdisposePlanSession(ctx.sessionManager.getSessionId());\n\t});\n}\n"]}
@@ -12,4 +12,9 @@ export interface ExitPlanDetails {
12
12
  /** Absolute path of the saved plan file. Present for both execute decisions. */
13
13
  planPath?: string;
14
14
  }
15
+ /** details of the post-compaction kickoff custom message. */
16
+ export interface PlanKickoffDetails {
17
+ title: string;
18
+ planPath: string;
19
+ }
15
20
  //# sourceMappingURL=schema.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../../src/extensions/plan/schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,MAAM,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AAE5C,eAAO,MAAM,oBAAoB;;;;EAY/B,CAAC;AAEH,MAAM,MAAM,cAAc,GAAG,MAAM,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAEjE,MAAM,MAAM,gBAAgB,GAAG,SAAS,GAAG,mBAAmB,GAAG,cAAc,GAAG,WAAW,CAAC;AAE9F,MAAM,WAAW,eAAe;IAC/B,QAAQ,EAAE,gBAAgB,CAAC;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,gFAAgF;IAChF,QAAQ,CAAC,EAAE,MAAM,CAAC;CAClB","sourcesContent":["import { type Static, Type } from \"typebox\";\n\nexport const ExitPlanParamsSchema = Type.Object({\n\ttitle: Type.String({\n\t\tdescription: \"Short plan title. Used for the plan file name and, when the session is unnamed, the session name.\",\n\t}),\n\tplan: Type.String({\n\t\tdescription: \"The complete plan as markdown: goal, steps, and verification criteria.\",\n\t}),\n\trevises: Type.Optional(\n\t\tType.String({\n\t\t\tdescription: \"File name of a previously saved plan this plan revises, e.g. `01-add-cache.md`.\",\n\t\t}),\n\t),\n});\n\nexport type ExitPlanParams = Static<typeof ExitPlanParamsSchema>;\n\nexport type ExitPlanDecision = \"execute\" | \"compactAndExecute\" | \"keepPlanning\" | \"cancelled\";\n\nexport interface ExitPlanDetails {\n\tdecision: ExitPlanDecision;\n\ttitle: string;\n\t/** Absolute path of the saved plan file. Present for both execute decisions. */\n\tplanPath?: string;\n}\n"]}
1
+ {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../../src/extensions/plan/schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,MAAM,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AAE5C,eAAO,MAAM,oBAAoB;;;;EAY/B,CAAC;AAEH,MAAM,MAAM,cAAc,GAAG,MAAM,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAEjE,MAAM,MAAM,gBAAgB,GAAG,SAAS,GAAG,mBAAmB,GAAG,cAAc,GAAG,WAAW,CAAC;AAE9F,MAAM,WAAW,eAAe;IAC/B,QAAQ,EAAE,gBAAgB,CAAC;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,gFAAgF;IAChF,QAAQ,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,6DAA6D;AAC7D,MAAM,WAAW,kBAAkB;IAClC,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;CACjB","sourcesContent":["import { type Static, Type } from \"typebox\";\n\nexport const ExitPlanParamsSchema = Type.Object({\n\ttitle: Type.String({\n\t\tdescription: \"Short plan title. Used for the plan file name and, when the session is unnamed, the session name.\",\n\t}),\n\tplan: Type.String({\n\t\tdescription: \"The complete plan as markdown: goal, steps, and verification criteria.\",\n\t}),\n\trevises: Type.Optional(\n\t\tType.String({\n\t\t\tdescription: \"File name of a previously saved plan this plan revises, e.g. `01-add-cache.md`.\",\n\t\t}),\n\t),\n});\n\nexport type ExitPlanParams = Static<typeof ExitPlanParamsSchema>;\n\nexport type ExitPlanDecision = \"execute\" | \"compactAndExecute\" | \"keepPlanning\" | \"cancelled\";\n\nexport interface ExitPlanDetails {\n\tdecision: ExitPlanDecision;\n\ttitle: string;\n\t/** Absolute path of the saved plan file. Present for both execute decisions. */\n\tplanPath?: string;\n}\n\n/** details of the post-compaction kickoff custom message. */\nexport interface PlanKickoffDetails {\n\ttitle: string;\n\tplanPath: string;\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"schema.js","sourceRoot":"","sources":["../../../src/extensions/plan/schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAe,IAAI,EAAE,MAAM,SAAS,CAAC;AAE5C,MAAM,CAAC,MAAM,oBAAoB,GAAG,IAAI,CAAC,MAAM,CAAC;IAC/C,KAAK,EAAE,IAAI,CAAC,MAAM,CAAC;QAClB,WAAW,EAAE,mGAAmG;KAChH,CAAC;IACF,IAAI,EAAE,IAAI,CAAC,MAAM,CAAC;QACjB,WAAW,EAAE,wEAAwE;KACrF,CAAC;IACF,OAAO,EAAE,IAAI,CAAC,QAAQ,CACrB,IAAI,CAAC,MAAM,CAAC;QACX,WAAW,EAAE,iFAAiF;KAC9F,CAAC,CACF;CACD,CAAC,CAAC","sourcesContent":["import { type Static, Type } from \"typebox\";\n\nexport const ExitPlanParamsSchema = Type.Object({\n\ttitle: Type.String({\n\t\tdescription: \"Short plan title. Used for the plan file name and, when the session is unnamed, the session name.\",\n\t}),\n\tplan: Type.String({\n\t\tdescription: \"The complete plan as markdown: goal, steps, and verification criteria.\",\n\t}),\n\trevises: Type.Optional(\n\t\tType.String({\n\t\t\tdescription: \"File name of a previously saved plan this plan revises, e.g. `01-add-cache.md`.\",\n\t\t}),\n\t),\n});\n\nexport type ExitPlanParams = Static<typeof ExitPlanParamsSchema>;\n\nexport type ExitPlanDecision = \"execute\" | \"compactAndExecute\" | \"keepPlanning\" | \"cancelled\";\n\nexport interface ExitPlanDetails {\n\tdecision: ExitPlanDecision;\n\ttitle: string;\n\t/** Absolute path of the saved plan file. Present for both execute decisions. */\n\tplanPath?: string;\n}\n"]}
1
+ {"version":3,"file":"schema.js","sourceRoot":"","sources":["../../../src/extensions/plan/schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAe,IAAI,EAAE,MAAM,SAAS,CAAC;AAE5C,MAAM,CAAC,MAAM,oBAAoB,GAAG,IAAI,CAAC,MAAM,CAAC;IAC/C,KAAK,EAAE,IAAI,CAAC,MAAM,CAAC;QAClB,WAAW,EAAE,mGAAmG;KAChH,CAAC;IACF,IAAI,EAAE,IAAI,CAAC,MAAM,CAAC;QACjB,WAAW,EAAE,wEAAwE;KACrF,CAAC;IACF,OAAO,EAAE,IAAI,CAAC,QAAQ,CACrB,IAAI,CAAC,MAAM,CAAC;QACX,WAAW,EAAE,iFAAiF;KAC9F,CAAC,CACF;CACD,CAAC,CAAC","sourcesContent":["import { type Static, Type } from \"typebox\";\n\nexport const ExitPlanParamsSchema = Type.Object({\n\ttitle: Type.String({\n\t\tdescription: \"Short plan title. Used for the plan file name and, when the session is unnamed, the session name.\",\n\t}),\n\tplan: Type.String({\n\t\tdescription: \"The complete plan as markdown: goal, steps, and verification criteria.\",\n\t}),\n\trevises: Type.Optional(\n\t\tType.String({\n\t\t\tdescription: \"File name of a previously saved plan this plan revises, e.g. `01-add-cache.md`.\",\n\t\t}),\n\t),\n});\n\nexport type ExitPlanParams = Static<typeof ExitPlanParamsSchema>;\n\nexport type ExitPlanDecision = \"execute\" | \"compactAndExecute\" | \"keepPlanning\" | \"cancelled\";\n\nexport interface ExitPlanDetails {\n\tdecision: ExitPlanDecision;\n\ttitle: string;\n\t/** Absolute path of the saved plan file. Present for both execute decisions. */\n\tplanPath?: string;\n}\n\n/** details of the post-compaction kickoff custom message. */\nexport interface PlanKickoffDetails {\n\ttitle: string;\n\tplanPath: string;\n}\n"]}
@@ -1,8 +1,13 @@
1
1
  export interface PlanSessionState {
2
2
  /** True from entry until the exit is fully applied (including a pending compaction). */
3
3
  planning: boolean;
4
- /** Tools actually removed on entry; restored verbatim on exit. */
5
- removedTools: string[];
4
+ /**
5
+ * Full active-tool set snapshotted on entry; restored verbatim on exit.
6
+ * Empty for legacy entries (pre-snapshot `removedTools` shape) — replay
7
+ * re-derives a fresh snapshot for active planning, and an inactive legacy
8
+ * entry needs no restore beyond dropping exit_plan.
9
+ */
10
+ toolSnapshot: string[];
6
11
  /** A compact-and-execute exit was approved and is waiting for agent_settled + compaction. */
7
12
  awaitingCompact: boolean;
8
13
  /** Absolute paths of plan files saved on this conversation branch. */
@@ -16,6 +21,10 @@ export declare function replacePlanState(next: PlanSessionState): void;
16
21
  /** Drop in-memory state for a session (call from session_shutdown). */
17
22
  export declare function disposePlanSession(sid: string): void;
18
23
  export declare function clonePlanState(source: PlanSessionState): PlanSessionState;
24
+ /**
25
+ * Accepts both the current shape (`toolSnapshot`) and the legacy one
26
+ * (`removedTools`), so branches recorded before the snapshot change replay.
27
+ */
19
28
  export declare function isPlanState(value: unknown): value is PlanSessionState;
20
29
  /**
21
30
  * Latest plan-mode entry on the current branch, or undefined when the branch
@@ -1 +1 @@
1
- {"version":3,"file":"state.d.ts","sourceRoot":"","sources":["../../../src/extensions/plan/state.ts"],"names":[],"mappings":"AAeA,MAAM,WAAW,gBAAgB;IAChC,wFAAwF;IACxF,QAAQ,EAAE,OAAO,CAAC;IAClB,kEAAkE;IAClE,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,6FAA6F;IAC7F,eAAe,EAAE,OAAO,CAAC;IACzB,sEAAsE;IACtE,SAAS,EAAE,MAAM,EAAE,CAAC;CACpB;AAED,eAAO,MAAM,gBAAgB,EAAE,gBAK9B,CAAC;AAKF,kFAAkF;AAClF,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAEtD;AAED,wBAAgB,YAAY,IAAI,gBAAgB,CAE/C;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,gBAAgB,GAAG,IAAI,CAE7D;AAED,uEAAuE;AACvE,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAIpD;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,gBAAgB,GAAG,gBAAgB,CAOzE;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,gBAAgB,CASrE;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,GAAG,EAAE;IACzC,cAAc,EAAE;QAAE,SAAS,IAAI,QAAQ,CAAC,OAAO,CAAC,CAAA;KAAE,CAAC;CACnD,GAAG,gBAAgB,GAAG,SAAS,CAS/B","sourcesContent":["/**\n * plan/state.ts — per-session plan-mode state, persisted as custom entries.\n *\n * State is conversation-backed like the todo extension: every transition is\n * appended as a `plan-mode` custom entry (never sent to the LLM), and lifecycle\n * handlers replay the current branch tail → head to restore. Custom entries are\n * copied on fork/clone, so `planFiles` keeps pointing at the right plan files\n * even though the forked session gets a new session id.\n *\n * State is keyed per session id: one Pi process can host several sessions over\n * its lifetime (resume, /tree switches), so callers re-point the active bucket\n * wherever ctx is in hand.\n */\nimport { PLAN_ENTRY_TYPE } from \"./constants.ts\";\n\nexport interface PlanSessionState {\n\t/** True from entry until the exit is fully applied (including a pending compaction). */\n\tplanning: boolean;\n\t/** Tools actually removed on entry; restored verbatim on exit. */\n\tremovedTools: string[];\n\t/** A compact-and-execute exit was approved and is waiting for agent_settled + compaction. */\n\tawaitingCompact: boolean;\n\t/** Absolute paths of plan files saved on this conversation branch. */\n\tplanFiles: string[];\n}\n\nexport const EMPTY_PLAN_STATE: PlanSessionState = {\n\tplanning: false,\n\tremovedTools: [],\n\tawaitingCompact: false,\n\tplanFiles: [],\n};\n\nconst states = new Map<string, PlanSessionState>();\nlet activeSid = \"\";\n\n/** Point the module at a session's state bucket. Call wherever ctx is in hand. */\nexport function setActivePlanSession(sid: string): void {\n\tactiveSid = sid;\n}\n\nexport function getPlanState(): PlanSessionState {\n\treturn states.get(activeSid) ?? clonePlanState(EMPTY_PLAN_STATE);\n}\n\nexport function replacePlanState(next: PlanSessionState): void {\n\tstates.set(activeSid, clonePlanState(next));\n}\n\n/** Drop in-memory state for a session (call from session_shutdown). */\nexport function disposePlanSession(sid: string): void {\n\tif (!sid) return;\n\tstates.delete(sid);\n\tif (activeSid === sid) activeSid = \"\";\n}\n\nexport function clonePlanState(source: PlanSessionState): PlanSessionState {\n\treturn {\n\t\tplanning: source.planning,\n\t\tremovedTools: [...source.removedTools],\n\t\tawaitingCompact: source.awaitingCompact,\n\t\tplanFiles: [...source.planFiles],\n\t};\n}\n\nexport function isPlanState(value: unknown): value is PlanSessionState {\n\tif (!value || typeof value !== \"object\") return false;\n\tconst record = value as Record<string, unknown>;\n\treturn (\n\t\ttypeof record.planning === \"boolean\" &&\n\t\ttypeof record.awaitingCompact === \"boolean\" &&\n\t\tArray.isArray(record.removedTools) &&\n\t\tArray.isArray(record.planFiles)\n\t);\n}\n\n/**\n * Latest plan-mode entry on the current branch, or undefined when the branch\n * has none (lets callers distinguish \"never used\" from \"explicitly exited\").\n */\nexport function replayPlanFromBranch(ctx: {\n\tsessionManager: { getBranch(): Iterable<unknown> };\n}): PlanSessionState | undefined {\n\tconst branch = Array.from(ctx.sessionManager.getBranch());\n\tfor (let i = branch.length - 1; i >= 0; i--) {\n\t\tconst item = branch[i] as { type?: string; customType?: string; data?: unknown };\n\t\tif (item.type !== \"custom\" || item.customType !== PLAN_ENTRY_TYPE) continue;\n\t\tif (!isPlanState(item.data)) continue;\n\t\treturn clonePlanState(item.data);\n\t}\n\treturn undefined;\n}\n"]}
1
+ {"version":3,"file":"state.d.ts","sourceRoot":"","sources":["../../../src/extensions/plan/state.ts"],"names":[],"mappings":"AAeA,MAAM,WAAW,gBAAgB;IAChC,wFAAwF;IACxF,QAAQ,EAAE,OAAO,CAAC;IAClB;;;;;OAKG;IACH,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,6FAA6F;IAC7F,eAAe,EAAE,OAAO,CAAC;IACzB,sEAAsE;IACtE,SAAS,EAAE,MAAM,EAAE,CAAC;CACpB;AAED,eAAO,MAAM,gBAAgB,EAAE,gBAK9B,CAAC;AAKF,kFAAkF;AAClF,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAEtD;AAED,wBAAgB,YAAY,IAAI,gBAAgB,CAE/C;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,gBAAgB,GAAG,IAAI,CAE7D;AAED,uEAAuE;AACvE,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAIpD;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,gBAAgB,GAAG,gBAAgB,CAOzE;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,gBAAgB,CAGrE;AAqBD;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,GAAG,EAAE;IACzC,cAAc,EAAE;QAAE,SAAS,IAAI,QAAQ,CAAC,OAAO,CAAC,CAAA;KAAE,CAAC;CACnD,GAAG,gBAAgB,GAAG,SAAS,CAU/B","sourcesContent":["/**\n * plan/state.ts — per-session plan-mode state, persisted as custom entries.\n *\n * State is conversation-backed like the todo extension: every transition is\n * appended as a `plan-mode` custom entry (never sent to the LLM), and lifecycle\n * handlers replay the current branch tail → head to restore. Custom entries are\n * copied on fork/clone, so `planFiles` keeps pointing at the right plan files\n * even though the forked session gets a new session id.\n *\n * State is keyed per session id: one Pi process can host several sessions over\n * its lifetime (resume, /tree switches), so callers re-point the active bucket\n * wherever ctx is in hand.\n */\nimport { PLAN_ENTRY_TYPE } from \"./constants.ts\";\n\nexport interface PlanSessionState {\n\t/** True from entry until the exit is fully applied (including a pending compaction). */\n\tplanning: boolean;\n\t/**\n\t * Full active-tool set snapshotted on entry; restored verbatim on exit.\n\t * Empty for legacy entries (pre-snapshot `removedTools` shape) — replay\n\t * re-derives a fresh snapshot for active planning, and an inactive legacy\n\t * entry needs no restore beyond dropping exit_plan.\n\t */\n\ttoolSnapshot: string[];\n\t/** A compact-and-execute exit was approved and is waiting for agent_settled + compaction. */\n\tawaitingCompact: boolean;\n\t/** Absolute paths of plan files saved on this conversation branch. */\n\tplanFiles: string[];\n}\n\nexport const EMPTY_PLAN_STATE: PlanSessionState = {\n\tplanning: false,\n\ttoolSnapshot: [],\n\tawaitingCompact: false,\n\tplanFiles: [],\n};\n\nconst states = new Map<string, PlanSessionState>();\nlet activeSid = \"\";\n\n/** Point the module at a session's state bucket. Call wherever ctx is in hand. */\nexport function setActivePlanSession(sid: string): void {\n\tactiveSid = sid;\n}\n\nexport function getPlanState(): PlanSessionState {\n\treturn states.get(activeSid) ?? clonePlanState(EMPTY_PLAN_STATE);\n}\n\nexport function replacePlanState(next: PlanSessionState): void {\n\tstates.set(activeSid, clonePlanState(next));\n}\n\n/** Drop in-memory state for a session (call from session_shutdown). */\nexport function disposePlanSession(sid: string): void {\n\tif (!sid) return;\n\tstates.delete(sid);\n\tif (activeSid === sid) activeSid = \"\";\n}\n\nexport function clonePlanState(source: PlanSessionState): PlanSessionState {\n\treturn {\n\t\tplanning: source.planning,\n\t\ttoolSnapshot: [...source.toolSnapshot],\n\t\tawaitingCompact: source.awaitingCompact,\n\t\tplanFiles: [...source.planFiles],\n\t};\n}\n\n/**\n * Accepts both the current shape (`toolSnapshot`) and the legacy one\n * (`removedTools`), so branches recorded before the snapshot change replay.\n */\nexport function isPlanState(value: unknown): value is PlanSessionState {\n\tconst parsed = parsePlanState(value);\n\treturn parsed !== undefined;\n}\n\nfunction parsePlanState(value: unknown): PlanSessionState | undefined {\n\tif (!value || typeof value !== \"object\") return undefined;\n\tconst record = value as Record<string, unknown>;\n\tif (typeof record.planning !== \"boolean\" || typeof record.awaitingCompact !== \"boolean\") return undefined;\n\tif (!Array.isArray(record.planFiles)) return undefined;\n\tconst snapshot = Array.isArray(record.toolSnapshot)\n\t\t? record.toolSnapshot\n\t\t: Array.isArray(record.removedTools)\n\t\t\t? [] // Legacy entries recorded removals, not a snapshot; nothing to restore from.\n\t\t\t: undefined;\n\tif (!snapshot) return undefined;\n\treturn {\n\t\tplanning: record.planning,\n\t\ttoolSnapshot: snapshot.filter((tool): tool is string => typeof tool === \"string\"),\n\t\tawaitingCompact: record.awaitingCompact,\n\t\tplanFiles: record.planFiles.filter((path): path is string => typeof path === \"string\"),\n\t};\n}\n\n/**\n * Latest plan-mode entry on the current branch, or undefined when the branch\n * has none (lets callers distinguish \"never used\" from \"explicitly exited\").\n */\nexport function replayPlanFromBranch(ctx: {\n\tsessionManager: { getBranch(): Iterable<unknown> };\n}): PlanSessionState | undefined {\n\tconst branch = Array.from(ctx.sessionManager.getBranch());\n\tfor (let i = branch.length - 1; i >= 0; i--) {\n\t\tconst item = branch[i] as { type?: string; customType?: string; data?: unknown };\n\t\tif (item.type !== \"custom\" || item.customType !== PLAN_ENTRY_TYPE) continue;\n\t\tconst parsed = parsePlanState(item.data);\n\t\tif (!parsed) continue;\n\t\treturn parsed;\n\t}\n\treturn undefined;\n}\n"]}
@@ -14,7 +14,7 @@
14
14
  import { PLAN_ENTRY_TYPE } from "./constants.js";
15
15
  export const EMPTY_PLAN_STATE = {
16
16
  planning: false,
17
- removedTools: [],
17
+ toolSnapshot: [],
18
18
  awaitingCompact: false,
19
19
  planFiles: [],
20
20
  };
@@ -41,19 +41,40 @@ export function disposePlanSession(sid) {
41
41
  export function clonePlanState(source) {
42
42
  return {
43
43
  planning: source.planning,
44
- removedTools: [...source.removedTools],
44
+ toolSnapshot: [...source.toolSnapshot],
45
45
  awaitingCompact: source.awaitingCompact,
46
46
  planFiles: [...source.planFiles],
47
47
  };
48
48
  }
49
+ /**
50
+ * Accepts both the current shape (`toolSnapshot`) and the legacy one
51
+ * (`removedTools`), so branches recorded before the snapshot change replay.
52
+ */
49
53
  export function isPlanState(value) {
54
+ const parsed = parsePlanState(value);
55
+ return parsed !== undefined;
56
+ }
57
+ function parsePlanState(value) {
50
58
  if (!value || typeof value !== "object")
51
- return false;
59
+ return undefined;
52
60
  const record = value;
53
- return (typeof record.planning === "boolean" &&
54
- typeof record.awaitingCompact === "boolean" &&
55
- Array.isArray(record.removedTools) &&
56
- Array.isArray(record.planFiles));
61
+ if (typeof record.planning !== "boolean" || typeof record.awaitingCompact !== "boolean")
62
+ return undefined;
63
+ if (!Array.isArray(record.planFiles))
64
+ return undefined;
65
+ const snapshot = Array.isArray(record.toolSnapshot)
66
+ ? record.toolSnapshot
67
+ : Array.isArray(record.removedTools)
68
+ ? [] // Legacy entries recorded removals, not a snapshot; nothing to restore from.
69
+ : undefined;
70
+ if (!snapshot)
71
+ return undefined;
72
+ return {
73
+ planning: record.planning,
74
+ toolSnapshot: snapshot.filter((tool) => typeof tool === "string"),
75
+ awaitingCompact: record.awaitingCompact,
76
+ planFiles: record.planFiles.filter((path) => typeof path === "string"),
77
+ };
57
78
  }
58
79
  /**
59
80
  * Latest plan-mode entry on the current branch, or undefined when the branch
@@ -65,9 +86,10 @@ export function replayPlanFromBranch(ctx) {
65
86
  const item = branch[i];
66
87
  if (item.type !== "custom" || item.customType !== PLAN_ENTRY_TYPE)
67
88
  continue;
68
- if (!isPlanState(item.data))
89
+ const parsed = parsePlanState(item.data);
90
+ if (!parsed)
69
91
  continue;
70
- return clonePlanState(item.data);
92
+ return parsed;
71
93
  }
72
94
  return undefined;
73
95
  }
@@ -1 +1 @@
1
- {"version":3,"file":"state.js","sourceRoot":"","sources":["../../../src/extensions/plan/state.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAajD,MAAM,CAAC,MAAM,gBAAgB,GAAqB;IACjD,QAAQ,EAAE,KAAK;IACf,YAAY,EAAE,EAAE;IAChB,eAAe,EAAE,KAAK;IACtB,SAAS,EAAE,EAAE;CACb,CAAC;AAEF,MAAM,MAAM,GAAG,IAAI,GAAG,EAA4B,CAAC;AACnD,IAAI,SAAS,GAAG,EAAE,CAAC;AAEnB,kFAAkF;AAClF,MAAM,UAAU,oBAAoB,CAAC,GAAW,EAAQ;IACvD,SAAS,GAAG,GAAG,CAAC;AAAA,CAChB;AAED,MAAM,UAAU,YAAY,GAAqB;IAChD,OAAO,MAAM,CAAC,GAAG,CAAC,SAAS,CAAC,IAAI,cAAc,CAAC,gBAAgB,CAAC,CAAC;AAAA,CACjE;AAED,MAAM,UAAU,gBAAgB,CAAC,IAAsB,EAAQ;IAC9D,MAAM,CAAC,GAAG,CAAC,SAAS,EAAE,cAAc,CAAC,IAAI,CAAC,CAAC,CAAC;AAAA,CAC5C;AAED,uEAAuE;AACvE,MAAM,UAAU,kBAAkB,CAAC,GAAW,EAAQ;IACrD,IAAI,CAAC,GAAG;QAAE,OAAO;IACjB,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IACnB,IAAI,SAAS,KAAK,GAAG;QAAE,SAAS,GAAG,EAAE,CAAC;AAAA,CACtC;AAED,MAAM,UAAU,cAAc,CAAC,MAAwB,EAAoB;IAC1E,OAAO;QACN,QAAQ,EAAE,MAAM,CAAC,QAAQ;QACzB,YAAY,EAAE,CAAC,GAAG,MAAM,CAAC,YAAY,CAAC;QACtC,eAAe,EAAE,MAAM,CAAC,eAAe;QACvC,SAAS,EAAE,CAAC,GAAG,MAAM,CAAC,SAAS,CAAC;KAChC,CAAC;AAAA,CACF;AAED,MAAM,UAAU,WAAW,CAAC,KAAc,EAA6B;IACtE,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAC;IACtD,MAAM,MAAM,GAAG,KAAgC,CAAC;IAChD,OAAO,CACN,OAAO,MAAM,CAAC,QAAQ,KAAK,SAAS;QACpC,OAAO,MAAM,CAAC,eAAe,KAAK,SAAS;QAC3C,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,YAAY,CAAC;QAClC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,SAAS,CAAC,CAC/B,CAAC;AAAA,CACF;AAED;;;GAGG;AACH,MAAM,UAAU,oBAAoB,CAAC,GAEpC,EAAgC;IAChC,MAAM,MAAM,GAAG,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,cAAc,CAAC,SAAS,EAAE,CAAC,CAAC;IAC1D,KAAK,IAAI,CAAC,GAAG,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;QAC7C,MAAM,IAAI,GAAG,MAAM,CAAC,CAAC,CAA2D,CAAC;QACjF,IAAI,IAAI,CAAC,IAAI,KAAK,QAAQ,IAAI,IAAI,CAAC,UAAU,KAAK,eAAe;YAAE,SAAS;QAC5E,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC;YAAE,SAAS;QACtC,OAAO,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAClC,CAAC;IACD,OAAO,SAAS,CAAC;AAAA,CACjB","sourcesContent":["/**\n * plan/state.ts — per-session plan-mode state, persisted as custom entries.\n *\n * State is conversation-backed like the todo extension: every transition is\n * appended as a `plan-mode` custom entry (never sent to the LLM), and lifecycle\n * handlers replay the current branch tail → head to restore. Custom entries are\n * copied on fork/clone, so `planFiles` keeps pointing at the right plan files\n * even though the forked session gets a new session id.\n *\n * State is keyed per session id: one Pi process can host several sessions over\n * its lifetime (resume, /tree switches), so callers re-point the active bucket\n * wherever ctx is in hand.\n */\nimport { PLAN_ENTRY_TYPE } from \"./constants.ts\";\n\nexport interface PlanSessionState {\n\t/** True from entry until the exit is fully applied (including a pending compaction). */\n\tplanning: boolean;\n\t/** Tools actually removed on entry; restored verbatim on exit. */\n\tremovedTools: string[];\n\t/** A compact-and-execute exit was approved and is waiting for agent_settled + compaction. */\n\tawaitingCompact: boolean;\n\t/** Absolute paths of plan files saved on this conversation branch. */\n\tplanFiles: string[];\n}\n\nexport const EMPTY_PLAN_STATE: PlanSessionState = {\n\tplanning: false,\n\tremovedTools: [],\n\tawaitingCompact: false,\n\tplanFiles: [],\n};\n\nconst states = new Map<string, PlanSessionState>();\nlet activeSid = \"\";\n\n/** Point the module at a session's state bucket. Call wherever ctx is in hand. */\nexport function setActivePlanSession(sid: string): void {\n\tactiveSid = sid;\n}\n\nexport function getPlanState(): PlanSessionState {\n\treturn states.get(activeSid) ?? clonePlanState(EMPTY_PLAN_STATE);\n}\n\nexport function replacePlanState(next: PlanSessionState): void {\n\tstates.set(activeSid, clonePlanState(next));\n}\n\n/** Drop in-memory state for a session (call from session_shutdown). */\nexport function disposePlanSession(sid: string): void {\n\tif (!sid) return;\n\tstates.delete(sid);\n\tif (activeSid === sid) activeSid = \"\";\n}\n\nexport function clonePlanState(source: PlanSessionState): PlanSessionState {\n\treturn {\n\t\tplanning: source.planning,\n\t\tremovedTools: [...source.removedTools],\n\t\tawaitingCompact: source.awaitingCompact,\n\t\tplanFiles: [...source.planFiles],\n\t};\n}\n\nexport function isPlanState(value: unknown): value is PlanSessionState {\n\tif (!value || typeof value !== \"object\") return false;\n\tconst record = value as Record<string, unknown>;\n\treturn (\n\t\ttypeof record.planning === \"boolean\" &&\n\t\ttypeof record.awaitingCompact === \"boolean\" &&\n\t\tArray.isArray(record.removedTools) &&\n\t\tArray.isArray(record.planFiles)\n\t);\n}\n\n/**\n * Latest plan-mode entry on the current branch, or undefined when the branch\n * has none (lets callers distinguish \"never used\" from \"explicitly exited\").\n */\nexport function replayPlanFromBranch(ctx: {\n\tsessionManager: { getBranch(): Iterable<unknown> };\n}): PlanSessionState | undefined {\n\tconst branch = Array.from(ctx.sessionManager.getBranch());\n\tfor (let i = branch.length - 1; i >= 0; i--) {\n\t\tconst item = branch[i] as { type?: string; customType?: string; data?: unknown };\n\t\tif (item.type !== \"custom\" || item.customType !== PLAN_ENTRY_TYPE) continue;\n\t\tif (!isPlanState(item.data)) continue;\n\t\treturn clonePlanState(item.data);\n\t}\n\treturn undefined;\n}\n"]}
1
+ {"version":3,"file":"state.js","sourceRoot":"","sources":["../../../src/extensions/plan/state.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * plan/state.ts — per-session plan-mode state, persisted as custom entries.\n *\n * State is conversation-backed like the todo extension: every transition is\n * appended as a `plan-mode` custom entry (never sent to the LLM), and lifecycle\n * handlers replay the current branch tail → head to restore. Custom entries are\n * copied on fork/clone, so `planFiles` keeps pointing at the right plan files\n * even though the forked session gets a new session id.\n *\n * State is keyed per session id: one Pi process can host several sessions over\n * its lifetime (resume, /tree switches), so callers re-point the active bucket\n * wherever ctx is in hand.\n */\nimport { PLAN_ENTRY_TYPE } from \"./constants.ts\";\n\nexport interface PlanSessionState {\n\t/** True from entry until the exit is fully applied (including a pending compaction). */\n\tplanning: boolean;\n\t/**\n\t * Full active-tool set snapshotted on entry; restored verbatim on exit.\n\t * Empty for legacy entries (pre-snapshot `removedTools` shape) — replay\n\t * re-derives a fresh snapshot for active planning, and an inactive legacy\n\t * entry needs no restore beyond dropping exit_plan.\n\t */\n\ttoolSnapshot: string[];\n\t/** A compact-and-execute exit was approved and is waiting for agent_settled + compaction. */\n\tawaitingCompact: boolean;\n\t/** Absolute paths of plan files saved on this conversation branch. */\n\tplanFiles: string[];\n}\n\nexport const EMPTY_PLAN_STATE: PlanSessionState = {\n\tplanning: false,\n\ttoolSnapshot: [],\n\tawaitingCompact: false,\n\tplanFiles: [],\n};\n\nconst states = new Map<string, PlanSessionState>();\nlet activeSid = \"\";\n\n/** Point the module at a session's state bucket. Call wherever ctx is in hand. */\nexport function setActivePlanSession(sid: string): void {\n\tactiveSid = sid;\n}\n\nexport function getPlanState(): PlanSessionState {\n\treturn states.get(activeSid) ?? clonePlanState(EMPTY_PLAN_STATE);\n}\n\nexport function replacePlanState(next: PlanSessionState): void {\n\tstates.set(activeSid, clonePlanState(next));\n}\n\n/** Drop in-memory state for a session (call from session_shutdown). */\nexport function disposePlanSession(sid: string): void {\n\tif (!sid) return;\n\tstates.delete(sid);\n\tif (activeSid === sid) activeSid = \"\";\n}\n\nexport function clonePlanState(source: PlanSessionState): PlanSessionState {\n\treturn {\n\t\tplanning: source.planning,\n\t\ttoolSnapshot: [...source.toolSnapshot],\n\t\tawaitingCompact: source.awaitingCompact,\n\t\tplanFiles: [...source.planFiles],\n\t};\n}\n\n/**\n * Accepts both the current shape (`toolSnapshot`) and the legacy one\n * (`removedTools`), so branches recorded before the snapshot change replay.\n */\nexport function isPlanState(value: unknown): value is PlanSessionState {\n\tconst parsed = parsePlanState(value);\n\treturn parsed !== undefined;\n}\n\nfunction parsePlanState(value: unknown): PlanSessionState | undefined {\n\tif (!value || typeof value !== \"object\") return undefined;\n\tconst record = value as Record<string, unknown>;\n\tif (typeof record.planning !== \"boolean\" || typeof record.awaitingCompact !== \"boolean\") return undefined;\n\tif (!Array.isArray(record.planFiles)) return undefined;\n\tconst snapshot = Array.isArray(record.toolSnapshot)\n\t\t? record.toolSnapshot\n\t\t: Array.isArray(record.removedTools)\n\t\t\t? [] // Legacy entries recorded removals, not a snapshot; nothing to restore from.\n\t\t\t: undefined;\n\tif (!snapshot) return undefined;\n\treturn {\n\t\tplanning: record.planning,\n\t\ttoolSnapshot: snapshot.filter((tool): tool is string => typeof tool === \"string\"),\n\t\tawaitingCompact: record.awaitingCompact,\n\t\tplanFiles: record.planFiles.filter((path): path is string => typeof path === \"string\"),\n\t};\n}\n\n/**\n * Latest plan-mode entry on the current branch, or undefined when the branch\n * has none (lets callers distinguish \"never used\" from \"explicitly exited\").\n */\nexport function replayPlanFromBranch(ctx: {\n\tsessionManager: { getBranch(): Iterable<unknown> };\n}): PlanSessionState | undefined {\n\tconst branch = Array.from(ctx.sessionManager.getBranch());\n\tfor (let i = branch.length - 1; i >= 0; i--) {\n\t\tconst item = branch[i] as { type?: string; customType?: string; data?: unknown };\n\t\tif (item.type !== \"custom\" || item.customType !== PLAN_ENTRY_TYPE) continue;\n\t\tconst parsed = parsePlanState(item.data);\n\t\tif (!parsed) continue;\n\t\treturn parsed;\n\t}\n\treturn undefined;\n}\n"]}
@@ -5,14 +5,22 @@ export interface SavePlanOptions {
5
5
  cwd: string;
6
6
  revises?: string;
7
7
  }
8
- export declare function getPlansDir(sessionId: string): string;
8
+ /** Per-project plans directory for a cwd (same encoding as sessions). */
9
+ export declare function getPlansDir(cwd: string): string;
9
10
  /** File-name slug from a plan title: lowercase, ascii-ish, bounded. */
10
11
  export declare function slugifyTitle(title: string): string;
11
- /** Next NN based on existing `NN-*.md` names; gaps never reuse numbers. */
12
- export declare function nextPlanNumber(existingNames: string[]): number;
12
+ /** Local-time `YYYYMMDD-HHmm` prefix: sortable and collision-poor per project. */
13
+ export declare function formatPlanFileStamp(date: Date): string;
13
14
  export declare function buildPlanDocument(options: SavePlanOptions & {
14
15
  createdIso: string;
15
16
  }): string;
17
+ /** First non-existing `<base>.md`, `<base>-2.md`, ... path in dir. */
18
+ export declare function resolvePlanFileName(dir: string, base: string): string;
19
+ /**
20
+ * Absolute paths of plans saved for this project, newest first (the
21
+ * `YYYYMMDD-HHmm` prefix makes file names sort chronologically).
22
+ */
23
+ export declare function listProjectPlanFiles(cwd: string): string[];
16
24
  /** Write the plan to disk and return its absolute path. */
17
25
  export declare function savePlanFile(options: SavePlanOptions): string;
18
26
  //# sourceMappingURL=storage.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../../../src/extensions/plan/storage.ts"],"names":[],"mappings":"AAYA,MAAM,WAAW,eAAe;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,wBAAgB,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAErD;AAED,uEAAuE;AACvE,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAQlD;AAED,2EAA2E;AAC3E,wBAAgB,cAAc,CAAC,aAAa,EAAE,MAAM,EAAE,GAAG,MAAM,CAO9D;AAED,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,eAAe,GAAG;IAAE,UAAU,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,CAW3F;AAED,2DAA2D;AAC3D,wBAAgB,YAAY,CAAC,OAAO,EAAE,eAAe,GAAG,MAAM,CAQ7D","sourcesContent":["/**\n * plan/storage.ts — plan file layout and writing.\n *\n * Plans live under `<agentDir>/plans/<sessionId>/NN-<slug>.md`, append-only\n * with a monotonically increasing NN. The directory is only the initial\n * storage location — the authoritative index is the `planFiles` list in the\n * session's plan-mode entries, which follows fork/clone.\n */\nimport { mkdirSync, readdirSync, writeFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { getAgentDir } from \"../../config.ts\";\n\nexport interface SavePlanOptions {\n\tsessionId: string;\n\ttitle: string;\n\tplan: string;\n\tcwd: string;\n\trevises?: string;\n}\n\nexport function getPlansDir(sessionId: string): string {\n\treturn join(getAgentDir(), \"plans\", sessionId);\n}\n\n/** File-name slug from a plan title: lowercase, ascii-ish, bounded. */\nexport function slugifyTitle(title: string): string {\n\tconst slug = title\n\t\t.toLowerCase()\n\t\t.replace(/[^a-z0-9]+/g, \"-\")\n\t\t.replace(/^-+|-+$/g, \"\")\n\t\t.slice(0, 40)\n\t\t.replace(/-+$/, \"\");\n\treturn slug || \"plan\";\n}\n\n/** Next NN based on existing `NN-*.md` names; gaps never reuse numbers. */\nexport function nextPlanNumber(existingNames: string[]): number {\n\tlet max = 0;\n\tfor (const name of existingNames) {\n\t\tconst match = /^(\\d+)-/.exec(name);\n\t\tif (match) max = Math.max(max, Number(match[1]));\n\t}\n\treturn max + 1;\n}\n\nexport function buildPlanDocument(options: SavePlanOptions & { createdIso: string }): string {\n\tconst lines = [\n\t\t\"---\",\n\t\t`title: ${JSON.stringify(options.title)}`,\n\t\t`created: ${options.createdIso}`,\n\t\t`cwd: ${JSON.stringify(options.cwd)}`,\n\t\t`session: ${options.sessionId}`,\n\t];\n\tif (options.revises) lines.push(`revises: ${JSON.stringify(options.revises)}`);\n\tlines.push(\"---\", \"\", options.plan.trimEnd(), \"\");\n\treturn lines.join(\"\\n\");\n}\n\n/** Write the plan to disk and return its absolute path. */\nexport function savePlanFile(options: SavePlanOptions): string {\n\tconst dir = getPlansDir(options.sessionId);\n\tmkdirSync(dir, { recursive: true });\n\tconst number = nextPlanNumber(readdirSync(dir));\n\tconst fileName = `${String(number).padStart(2, \"0\")}-${slugifyTitle(options.title)}.md`;\n\tconst path = join(dir, fileName);\n\twriteFileSync(path, buildPlanDocument({ ...options, createdIso: new Date().toISOString() }), \"utf-8\");\n\treturn path;\n}\n"]}
1
+ {"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../../../src/extensions/plan/storage.ts"],"names":[],"mappings":"AAeA,MAAM,WAAW,eAAe;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,yEAAyE;AACzE,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAE/C;AAED,uEAAuE;AACvE,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAQlD;AAED,kFAAkF;AAClF,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,CAGtD;AAED,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,eAAe,GAAG;IAAE,UAAU,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,CAW3F;AAED,sEAAsE;AACtE,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAMrE;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,CAY1D;AAED,2DAA2D;AAC3D,wBAAgB,YAAY,CAAC,OAAO,EAAE,eAAe,GAAG,MAAM,CAQ7D","sourcesContent":["/**\n * plan/storage.ts — plan file layout and writing.\n *\n * Plans live under `<agentDir>/plans/<safeCwdDir>/<YYYYMMDD-HHmm>-<slug>.md`,\n * grouped by project the same way sessions are (cwdToSafeDirName). The\n * directory is only the initial storage location — the authoritative index is\n * the `planFiles` list in the session's plan-mode entries, which follows\n * fork/clone. Plans saved before the per-project layout stay in their legacy\n * `plans/<sessionId>/` directories and remain readable via those entries.\n */\nimport { existsSync, mkdirSync, readdirSync, writeFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { getAgentDir } from \"../../config.ts\";\nimport { cwdToSafeDirName, resolvePath } from \"../../utils/paths.ts\";\n\nexport interface SavePlanOptions {\n\tsessionId: string;\n\ttitle: string;\n\tplan: string;\n\tcwd: string;\n\trevises?: string;\n}\n\n/** Per-project plans directory for a cwd (same encoding as sessions). */\nexport function getPlansDir(cwd: string): string {\n\treturn join(getAgentDir(), \"plans\", cwdToSafeDirName(resolvePath(cwd)));\n}\n\n/** File-name slug from a plan title: lowercase, ascii-ish, bounded. */\nexport function slugifyTitle(title: string): string {\n\tconst slug = title\n\t\t.toLowerCase()\n\t\t.replace(/[^a-z0-9]+/g, \"-\")\n\t\t.replace(/^-+|-+$/g, \"\")\n\t\t.slice(0, 40)\n\t\t.replace(/-+$/, \"\");\n\treturn slug || \"plan\";\n}\n\n/** Local-time `YYYYMMDD-HHmm` prefix: sortable and collision-poor per project. */\nexport function formatPlanFileStamp(date: Date): string {\n\tconst pad = (value: number) => String(value).padStart(2, \"0\");\n\treturn `${date.getFullYear()}${pad(date.getMonth() + 1)}${pad(date.getDate())}-${pad(date.getHours())}${pad(date.getMinutes())}`;\n}\n\nexport function buildPlanDocument(options: SavePlanOptions & { createdIso: string }): string {\n\tconst lines = [\n\t\t\"---\",\n\t\t`title: ${JSON.stringify(options.title)}`,\n\t\t`created: ${options.createdIso}`,\n\t\t`cwd: ${JSON.stringify(options.cwd)}`,\n\t\t`session: ${options.sessionId}`,\n\t];\n\tif (options.revises) lines.push(`revises: ${JSON.stringify(options.revises)}`);\n\tlines.push(\"---\", \"\", options.plan.trimEnd(), \"\");\n\treturn lines.join(\"\\n\");\n}\n\n/** First non-existing `<base>.md`, `<base>-2.md`, ... path in dir. */\nexport function resolvePlanFileName(dir: string, base: string): string {\n\tlet candidate = `${base}.md`;\n\tfor (let attempt = 2; existsSync(join(dir, candidate)); attempt++) {\n\t\tcandidate = `${base}-${attempt}.md`;\n\t}\n\treturn candidate;\n}\n\n/**\n * Absolute paths of plans saved for this project, newest first (the\n * `YYYYMMDD-HHmm` prefix makes file names sort chronologically).\n */\nexport function listProjectPlanFiles(cwd: string): string[] {\n\tconst dir = getPlansDir(cwd);\n\tlet names: string[];\n\ttry {\n\t\tnames = readdirSync(dir);\n\t} catch {\n\t\treturn [];\n\t}\n\treturn names\n\t\t.filter((name) => name.endsWith(\".md\"))\n\t\t.sort((left, right) => right.localeCompare(left))\n\t\t.map((name) => join(dir, name));\n}\n\n/** Write the plan to disk and return its absolute path. */\nexport function savePlanFile(options: SavePlanOptions): string {\n\tconst dir = getPlansDir(options.cwd);\n\tmkdirSync(dir, { recursive: true });\n\tconst now = new Date();\n\tconst fileName = resolvePlanFileName(dir, `${formatPlanFileStamp(now)}-${slugifyTitle(options.title)}`);\n\tconst path = join(dir, fileName);\n\twriteFileSync(path, buildPlanDocument({ ...options, createdIso: now.toISOString() }), \"utf-8\");\n\treturn path;\n}\n"]}
@@ -1,16 +1,20 @@
1
1
  /**
2
2
  * plan/storage.ts — plan file layout and writing.
3
3
  *
4
- * Plans live under `<agentDir>/plans/<sessionId>/NN-<slug>.md`, append-only
5
- * with a monotonically increasing NN. The directory is only the initial
6
- * storage location — the authoritative index is the `planFiles` list in the
7
- * session's plan-mode entries, which follows fork/clone.
4
+ * Plans live under `<agentDir>/plans/<safeCwdDir>/<YYYYMMDD-HHmm>-<slug>.md`,
5
+ * grouped by project the same way sessions are (cwdToSafeDirName). The
6
+ * directory is only the initial storage location — the authoritative index is
7
+ * the `planFiles` list in the session's plan-mode entries, which follows
8
+ * fork/clone. Plans saved before the per-project layout stay in their legacy
9
+ * `plans/<sessionId>/` directories and remain readable via those entries.
8
10
  */
9
- import { mkdirSync, readdirSync, writeFileSync } from "node:fs";
11
+ import { existsSync, mkdirSync, readdirSync, writeFileSync } from "node:fs";
10
12
  import { join } from "node:path";
11
13
  import { getAgentDir } from "../../config.js";
12
- export function getPlansDir(sessionId) {
13
- return join(getAgentDir(), "plans", sessionId);
14
+ import { cwdToSafeDirName, resolvePath } from "../../utils/paths.js";
15
+ /** Per-project plans directory for a cwd (same encoding as sessions). */
16
+ export function getPlansDir(cwd) {
17
+ return join(getAgentDir(), "plans", cwdToSafeDirName(resolvePath(cwd)));
14
18
  }
15
19
  /** File-name slug from a plan title: lowercase, ascii-ish, bounded. */
16
20
  export function slugifyTitle(title) {
@@ -22,15 +26,10 @@ export function slugifyTitle(title) {
22
26
  .replace(/-+$/, "");
23
27
  return slug || "plan";
24
28
  }
25
- /** Next NN based on existing `NN-*.md` names; gaps never reuse numbers. */
26
- export function nextPlanNumber(existingNames) {
27
- let max = 0;
28
- for (const name of existingNames) {
29
- const match = /^(\d+)-/.exec(name);
30
- if (match)
31
- max = Math.max(max, Number(match[1]));
32
- }
33
- return max + 1;
29
+ /** Local-time `YYYYMMDD-HHmm` prefix: sortable and collision-poor per project. */
30
+ export function formatPlanFileStamp(date) {
31
+ const pad = (value) => String(value).padStart(2, "0");
32
+ return `${date.getFullYear()}${pad(date.getMonth() + 1)}${pad(date.getDate())}-${pad(date.getHours())}${pad(date.getMinutes())}`;
34
33
  }
35
34
  export function buildPlanDocument(options) {
36
35
  const lines = [
@@ -45,14 +44,40 @@ export function buildPlanDocument(options) {
45
44
  lines.push("---", "", options.plan.trimEnd(), "");
46
45
  return lines.join("\n");
47
46
  }
47
+ /** First non-existing `<base>.md`, `<base>-2.md`, ... path in dir. */
48
+ export function resolvePlanFileName(dir, base) {
49
+ let candidate = `${base}.md`;
50
+ for (let attempt = 2; existsSync(join(dir, candidate)); attempt++) {
51
+ candidate = `${base}-${attempt}.md`;
52
+ }
53
+ return candidate;
54
+ }
55
+ /**
56
+ * Absolute paths of plans saved for this project, newest first (the
57
+ * `YYYYMMDD-HHmm` prefix makes file names sort chronologically).
58
+ */
59
+ export function listProjectPlanFiles(cwd) {
60
+ const dir = getPlansDir(cwd);
61
+ let names;
62
+ try {
63
+ names = readdirSync(dir);
64
+ }
65
+ catch {
66
+ return [];
67
+ }
68
+ return names
69
+ .filter((name) => name.endsWith(".md"))
70
+ .sort((left, right) => right.localeCompare(left))
71
+ .map((name) => join(dir, name));
72
+ }
48
73
  /** Write the plan to disk and return its absolute path. */
49
74
  export function savePlanFile(options) {
50
- const dir = getPlansDir(options.sessionId);
75
+ const dir = getPlansDir(options.cwd);
51
76
  mkdirSync(dir, { recursive: true });
52
- const number = nextPlanNumber(readdirSync(dir));
53
- const fileName = `${String(number).padStart(2, "0")}-${slugifyTitle(options.title)}.md`;
77
+ const now = new Date();
78
+ const fileName = resolvePlanFileName(dir, `${formatPlanFileStamp(now)}-${slugifyTitle(options.title)}`);
54
79
  const path = join(dir, fileName);
55
- writeFileSync(path, buildPlanDocument({ ...options, createdIso: new Date().toISOString() }), "utf-8");
80
+ writeFileSync(path, buildPlanDocument({ ...options, createdIso: now.toISOString() }), "utf-8");
56
81
  return path;
57
82
  }
58
83
  //# sourceMappingURL=storage.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"storage.js","sourceRoot":"","sources":["../../../src/extensions/plan/storage.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAChE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAU9C,MAAM,UAAU,WAAW,CAAC,SAAiB,EAAU;IACtD,OAAO,IAAI,CAAC,WAAW,EAAE,EAAE,OAAO,EAAE,SAAS,CAAC,CAAC;AAAA,CAC/C;AAED,uEAAuE;AACvE,MAAM,UAAU,YAAY,CAAC,KAAa,EAAU;IACnD,MAAM,IAAI,GAAG,KAAK;SAChB,WAAW,EAAE;SACb,OAAO,CAAC,aAAa,EAAE,GAAG,CAAC;SAC3B,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC;SACvB,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC;SACZ,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;IACrB,OAAO,IAAI,IAAI,MAAM,CAAC;AAAA,CACtB;AAED,2EAA2E;AAC3E,MAAM,UAAU,cAAc,CAAC,aAAuB,EAAU;IAC/D,IAAI,GAAG,GAAG,CAAC,CAAC;IACZ,KAAK,MAAM,IAAI,IAAI,aAAa,EAAE,CAAC;QAClC,MAAM,KAAK,GAAG,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACnC,IAAI,KAAK;YAAE,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAClD,CAAC;IACD,OAAO,GAAG,GAAG,CAAC,CAAC;AAAA,CACf;AAED,MAAM,UAAU,iBAAiB,CAAC,OAAiD,EAAU;IAC5F,MAAM,KAAK,GAAG;QACb,KAAK;QACL,UAAU,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE;QACzC,YAAY,OAAO,CAAC,UAAU,EAAE;QAChC,QAAQ,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE;QACrC,YAAY,OAAO,CAAC,SAAS,EAAE;KAC/B,CAAC;IACF,IAAI,OAAO,CAAC,OAAO;QAAE,KAAK,CAAC,IAAI,CAAC,YAAY,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;IAC/E,KAAK,CAAC,IAAI,CAAC,KAAK,EAAE,EAAE,EAAE,OAAO,CAAC,IAAI,CAAC,OAAO,EAAE,EAAE,EAAE,CAAC,CAAC;IAClD,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAAA,CACxB;AAED,2DAA2D;AAC3D,MAAM,UAAU,YAAY,CAAC,OAAwB,EAAU;IAC9D,MAAM,GAAG,GAAG,WAAW,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAC3C,SAAS,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACpC,MAAM,MAAM,GAAG,cAAc,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC;IAChD,MAAM,QAAQ,GAAG,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,IAAI,YAAY,CAAC,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC;IACxF,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC;IACjC,aAAa,CAAC,IAAI,EAAE,iBAAiB,CAAC,EAAE,GAAG,OAAO,EAAE,UAAU,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,EAAE,CAAC,EAAE,OAAO,CAAC,CAAC;IACtG,OAAO,IAAI,CAAC;AAAA,CACZ","sourcesContent":["/**\n * plan/storage.ts — plan file layout and writing.\n *\n * Plans live under `<agentDir>/plans/<sessionId>/NN-<slug>.md`, append-only\n * with a monotonically increasing NN. The directory is only the initial\n * storage location — the authoritative index is the `planFiles` list in the\n * session's plan-mode entries, which follows fork/clone.\n */\nimport { mkdirSync, readdirSync, writeFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { getAgentDir } from \"../../config.ts\";\n\nexport interface SavePlanOptions {\n\tsessionId: string;\n\ttitle: string;\n\tplan: string;\n\tcwd: string;\n\trevises?: string;\n}\n\nexport function getPlansDir(sessionId: string): string {\n\treturn join(getAgentDir(), \"plans\", sessionId);\n}\n\n/** File-name slug from a plan title: lowercase, ascii-ish, bounded. */\nexport function slugifyTitle(title: string): string {\n\tconst slug = title\n\t\t.toLowerCase()\n\t\t.replace(/[^a-z0-9]+/g, \"-\")\n\t\t.replace(/^-+|-+$/g, \"\")\n\t\t.slice(0, 40)\n\t\t.replace(/-+$/, \"\");\n\treturn slug || \"plan\";\n}\n\n/** Next NN based on existing `NN-*.md` names; gaps never reuse numbers. */\nexport function nextPlanNumber(existingNames: string[]): number {\n\tlet max = 0;\n\tfor (const name of existingNames) {\n\t\tconst match = /^(\\d+)-/.exec(name);\n\t\tif (match) max = Math.max(max, Number(match[1]));\n\t}\n\treturn max + 1;\n}\n\nexport function buildPlanDocument(options: SavePlanOptions & { createdIso: string }): string {\n\tconst lines = [\n\t\t\"---\",\n\t\t`title: ${JSON.stringify(options.title)}`,\n\t\t`created: ${options.createdIso}`,\n\t\t`cwd: ${JSON.stringify(options.cwd)}`,\n\t\t`session: ${options.sessionId}`,\n\t];\n\tif (options.revises) lines.push(`revises: ${JSON.stringify(options.revises)}`);\n\tlines.push(\"---\", \"\", options.plan.trimEnd(), \"\");\n\treturn lines.join(\"\\n\");\n}\n\n/** Write the plan to disk and return its absolute path. */\nexport function savePlanFile(options: SavePlanOptions): string {\n\tconst dir = getPlansDir(options.sessionId);\n\tmkdirSync(dir, { recursive: true });\n\tconst number = nextPlanNumber(readdirSync(dir));\n\tconst fileName = `${String(number).padStart(2, \"0\")}-${slugifyTitle(options.title)}.md`;\n\tconst path = join(dir, fileName);\n\twriteFileSync(path, buildPlanDocument({ ...options, createdIso: new Date().toISOString() }), \"utf-8\");\n\treturn path;\n}\n"]}
1
+ {"version":3,"file":"storage.js","sourceRoot":"","sources":["../../../src/extensions/plan/storage.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * plan/storage.ts — plan file layout and writing.\n *\n * Plans live under `<agentDir>/plans/<safeCwdDir>/<YYYYMMDD-HHmm>-<slug>.md`,\n * grouped by project the same way sessions are (cwdToSafeDirName). The\n * directory is only the initial storage location — the authoritative index is\n * the `planFiles` list in the session's plan-mode entries, which follows\n * fork/clone. Plans saved before the per-project layout stay in their legacy\n * `plans/<sessionId>/` directories and remain readable via those entries.\n */\nimport { existsSync, mkdirSync, readdirSync, writeFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { getAgentDir } from \"../../config.ts\";\nimport { cwdToSafeDirName, resolvePath } from \"../../utils/paths.ts\";\n\nexport interface SavePlanOptions {\n\tsessionId: string;\n\ttitle: string;\n\tplan: string;\n\tcwd: string;\n\trevises?: string;\n}\n\n/** Per-project plans directory for a cwd (same encoding as sessions). */\nexport function getPlansDir(cwd: string): string {\n\treturn join(getAgentDir(), \"plans\", cwdToSafeDirName(resolvePath(cwd)));\n}\n\n/** File-name slug from a plan title: lowercase, ascii-ish, bounded. */\nexport function slugifyTitle(title: string): string {\n\tconst slug = title\n\t\t.toLowerCase()\n\t\t.replace(/[^a-z0-9]+/g, \"-\")\n\t\t.replace(/^-+|-+$/g, \"\")\n\t\t.slice(0, 40)\n\t\t.replace(/-+$/, \"\");\n\treturn slug || \"plan\";\n}\n\n/** Local-time `YYYYMMDD-HHmm` prefix: sortable and collision-poor per project. */\nexport function formatPlanFileStamp(date: Date): string {\n\tconst pad = (value: number) => String(value).padStart(2, \"0\");\n\treturn `${date.getFullYear()}${pad(date.getMonth() + 1)}${pad(date.getDate())}-${pad(date.getHours())}${pad(date.getMinutes())}`;\n}\n\nexport function buildPlanDocument(options: SavePlanOptions & { createdIso: string }): string {\n\tconst lines = [\n\t\t\"---\",\n\t\t`title: ${JSON.stringify(options.title)}`,\n\t\t`created: ${options.createdIso}`,\n\t\t`cwd: ${JSON.stringify(options.cwd)}`,\n\t\t`session: ${options.sessionId}`,\n\t];\n\tif (options.revises) lines.push(`revises: ${JSON.stringify(options.revises)}`);\n\tlines.push(\"---\", \"\", options.plan.trimEnd(), \"\");\n\treturn lines.join(\"\\n\");\n}\n\n/** First non-existing `<base>.md`, `<base>-2.md`, ... path in dir. */\nexport function resolvePlanFileName(dir: string, base: string): string {\n\tlet candidate = `${base}.md`;\n\tfor (let attempt = 2; existsSync(join(dir, candidate)); attempt++) {\n\t\tcandidate = `${base}-${attempt}.md`;\n\t}\n\treturn candidate;\n}\n\n/**\n * Absolute paths of plans saved for this project, newest first (the\n * `YYYYMMDD-HHmm` prefix makes file names sort chronologically).\n */\nexport function listProjectPlanFiles(cwd: string): string[] {\n\tconst dir = getPlansDir(cwd);\n\tlet names: string[];\n\ttry {\n\t\tnames = readdirSync(dir);\n\t} catch {\n\t\treturn [];\n\t}\n\treturn names\n\t\t.filter((name) => name.endsWith(\".md\"))\n\t\t.sort((left, right) => right.localeCompare(left))\n\t\t.map((name) => join(dir, name));\n}\n\n/** Write the plan to disk and return its absolute path. */\nexport function savePlanFile(options: SavePlanOptions): string {\n\tconst dir = getPlansDir(options.cwd);\n\tmkdirSync(dir, { recursive: true });\n\tconst now = new Date();\n\tconst fileName = resolvePlanFileName(dir, `${formatPlanFileStamp(now)}-${slugifyTitle(options.title)}`);\n\tconst path = join(dir, fileName);\n\twriteFileSync(path, buildPlanDocument({ ...options, createdIso: now.toISOString() }), \"utf-8\");\n\treturn path;\n}\n"]}
@@ -9,23 +9,31 @@
9
9
  * The result text is written for the model (it restates tool-state precedence
10
10
  * rules), so the result row renders from `details` instead.
11
11
  */
12
+ import { type Component } from "@earendil-works/pi-tui";
12
13
  import type { AgentToolResult, ExtensionContext } from "../../core/extensions/types.ts";
13
- import type { Theme } from "../../modes/interactive/theme/theme.ts";
14
- import type { ExitPlanDetails } from "./schema.ts";
14
+ import type { CustomMessage } from "../../core/messages.ts";
15
+ import { type Theme } from "../../modes/interactive/theme/theme.ts";
16
+ import type { ExitPlanDetails, PlanKickoffDetails } from "./schema.ts";
15
17
  /** Collapse the home prefix so a plan path stays on one line. */
16
18
  export declare function shortenPlanPath(path: string): string;
19
+ export declare function formatApprovalSubtitle(ctx: ExtensionContext): string | undefined;
17
20
  /**
18
- * Context pressure is the fact the compact-or-not decision turns on, so the
19
- * dialog states it instead of making the user leave and check the footer.
21
+ * The kickoff message body repeats the whole plan for the LLM, so the default
22
+ * renderer would flood the chat with markdown the user already approved.
23
+ * Collapsed: a two-line card (title + saved path with the expand hint).
24
+ * Expanded: the complete kickoff markdown, styled like the exit_plan body.
20
25
  */
21
- export declare function formatApprovalSubtitle(ctx: ExtensionContext): string | undefined;
26
+ export declare function renderPlanKickoffMessage(message: CustomMessage<PlanKickoffDetails>, options: {
27
+ expanded: boolean;
28
+ outputPad: number;
29
+ }, theme: Theme): Component;
22
30
  type ExitPlanCallArgs = {
23
31
  title?: unknown;
24
32
  plan?: unknown;
25
33
  revises?: unknown;
26
34
  };
27
- /** Title-only headline when collapsed; plan body and revision target on expand. */
28
- export declare function formatExitPlanCall(args: ExitPlanCallArgs | undefined, theme: Theme, expanded: boolean): string;
35
+ /** Title-only headline when collapsed; Markdown plan body and revision target on expand. */
36
+ export declare function renderExitPlanCall(args: ExitPlanCallArgs | undefined, theme: Theme, expanded: boolean): Component;
29
37
  /**
30
38
  * What the user needs to know: where the plan landed and what happens next.
31
39
  * Falls back to the model-facing text when details are missing — a thrown
@@ -1 +1 @@
1
- {"version":3,"file":"view.d.ts","sourceRoot":"","sources":["../../../src/extensions/plan/view.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,KAAK,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,gCAAgC,CAAC;AACxF,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,wCAAwC,CAAC;AACpE,OAAO,KAAK,EAAoB,eAAe,EAAE,MAAM,aAAa,CAAC;AAErE,iEAAiE;AACjE,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAOpD;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,gBAAgB,GAAG,MAAM,GAAG,SAAS,CAIhF;AAKD,KAAK,gBAAgB,GAAG;IAAE,KAAK,CAAC,EAAE,OAAO,CAAC;IAAC,IAAI,CAAC,EAAE,OAAO,CAAC;IAAC,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC;AAM/E,mFAAmF;AACnF,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,gBAAgB,GAAG,SAAS,EAAE,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,OAAO,GAAG,MAAM,CAiB9G;AAkBD;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,eAAe,CAAC,eAAe,CAAC,EAAE,KAAK,EAAE,KAAK,GAAG,MAAM,CAQnG","sourcesContent":["/**\n * plan/view.ts — approval-dialog context and exit_plan tool rendering.\n *\n * `exit_plan` carries the whole plan as a parameter, and the default renderer\n * echoes parameters verbatim: the row would repeat, escaped onto one line, the\n * markdown the model just printed as response text. Collapsed rows therefore\n * carry the title only, and the plan body is available on expand.\n *\n * The result text is written for the model (it restates tool-state precedence\n * rules), so the result row renders from `details` instead.\n */\nimport type { AgentToolResult, ExtensionContext } from \"../../core/extensions/types.ts\";\nimport type { Theme } from \"../../modes/interactive/theme/theme.ts\";\nimport type { ExitPlanDecision, ExitPlanDetails } from \"./schema.ts\";\n\n/** Collapse the home prefix so a plan path stays on one line. */\nexport function shortenPlanPath(path: string): string {\n\tconst rawHome = process.env.USERPROFILE || process.env.HOME || \"\";\n\tconst home = rawHome.replace(/[\\\\/]+$/, \"\");\n\tif (!home) return path;\n\tconst normalize = (value: string) => value.replace(/\\\\/g, \"/\").toLowerCase();\n\tif (!normalize(path).startsWith(`${normalize(home)}/`)) return path;\n\treturn `~${path.slice(home.length).replace(/\\\\/g, \"/\")}`;\n}\n\n/**\n * Context pressure is the fact the compact-or-not decision turns on, so the\n * dialog states it instead of making the user leave and check the footer.\n */\nexport function formatApprovalSubtitle(ctx: ExtensionContext): string | undefined {\n\tconst percent = ctx.getContextUsage()?.percent;\n\tif (percent == null) return undefined;\n\treturn `Context now ${percent.toFixed(0)}% full`;\n}\n\n// ---- tool call rendering ----------------------------------------------------\n// Args arrive off the wire and stream in, so every field is read defensively.\n\ntype ExitPlanCallArgs = { title?: unknown; plan?: unknown; revises?: unknown };\n\nfunction callText(value: unknown): string {\n\treturn typeof value === \"string\" ? value.trim() : \"\";\n}\n\n/** Title-only headline when collapsed; plan body and revision target on expand. */\nexport function formatExitPlanCall(args: ExitPlanCallArgs | undefined, theme: Theme, expanded: boolean): string {\n\tconst title = callText(args?.title);\n\tconst headline = [\n\t\ttheme.fg(\"toolTitle\", theme.bold(\"exit_plan\")),\n\t\ttitle ? theme.fg(\"text\", title) : theme.fg(\"dim\", \"…\"),\n\t].join(\" \");\n\tif (!expanded) return headline;\n\n\tconst lines = [headline];\n\tconst revises = callText(args?.revises);\n\tif (revises) lines.push(theme.fg(\"dim\", `revises ${revises}`));\n\tconst plan = callText(args?.plan);\n\tif (plan) {\n\t\tlines.push(\"\");\n\t\tfor (const line of plan.split(\"\\n\")) lines.push(theme.fg(\"dim\", line));\n\t}\n\treturn lines.join(\"\\n\");\n}\n\n// ---- tool result rendering --------------------------------------------------\n\nconst DECISION_SUMMARY: Record<ExitPlanDecision, string> = {\n\texecute: \"Executing with full context\",\n\tcompactAndExecute: \"Compacting context, then executing\",\n\tkeepPlanning: \"Still planning\",\n\tcancelled: \"No approval was pending\",\n};\n\nfunction resultText(result: AgentToolResult<ExitPlanDetails>): string {\n\treturn result.content\n\t\t.filter((block): block is { type: \"text\"; text: string } => block.type === \"text\")\n\t\t.map((block) => block.text)\n\t\t.join(\"\\n\");\n}\n\n/**\n * What the user needs to know: where the plan landed and what happens next.\n * Falls back to the model-facing text when details are missing — a thrown\n * approval (interrupt) has no details to render from.\n */\nexport function formatExitPlanResult(result: AgentToolResult<ExitPlanDetails>, theme: Theme): string {\n\tconst details = result.details;\n\tif (!details) return theme.fg(\"dim\", resultText(result));\n\n\tconst lines: string[] = [];\n\tif (details.planPath) lines.push(theme.fg(\"success\", `Saved to ${shortenPlanPath(details.planPath)}`));\n\tlines.push(theme.fg(\"dim\", DECISION_SUMMARY[details.decision]));\n\treturn lines.join(\"\\n\");\n}\n"]}
1
+ {"version":3,"file":"view.d.ts","sourceRoot":"","sources":["../../../src/extensions/plan/view.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,EAAE,KAAK,SAAS,EAAqC,MAAM,wBAAwB,CAAC;AAC3F,OAAO,KAAK,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,gCAAgC,CAAC;AACxF,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAE5D,OAAO,EAAoB,KAAK,KAAK,EAAE,MAAM,wCAAwC,CAAC;AACtF,OAAO,KAAK,EAAoB,eAAe,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAEzF,iEAAiE;AACjE,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAOpD;AAcD,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,gBAAgB,GAAG,MAAM,GAAG,SAAS,CAKhF;AAID;;;;;GAKG;AACH,wBAAgB,wBAAwB,CACvC,OAAO,EAAE,aAAa,CAAC,kBAAkB,CAAC,EAC1C,OAAO,EAAE;IAAE,QAAQ,EAAE,OAAO,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,EACjD,KAAK,EAAE,KAAK,GACV,SAAS,CAsCX;AAKD,KAAK,gBAAgB,GAAG;IAAE,KAAK,CAAC,EAAE,OAAO,CAAC;IAAC,IAAI,CAAC,EAAE,OAAO,CAAC;IAAC,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC;AAM/E,4FAA4F;AAC5F,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,gBAAgB,GAAG,SAAS,EAAE,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,OAAO,GAAG,SAAS,CAsBjH;AAsBD;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,eAAe,CAAC,eAAe,CAAC,EAAE,KAAK,EAAE,KAAK,GAAG,MAAM,CAgBnG","sourcesContent":["/**\n * plan/view.ts — approval-dialog context and exit_plan tool rendering.\n *\n * `exit_plan` carries the whole plan as a parameter, and the default renderer\n * echoes parameters verbatim: the row would repeat, escaped onto one line, the\n * markdown the model just printed as response text. Collapsed rows therefore\n * carry the title only, and the plan body is available on expand.\n *\n * The result text is written for the model (it restates tool-state precedence\n * rules), so the result row renders from `details` instead.\n */\nimport { type Component, Container, Markdown, Spacer, Text } from \"@earendil-works/pi-tui\";\nimport type { AgentToolResult, ExtensionContext } from \"../../core/extensions/types.ts\";\nimport type { CustomMessage } from \"../../core/messages.ts\";\nimport { keyText } from \"../../modes/interactive/components/keybinding-hints.ts\";\nimport { getMarkdownTheme, type Theme } from \"../../modes/interactive/theme/theme.ts\";\nimport type { ExitPlanDecision, ExitPlanDetails, PlanKickoffDetails } from \"./schema.ts\";\n\n/** Collapse the home prefix so a plan path stays on one line. */\nexport function shortenPlanPath(path: string): string {\n\tconst rawHome = process.env.USERPROFILE || process.env.HOME || \"\";\n\tconst home = rawHome.replace(/[\\\\/]+$/, \"\");\n\tif (!home) return path;\n\tconst normalize = (value: string) => value.replace(/\\\\/g, \"/\").toLowerCase();\n\tif (!normalize(path).startsWith(`${normalize(home)}/`)) return path;\n\treturn `~${path.slice(home.length).replace(/\\\\/g, \"/\")}`;\n}\n\n/**\n * Context pressure is the fact the compact-or-not decision turns on, so the\n * dialog states it instead of making the user leave and check the footer.\n */\nfunction formatTokenCount(tokenCount: number): string {\n\tif (tokenCount < 1000) return `${tokenCount}`;\n\tif (tokenCount < 10_000) return `${(tokenCount / 1000).toFixed(1)}k`;\n\tif (tokenCount < 1_000_000) return `${Math.round(tokenCount / 1000)}k`;\n\tif (tokenCount < 10_000_000) return `${(tokenCount / 1_000_000).toFixed(1)}M`;\n\treturn `${Math.round(tokenCount / 1_000_000)}M`;\n}\n\nexport function formatApprovalSubtitle(ctx: ExtensionContext): string | undefined {\n\tconst usage = ctx.getContextUsage();\n\tif (usage?.percent == null) return undefined;\n\tconst window = usage.contextWindow > 0 ? ` of ${formatTokenCount(usage.contextWindow)}` : \"\";\n\treturn `Context ${usage.percent.toFixed(1)}%${window}`;\n}\n\n// ---- kickoff message rendering ----------------------------------------------\n\n/**\n * The kickoff message body repeats the whole plan for the LLM, so the default\n * renderer would flood the chat with markdown the user already approved.\n * Collapsed: a two-line card (title + saved path with the expand hint).\n * Expanded: the complete kickoff markdown, styled like the exit_plan body.\n */\nexport function renderPlanKickoffMessage(\n\tmessage: CustomMessage<PlanKickoffDetails>,\n\toptions: { expanded: boolean; outputPad: number },\n\ttheme: Theme,\n): Component {\n\tconst details = (message.details ?? {}) as Partial<PlanKickoffDetails>;\n\tconst title = typeof details.title === \"string\" && details.title.trim() ? details.title.trim() : \"plan\";\n\tconst planPath = typeof details.planPath === \"string\" ? details.planPath.trim() : \"\";\n\n\tconst container = new Container();\n\tcontainer.addChild(\n\t\tnew Text(\n\t\t\t[theme.fg(\"toolTitle\", theme.bold(\"plan\")), theme.fg(\"text\", `Executing \"${title}\"`)].join(\" \"),\n\t\t\toptions.outputPad,\n\t\t\t0,\n\t\t),\n\t);\n\tconst pathText = planPath ? `${shortenPlanPath(planPath)} ` : \"\";\n\tconst secondLine = options.expanded\n\t\t? planPath\n\t\t\t? theme.fg(\"dim\", shortenPlanPath(planPath))\n\t\t\t: undefined\n\t\t: theme.fg(\"dim\", `${pathText}(${keyText(\"app.tools.expand\")} to expand)`);\n\tif (secondLine) container.addChild(new Text(secondLine, options.outputPad, 0));\n\tif (!options.expanded) return container;\n\n\tconst body =\n\t\ttypeof message.content === \"string\"\n\t\t\t? message.content\n\t\t\t: message.content\n\t\t\t\t\t.filter((block): block is { type: \"text\"; text: string } => block.type === \"text\")\n\t\t\t\t\t.map((block) => block.text)\n\t\t\t\t\t.join(\"\\n\");\n\tif (body.trim()) {\n\t\tcontainer.addChild(new Spacer(1));\n\t\tcontainer.addChild(\n\t\t\tnew Markdown(body, options.outputPad, 0, getMarkdownTheme(), {\n\t\t\t\tcolor: (text) => theme.fg(\"toolOutput\", text),\n\t\t\t}),\n\t\t);\n\t}\n\treturn container;\n}\n\n// ---- tool call rendering ----------------------------------------------------\n// Args arrive off the wire and stream in, so every field is read defensively.\n\ntype ExitPlanCallArgs = { title?: unknown; plan?: unknown; revises?: unknown };\n\nfunction callText(value: unknown): string {\n\treturn typeof value === \"string\" ? value.trim() : \"\";\n}\n\n/** Title-only headline when collapsed; Markdown plan body and revision target on expand. */\nexport function renderExitPlanCall(args: ExitPlanCallArgs | undefined, theme: Theme, expanded: boolean): Component {\n\tconst title = callText(args?.title);\n\tconst headline = [\n\t\ttheme.fg(\"toolTitle\", theme.bold(\"exit_plan\")),\n\t\ttitle ? theme.fg(\"text\", title) : theme.fg(\"dim\", \"…\"),\n\t].join(\" \");\n\tif (!expanded) return new Text(headline, 0, 0);\n\n\tconst container = new Container();\n\tcontainer.addChild(new Text(headline, 0, 0));\n\tconst revises = callText(args?.revises);\n\tif (revises) container.addChild(new Text(theme.fg(\"dim\", `revises ${revises}`), 0, 0));\n\tconst plan = callText(args?.plan);\n\tif (plan) {\n\t\tcontainer.addChild(new Spacer(1));\n\t\tcontainer.addChild(\n\t\t\tnew Markdown(plan, 0, 0, getMarkdownTheme(), {\n\t\t\t\tcolor: (text) => theme.fg(\"toolOutput\", text),\n\t\t\t}),\n\t\t);\n\t}\n\treturn container;\n}\n\n// ---- tool result rendering --------------------------------------------------\n\nconst DECISION_SUMMARY: Record<ExitPlanDecision, string> = {\n\texecute: \"Executing with full context\",\n\tcompactAndExecute: \"Compacting context, then executing\",\n\tkeepPlanning: \"Still planning\",\n\tcancelled: \"No approval was pending\",\n};\n\nfunction isExitPlanDecision(value: unknown): value is ExitPlanDecision {\n\treturn value === \"execute\" || value === \"compactAndExecute\" || value === \"keepPlanning\" || value === \"cancelled\";\n}\n\nfunction resultText(result: AgentToolResult<ExitPlanDetails>): string {\n\treturn result.content\n\t\t.filter((block): block is { type: \"text\"; text: string } => block.type === \"text\")\n\t\t.map((block) => block.text)\n\t\t.join(\"\\n\");\n}\n\n/**\n * What the user needs to know: where the plan landed and what happens next.\n * Falls back to the model-facing text when details are missing — a thrown\n * approval (interrupt) has no details to render from.\n */\nexport function formatExitPlanResult(result: AgentToolResult<ExitPlanDetails>, theme: Theme): string {\n\tconst details = result.details as unknown;\n\tif (!details || typeof details !== \"object\" || Array.isArray(details)) {\n\t\treturn theme.fg(\"dim\", resultText(result));\n\t}\n\tconst decision = (details as Record<string, unknown>).decision;\n\tif (!isExitPlanDecision(decision)) return theme.fg(\"dim\", resultText(result));\n\tconst rawPlanPath = (details as Record<string, unknown>).planPath;\n\n\tconst lines: string[] = [];\n\tif (typeof rawPlanPath === \"string\" && rawPlanPath) {\n\t\tlines.push(theme.fg(\"dim\", `Saved to ${shortenPlanPath(rawPlanPath)}`));\n\t}\n\tconst decisionColor = decision === \"cancelled\" ? \"warning\" : \"text\";\n\tlines.push(theme.fg(decisionColor, DECISION_SUMMARY[decision]));\n\treturn lines.join(\"\\n\");\n}\n"]}