@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":"render-utils.d.ts","sourceRoot":"","sources":["../../../src/core/tools/render-utils.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAEvE,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,wCAAwC,CAAC;AAKpE,wBAAgB,WAAW,CAAC,IAAI,EAAE,OAAO,GAAG,MAAM,CAOjD;AAED,wBAAgB,QAAQ,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAIjF;AAED,wBAAgB,GAAG,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAIjD;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEhD;AAED,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEzD;AAED,wBAAgB,aAAa,CAC5B,MAAM,EAAE;IAAE,OAAO,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CAAE,GAAG,SAAS,EACzG,UAAU,EAAE,OAAO,GACjB,MAAM,CAsBR;AAED,MAAM,MAAM,oBAAoB,CAAC,QAAQ,IAAI;IAC5C,OAAO,EAAE,CAAC,WAAW,GAAG,YAAY,CAAC,EAAE,CAAC;IACxC,OAAO,EAAE,QAAQ,CAAC;CAClB,CAAC;AAEF,wBAAgB,cAAc,CAAC,KAAK,EAAE,KAAK,GAAG,MAAM,CAEnD;AAED,wBAAgB,cAAc,CAC7B,OAAO,EAAE,MAAM,GAAG,IAAI,EACtB,KAAK,EAAE,KAAK,EACZ,GAAG,EAAE,MAAM,EACX,OAAO,CAAC,EAAE;IAAE,aAAa,CAAC,EAAE,MAAM,CAAA;CAAE,GAClC,MAAM,CAKR","sourcesContent":["import * as os from \"node:os\";\nimport { pathToFileURL } from \"node:url\";\nimport type { ImageContent, TextContent } from \"@earendil-works/pi-ai\";\nimport { getCapabilities, getImageDimensions, hyperlink, imageFallback } from \"@earendil-works/pi-tui\";\nimport type { Theme } from \"../../modes/interactive/theme/theme.ts\";\nimport { stripAnsi } from \"../../utils/ansi.ts\";\nimport { resolvePath } from \"../../utils/paths.ts\";\nimport { sanitizeBinaryOutput } from \"../../utils/shell.ts\";\n\nexport function shortenPath(path: unknown): string {\n\tif (typeof path !== \"string\") return \"\";\n\tconst home = os.homedir();\n\tif (path.startsWith(home)) {\n\t\treturn `~${path.slice(home.length)}`;\n\t}\n\treturn path;\n}\n\nexport function linkPath(styledText: string, rawPath: string, cwd: string): string {\n\tif (!getCapabilities().hyperlinks) return styledText;\n\tconst absolutePath = resolvePath(rawPath, cwd);\n\treturn hyperlink(styledText, pathToFileURL(absolutePath).href);\n}\n\nexport function str(value: unknown): string | null {\n\tif (typeof value === \"string\") return value;\n\tif (value == null) return \"\";\n\treturn null;\n}\n\nexport function replaceTabs(text: string): string {\n\treturn text.replace(/\\t/g, \" \");\n}\n\nexport function normalizeDisplayText(text: string): string {\n\treturn text.replace(/\\r/g, \"\");\n}\n\nexport function getTextOutput(\n\tresult: { content: Array<{ type: string; text?: string; data?: string; mimeType?: string }> } | undefined,\n\tshowImages: boolean,\n): string {\n\tif (!result) return \"\";\n\n\tconst textBlocks = result.content.filter((c) => c.type === \"text\");\n\tconst imageBlocks = result.content.filter((c) => c.type === \"image\");\n\n\tlet output = textBlocks.map((c) => sanitizeBinaryOutput(stripAnsi(c.text || \"\")).replace(/\\r/g, \"\")).join(\"\\n\");\n\n\tconst caps = getCapabilities();\n\tif (imageBlocks.length > 0 && (!caps.images || !showImages)) {\n\t\tconst imageIndicators = imageBlocks\n\t\t\t.map((img) => {\n\t\t\t\tconst mimeType = img.mimeType ?? \"image/unknown\";\n\t\t\t\tconst dims =\n\t\t\t\t\timg.data && img.mimeType ? (getImageDimensions(img.data, img.mimeType) ?? undefined) : undefined;\n\t\t\t\treturn imageFallback(mimeType, dims);\n\t\t\t})\n\t\t\t.join(\"\\n\");\n\t\toutput = output ? `${output}\\n${imageIndicators}` : imageIndicators;\n\t}\n\n\treturn output;\n}\n\nexport type ToolRenderResultLike<TDetails> = {\n\tcontent: (TextContent | ImageContent)[];\n\tdetails: TDetails;\n};\n\nexport function invalidArgText(theme: Theme): string {\n\treturn theme.fg(\"error\", \"[invalid arg]\");\n}\n\nexport function renderToolPath(\n\trawPath: string | null,\n\ttheme: Theme,\n\tcwd: string,\n\toptions?: { emptyFallback?: string },\n): string {\n\tif (rawPath === null) return invalidArgText(theme);\n\tconst value = rawPath || options?.emptyFallback;\n\tif (!value) return theme.fg(\"toolOutput\", \"...\");\n\treturn linkPath(theme.fg(\"accent\", shortenPath(value)), value, cwd);\n}\n"]}
1
+ {"version":3,"file":"render-utils.d.ts","sourceRoot":"","sources":["../../../src/core/tools/render-utils.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAGvE,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,wCAAwC,CAAC;AAKpE,wBAAgB,WAAW,CAAC,IAAI,EAAE,OAAO,GAAG,MAAM,CAOjD;AAED,wBAAgB,QAAQ,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAIjF;AAED,wBAAgB,GAAG,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAIjD;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEhD;AAED,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEzD;AAED,wBAAgB,aAAa,CAC5B,MAAM,EAAE;IAAE,OAAO,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CAAE,GAAG,SAAS,EACzG,UAAU,EAAE,OAAO,GACjB,MAAM,CAsBR;AAED,MAAM,MAAM,oBAAoB,CAAC,QAAQ,IAAI;IAC5C,OAAO,EAAE,CAAC,WAAW,GAAG,YAAY,CAAC,EAAE,CAAC;IACxC,OAAO,EAAE,QAAQ,CAAC;CAClB,CAAC;AAEF,wBAAgB,cAAc,CAAC,KAAK,EAAE,KAAK,GAAG,MAAM,CAEnD;AAED,wBAAgB,cAAc,CAC7B,OAAO,EAAE,MAAM,GAAG,IAAI,EACtB,KAAK,EAAE,KAAK,EACZ,GAAG,EAAE,MAAM,EACX,OAAO,CAAC,EAAE;IAAE,aAAa,CAAC,EAAE,MAAM,CAAA;CAAE,GAClC,MAAM,CAKR;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CACjC,KAAK,EAAE,KAAK,EACZ,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,SAAS,GAAG,MAAM,EAC7B,OAAO,CAAC,EAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,GAC1B,MAAM,CAOR","sourcesContent":["import * as os from \"node:os\";\nimport { pathToFileURL } from \"node:url\";\nimport type { ImageContent, TextContent } from \"@earendil-works/pi-ai\";\nimport { getCapabilities, getImageDimensions, hyperlink, imageFallback } from \"@earendil-works/pi-tui\";\nimport { keyHint } from \"../../modes/interactive/components/keybinding-hints.ts\";\nimport type { Theme } from \"../../modes/interactive/theme/theme.ts\";\nimport { stripAnsi } from \"../../utils/ansi.ts\";\nimport { resolvePath } from \"../../utils/paths.ts\";\nimport { sanitizeBinaryOutput } from \"../../utils/shell.ts\";\n\nexport function shortenPath(path: unknown): string {\n\tif (typeof path !== \"string\") return \"\";\n\tconst home = os.homedir();\n\tif (path.startsWith(home)) {\n\t\treturn `~${path.slice(home.length)}`;\n\t}\n\treturn path;\n}\n\nexport function linkPath(styledText: string, rawPath: string, cwd: string): string {\n\tif (!getCapabilities().hyperlinks) return styledText;\n\tconst absolutePath = resolvePath(rawPath, cwd);\n\treturn hyperlink(styledText, pathToFileURL(absolutePath).href);\n}\n\nexport function str(value: unknown): string | null {\n\tif (typeof value === \"string\") return value;\n\tif (value == null) return \"\";\n\treturn null;\n}\n\nexport function replaceTabs(text: string): string {\n\treturn text.replace(/\\t/g, \" \");\n}\n\nexport function normalizeDisplayText(text: string): string {\n\treturn text.replace(/\\r/g, \"\");\n}\n\nexport function getTextOutput(\n\tresult: { content: Array<{ type: string; text?: string; data?: string; mimeType?: string }> } | undefined,\n\tshowImages: boolean,\n): string {\n\tif (!result) return \"\";\n\n\tconst textBlocks = result.content.filter((c) => c.type === \"text\");\n\tconst imageBlocks = result.content.filter((c) => c.type === \"image\");\n\n\tlet output = textBlocks.map((c) => sanitizeBinaryOutput(stripAnsi(c.text || \"\")).replace(/\\r/g, \"\")).join(\"\\n\");\n\n\tconst caps = getCapabilities();\n\tif (imageBlocks.length > 0 && (!caps.images || !showImages)) {\n\t\tconst imageIndicators = imageBlocks\n\t\t\t.map((img) => {\n\t\t\t\tconst mimeType = img.mimeType ?? \"image/unknown\";\n\t\t\t\tconst dims =\n\t\t\t\t\timg.data && img.mimeType ? (getImageDimensions(img.data, img.mimeType) ?? undefined) : undefined;\n\t\t\t\treturn imageFallback(mimeType, dims);\n\t\t\t})\n\t\t\t.join(\"\\n\");\n\t\toutput = output ? `${output}\\n${imageIndicators}` : imageIndicators;\n\t}\n\n\treturn output;\n}\n\nexport type ToolRenderResultLike<TDetails> = {\n\tcontent: (TextContent | ImageContent)[];\n\tdetails: TDetails;\n};\n\nexport function invalidArgText(theme: Theme): string {\n\treturn theme.fg(\"error\", \"[invalid arg]\");\n}\n\nexport function renderToolPath(\n\trawPath: string | null,\n\ttheme: Theme,\n\tcwd: string,\n\toptions?: { emptyFallback?: string },\n): string {\n\tif (rawPath === null) return invalidArgText(theme);\n\tconst value = rawPath || options?.emptyFallback;\n\tif (!value) return theme.fg(\"toolOutput\", \"...\");\n\treturn linkPath(theme.fg(\"accent\", shortenPath(value)), value, cwd);\n}\n\n/**\n * Shared collapsed-output hint: `… (N earlier/more lines, ctrl+o to expand)`.\n * `direction` is \"earlier\" when the preview keeps the tail, \"more\" when it keeps the head.\n */\nexport function collapsedLinesHint(\n\ttheme: Theme,\n\thidden: number,\n\tdirection: \"earlier\" | \"more\",\n\toptions?: { total?: number },\n): string {\n\tconst totalSuffix = options?.total !== undefined ? `, ${options.total} total` : \"\";\n\tconst noun = hidden === 1 ? \"line\" : \"lines\";\n\treturn (\n\t\ttheme.fg(\"muted\", `… (${hidden} ${direction} ${noun}${totalSuffix},`) +\n\t\t` ${keyHint(\"app.tools.expand\", \"to expand\")}${theme.fg(\"muted\", \")\")}`\n\t);\n}\n"]}
@@ -1,6 +1,7 @@
1
1
  import * as os from "node:os";
2
2
  import { pathToFileURL } from "node:url";
3
3
  import { getCapabilities, getImageDimensions, hyperlink, imageFallback } from "@earendil-works/pi-tui";
4
+ import { keyHint } from "../../modes/interactive/components/keybinding-hints.js";
4
5
  import { stripAnsi } from "../../utils/ansi.js";
5
6
  import { resolvePath } from "../../utils/paths.js";
6
7
  import { sanitizeBinaryOutput } from "../../utils/shell.js";
@@ -62,4 +63,14 @@ export function renderToolPath(rawPath, theme, cwd, options) {
62
63
  return theme.fg("toolOutput", "...");
63
64
  return linkPath(theme.fg("accent", shortenPath(value)), value, cwd);
64
65
  }
66
+ /**
67
+ * Shared collapsed-output hint: `… (N earlier/more lines, ctrl+o to expand)`.
68
+ * `direction` is "earlier" when the preview keeps the tail, "more" when it keeps the head.
69
+ */
70
+ export function collapsedLinesHint(theme, hidden, direction, options) {
71
+ const totalSuffix = options?.total !== undefined ? `, ${options.total} total` : "";
72
+ const noun = hidden === 1 ? "line" : "lines";
73
+ return (theme.fg("muted", `… (${hidden} ${direction} ${noun}${totalSuffix},`) +
74
+ ` ${keyHint("app.tools.expand", "to expand")}${theme.fg("muted", ")")}`);
75
+ }
65
76
  //# sourceMappingURL=render-utils.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"render-utils.js","sourceRoot":"","sources":["../../../src/core/tools/render-utils.ts"],"names":[],"mappings":"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","sourcesContent":["import * as os from \"node:os\";\nimport { pathToFileURL } from \"node:url\";\nimport type { ImageContent, TextContent } from \"@earendil-works/pi-ai\";\nimport { getCapabilities, getImageDimensions, hyperlink, imageFallback } from \"@earendil-works/pi-tui\";\nimport type { Theme } from \"../../modes/interactive/theme/theme.ts\";\nimport { stripAnsi } from \"../../utils/ansi.ts\";\nimport { resolvePath } from \"../../utils/paths.ts\";\nimport { sanitizeBinaryOutput } from \"../../utils/shell.ts\";\n\nexport function shortenPath(path: unknown): string {\n\tif (typeof path !== \"string\") return \"\";\n\tconst home = os.homedir();\n\tif (path.startsWith(home)) {\n\t\treturn `~${path.slice(home.length)}`;\n\t}\n\treturn path;\n}\n\nexport function linkPath(styledText: string, rawPath: string, cwd: string): string {\n\tif (!getCapabilities().hyperlinks) return styledText;\n\tconst absolutePath = resolvePath(rawPath, cwd);\n\treturn hyperlink(styledText, pathToFileURL(absolutePath).href);\n}\n\nexport function str(value: unknown): string | null {\n\tif (typeof value === \"string\") return value;\n\tif (value == null) return \"\";\n\treturn null;\n}\n\nexport function replaceTabs(text: string): string {\n\treturn text.replace(/\\t/g, \" \");\n}\n\nexport function normalizeDisplayText(text: string): string {\n\treturn text.replace(/\\r/g, \"\");\n}\n\nexport function getTextOutput(\n\tresult: { content: Array<{ type: string; text?: string; data?: string; mimeType?: string }> } | undefined,\n\tshowImages: boolean,\n): string {\n\tif (!result) return \"\";\n\n\tconst textBlocks = result.content.filter((c) => c.type === \"text\");\n\tconst imageBlocks = result.content.filter((c) => c.type === \"image\");\n\n\tlet output = textBlocks.map((c) => sanitizeBinaryOutput(stripAnsi(c.text || \"\")).replace(/\\r/g, \"\")).join(\"\\n\");\n\n\tconst caps = getCapabilities();\n\tif (imageBlocks.length > 0 && (!caps.images || !showImages)) {\n\t\tconst imageIndicators = imageBlocks\n\t\t\t.map((img) => {\n\t\t\t\tconst mimeType = img.mimeType ?? \"image/unknown\";\n\t\t\t\tconst dims =\n\t\t\t\t\timg.data && img.mimeType ? (getImageDimensions(img.data, img.mimeType) ?? undefined) : undefined;\n\t\t\t\treturn imageFallback(mimeType, dims);\n\t\t\t})\n\t\t\t.join(\"\\n\");\n\t\toutput = output ? `${output}\\n${imageIndicators}` : imageIndicators;\n\t}\n\n\treturn output;\n}\n\nexport type ToolRenderResultLike<TDetails> = {\n\tcontent: (TextContent | ImageContent)[];\n\tdetails: TDetails;\n};\n\nexport function invalidArgText(theme: Theme): string {\n\treturn theme.fg(\"error\", \"[invalid arg]\");\n}\n\nexport function renderToolPath(\n\trawPath: string | null,\n\ttheme: Theme,\n\tcwd: string,\n\toptions?: { emptyFallback?: string },\n): string {\n\tif (rawPath === null) return invalidArgText(theme);\n\tconst value = rawPath || options?.emptyFallback;\n\tif (!value) return theme.fg(\"toolOutput\", \"...\");\n\treturn linkPath(theme.fg(\"accent\", shortenPath(value)), value, cwd);\n}\n"]}
1
+ {"version":3,"file":"render-utils.js","sourceRoot":"","sources":["../../../src/core/tools/render-utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAC9B,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,OAAO,EAAE,eAAe,EAAE,kBAAkB,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AACvG,OAAO,EAAE,OAAO,EAAE,MAAM,wDAAwD,CAAC;AAEjF,OAAO,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAChD,OAAO,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AACnD,OAAO,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AAE5D,MAAM,UAAU,WAAW,CAAC,IAAa,EAAU;IAClD,IAAI,OAAO,IAAI,KAAK,QAAQ;QAAE,OAAO,EAAE,CAAC;IACxC,MAAM,IAAI,GAAG,EAAE,CAAC,OAAO,EAAE,CAAC;IAC1B,IAAI,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;QAC3B,OAAO,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;IACtC,CAAC;IACD,OAAO,IAAI,CAAC;AAAA,CACZ;AAED,MAAM,UAAU,QAAQ,CAAC,UAAkB,EAAE,OAAe,EAAE,GAAW,EAAU;IAClF,IAAI,CAAC,eAAe,EAAE,CAAC,UAAU;QAAE,OAAO,UAAU,CAAC;IACrD,MAAM,YAAY,GAAG,WAAW,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;IAC/C,OAAO,SAAS,CAAC,UAAU,EAAE,aAAa,CAAC,YAAY,CAAC,CAAC,IAAI,CAAC,CAAC;AAAA,CAC/D;AAED,MAAM,UAAU,GAAG,CAAC,KAAc,EAAiB;IAClD,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAC;IAC5C,IAAI,KAAK,IAAI,IAAI;QAAE,OAAO,EAAE,CAAC;IAC7B,OAAO,IAAI,CAAC;AAAA,CACZ;AAED,MAAM,UAAU,WAAW,CAAC,IAAY,EAAU;IACjD,OAAO,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;AAAA,CAClC;AAED,MAAM,UAAU,oBAAoB,CAAC,IAAY,EAAU;IAC1D,OAAO,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;AAAA,CAC/B;AAED,MAAM,UAAU,aAAa,CAC5B,MAAyG,EACzG,UAAmB,EACV;IACT,IAAI,CAAC,MAAM;QAAE,OAAO,EAAE,CAAC;IAEvB,MAAM,UAAU,GAAG,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,MAAM,CAAC,CAAC;IACnE,MAAM,WAAW,GAAG,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,OAAO,CAAC,CAAC;IAErE,IAAI,MAAM,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,oBAAoB,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAEhH,MAAM,IAAI,GAAG,eAAe,EAAE,CAAC;IAC/B,IAAI,WAAW,CAAC,MAAM,GAAG,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,MAAM,IAAI,CAAC,UAAU,CAAC,EAAE,CAAC;QAC7D,MAAM,eAAe,GAAG,WAAW;aACjC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC;YACb,MAAM,QAAQ,GAAG,GAAG,CAAC,QAAQ,IAAI,eAAe,CAAC;YACjD,MAAM,IAAI,GACT,GAAG,CAAC,IAAI,IAAI,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,kBAAkB,CAAC,GAAG,CAAC,IAAI,EAAE,GAAG,CAAC,QAAQ,CAAC,IAAI,SAAS,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;YAClG,OAAO,aAAa,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;QAAA,CACrC,CAAC;aACD,IAAI,CAAC,IAAI,CAAC,CAAC;QACb,MAAM,GAAG,MAAM,CAAC,CAAC,CAAC,GAAG,MAAM,KAAK,eAAe,EAAE,CAAC,CAAC,CAAC,eAAe,CAAC;IACrE,CAAC;IAED,OAAO,MAAM,CAAC;AAAA,CACd;AAOD,MAAM,UAAU,cAAc,CAAC,KAAY,EAAU;IACpD,OAAO,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,eAAe,CAAC,CAAC;AAAA,CAC1C;AAED,MAAM,UAAU,cAAc,CAC7B,OAAsB,EACtB,KAAY,EACZ,GAAW,EACX,OAAoC,EAC3B;IACT,IAAI,OAAO,KAAK,IAAI;QAAE,OAAO,cAAc,CAAC,KAAK,CAAC,CAAC;IACnD,MAAM,KAAK,GAAG,OAAO,IAAI,OAAO,EAAE,aAAa,CAAC;IAChD,IAAI,CAAC,KAAK;QAAE,OAAO,KAAK,CAAC,EAAE,CAAC,YAAY,EAAE,KAAK,CAAC,CAAC;IACjD,OAAO,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC,QAAQ,EAAE,WAAW,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,EAAE,GAAG,CAAC,CAAC;AAAA,CACpE;AAED;;;GAGG;AACH,MAAM,UAAU,kBAAkB,CACjC,KAAY,EACZ,MAAc,EACd,SAA6B,EAC7B,OAA4B,EACnB;IACT,MAAM,WAAW,GAAG,OAAO,EAAE,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,KAAK,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC;IACnF,MAAM,IAAI,GAAG,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC;IAC7C,OAAO,CACN,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,QAAM,MAAM,IAAI,SAAS,IAAI,IAAI,GAAG,WAAW,GAAG,CAAC;QACrE,IAAI,OAAO,CAAC,kBAAkB,EAAE,WAAW,CAAC,GAAG,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,CAAC,EAAE,CACvE,CAAC;AAAA,CACF","sourcesContent":["import * as os from \"node:os\";\nimport { pathToFileURL } from \"node:url\";\nimport type { ImageContent, TextContent } from \"@earendil-works/pi-ai\";\nimport { getCapabilities, getImageDimensions, hyperlink, imageFallback } from \"@earendil-works/pi-tui\";\nimport { keyHint } from \"../../modes/interactive/components/keybinding-hints.ts\";\nimport type { Theme } from \"../../modes/interactive/theme/theme.ts\";\nimport { stripAnsi } from \"../../utils/ansi.ts\";\nimport { resolvePath } from \"../../utils/paths.ts\";\nimport { sanitizeBinaryOutput } from \"../../utils/shell.ts\";\n\nexport function shortenPath(path: unknown): string {\n\tif (typeof path !== \"string\") return \"\";\n\tconst home = os.homedir();\n\tif (path.startsWith(home)) {\n\t\treturn `~${path.slice(home.length)}`;\n\t}\n\treturn path;\n}\n\nexport function linkPath(styledText: string, rawPath: string, cwd: string): string {\n\tif (!getCapabilities().hyperlinks) return styledText;\n\tconst absolutePath = resolvePath(rawPath, cwd);\n\treturn hyperlink(styledText, pathToFileURL(absolutePath).href);\n}\n\nexport function str(value: unknown): string | null {\n\tif (typeof value === \"string\") return value;\n\tif (value == null) return \"\";\n\treturn null;\n}\n\nexport function replaceTabs(text: string): string {\n\treturn text.replace(/\\t/g, \" \");\n}\n\nexport function normalizeDisplayText(text: string): string {\n\treturn text.replace(/\\r/g, \"\");\n}\n\nexport function getTextOutput(\n\tresult: { content: Array<{ type: string; text?: string; data?: string; mimeType?: string }> } | undefined,\n\tshowImages: boolean,\n): string {\n\tif (!result) return \"\";\n\n\tconst textBlocks = result.content.filter((c) => c.type === \"text\");\n\tconst imageBlocks = result.content.filter((c) => c.type === \"image\");\n\n\tlet output = textBlocks.map((c) => sanitizeBinaryOutput(stripAnsi(c.text || \"\")).replace(/\\r/g, \"\")).join(\"\\n\");\n\n\tconst caps = getCapabilities();\n\tif (imageBlocks.length > 0 && (!caps.images || !showImages)) {\n\t\tconst imageIndicators = imageBlocks\n\t\t\t.map((img) => {\n\t\t\t\tconst mimeType = img.mimeType ?? \"image/unknown\";\n\t\t\t\tconst dims =\n\t\t\t\t\timg.data && img.mimeType ? (getImageDimensions(img.data, img.mimeType) ?? undefined) : undefined;\n\t\t\t\treturn imageFallback(mimeType, dims);\n\t\t\t})\n\t\t\t.join(\"\\n\");\n\t\toutput = output ? `${output}\\n${imageIndicators}` : imageIndicators;\n\t}\n\n\treturn output;\n}\n\nexport type ToolRenderResultLike<TDetails> = {\n\tcontent: (TextContent | ImageContent)[];\n\tdetails: TDetails;\n};\n\nexport function invalidArgText(theme: Theme): string {\n\treturn theme.fg(\"error\", \"[invalid arg]\");\n}\n\nexport function renderToolPath(\n\trawPath: string | null,\n\ttheme: Theme,\n\tcwd: string,\n\toptions?: { emptyFallback?: string },\n): string {\n\tif (rawPath === null) return invalidArgText(theme);\n\tconst value = rawPath || options?.emptyFallback;\n\tif (!value) return theme.fg(\"toolOutput\", \"...\");\n\treturn linkPath(theme.fg(\"accent\", shortenPath(value)), value, cwd);\n}\n\n/**\n * Shared collapsed-output hint: `… (N earlier/more lines, ctrl+o to expand)`.\n * `direction` is \"earlier\" when the preview keeps the tail, \"more\" when it keeps the head.\n */\nexport function collapsedLinesHint(\n\ttheme: Theme,\n\thidden: number,\n\tdirection: \"earlier\" | \"more\",\n\toptions?: { total?: number },\n): string {\n\tconst totalSuffix = options?.total !== undefined ? `, ${options.total} total` : \"\";\n\tconst noun = hidden === 1 ? \"line\" : \"lines\";\n\treturn (\n\t\ttheme.fg(\"muted\", `… (${hidden} ${direction} ${noun}${totalSuffix},`) +\n\t\t` ${keyHint(\"app.tools.expand\", \"to expand\")}${theme.fg(\"muted\", \")\")}`\n\t);\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"write.d.ts","sourceRoot":"","sources":["../../../src/core/tools/write.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,+BAA+B,CAAC;AAI/D,OAAO,EAAE,KAAK,MAAM,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AAG5C,OAAO,KAAK,EAAE,cAAc,EAA2B,MAAM,wBAAwB,CAAC;AAMtF,QAAA,MAAM,WAAW;;;EAGf,CAAC;AAEH,MAAM,MAAM,cAAc,GAAG,MAAM,CAAC,OAAO,WAAW,CAAC,CAAC;AAExD;;;GAGG;AACH,MAAM,WAAW,eAAe;IAC/B,8BAA8B;IAC9B,SAAS,EAAE,CAAC,YAAY,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACpE,mCAAmC;IACnC,KAAK,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACtC;AAOD,MAAM,WAAW,gBAAgB;IAChC,oEAAoE;IACpE,UAAU,CAAC,EAAE,eAAe,CAAC;CAC7B;AA6ID,wBAAgB,yBAAyB,CACxC,GAAG,EAAE,MAAM,EACX,OAAO,CAAC,EAAE,gBAAgB,GACxB,cAAc,CAAC,OAAO,WAAW,EAAE,SAAS,CAAC,CA+E/C;AAED,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,gBAAgB,GAAG,SAAS,CAAC,OAAO,WAAW,CAAC,CAEtG","sourcesContent":["import type { AgentTool } from \"@earendil-works/pi-agent-core\";\nimport { Container, Text } from \"@earendil-works/pi-tui\";\nimport { mkdir as fsMkdir, writeFile as fsWriteFile } from \"fs/promises\";\nimport { dirname } from \"path\";\nimport { type Static, Type } from \"typebox\";\nimport { keyHint } from \"../../modes/interactive/components/keybinding-hints.ts\";\nimport { getLanguageFromPath, highlightCode, type Theme } from \"../../modes/interactive/theme/theme.ts\";\nimport type { ToolDefinition, ToolRenderResultOptions } from \"../extensions/types.ts\";\nimport { withFileMutationQueue } from \"./file-mutation-queue.ts\";\nimport { resolveToCwd } from \"./path-utils.ts\";\nimport { normalizeDisplayText, renderToolPath, replaceTabs, str } from \"./render-utils.ts\";\nimport { wrapToolDefinition } from \"./tool-definition-wrapper.ts\";\n\nconst writeSchema = Type.Object({\n\tpath: Type.String({ description: \"Path to the file to write (relative or absolute)\" }),\n\tcontent: Type.String({ description: \"Content to write to the file\" }),\n});\n\nexport type WriteToolInput = Static<typeof writeSchema>;\n\n/**\n * Pluggable operations for the write tool.\n * Override these to delegate file writing to remote systems (for example SSH).\n */\nexport interface WriteOperations {\n\t/** Write content to a file */\n\twriteFile: (absolutePath: string, content: string) => Promise<void>;\n\t/** Create directory recursively */\n\tmkdir: (dir: string) => Promise<void>;\n}\n\nconst defaultWriteOperations: WriteOperations = {\n\twriteFile: (path, content) => fsWriteFile(path, content, \"utf-8\"),\n\tmkdir: (dir) => fsMkdir(dir, { recursive: true }).then(() => {}),\n};\n\nexport interface WriteToolOptions {\n\t/** Custom operations for file writing. Default: local filesystem */\n\toperations?: WriteOperations;\n}\n\ntype WriteHighlightCache = {\n\trawPath: string | null;\n\tlang: string;\n\trawContent: string;\n\tnormalizedLines: string[];\n\thighlightedLines: string[];\n};\n\nclass WriteCallRenderComponent extends Text {\n\tcache?: WriteHighlightCache;\n\n\tconstructor() {\n\t\tsuper(\"\", 0, 0);\n\t}\n}\n\nconst WRITE_PARTIAL_FULL_HIGHLIGHT_LINES = 50;\n\nfunction highlightSingleLine(line: string, lang: string): string {\n\tconst highlighted = highlightCode(line, lang);\n\treturn highlighted[0] ?? \"\";\n}\n\nfunction refreshWriteHighlightPrefix(cache: WriteHighlightCache): void {\n\tconst prefixCount = Math.min(WRITE_PARTIAL_FULL_HIGHLIGHT_LINES, cache.normalizedLines.length);\n\tif (prefixCount === 0) return;\n\tconst prefixSource = cache.normalizedLines.slice(0, prefixCount).join(\"\\n\");\n\tconst prefixHighlighted = highlightCode(prefixSource, cache.lang);\n\tfor (let i = 0; i < prefixCount; i++) {\n\t\tcache.highlightedLines[i] =\n\t\t\tprefixHighlighted[i] ?? highlightSingleLine(cache.normalizedLines[i] ?? \"\", cache.lang);\n\t}\n}\n\nfunction rebuildWriteHighlightCacheFull(rawPath: string | null, fileContent: string): WriteHighlightCache | undefined {\n\tconst lang = rawPath ? getLanguageFromPath(rawPath) : undefined;\n\tif (!lang) return undefined;\n\tconst displayContent = normalizeDisplayText(fileContent);\n\tconst normalized = replaceTabs(displayContent);\n\treturn {\n\t\trawPath,\n\t\tlang,\n\t\trawContent: fileContent,\n\t\tnormalizedLines: normalized.split(\"\\n\"),\n\t\thighlightedLines: highlightCode(normalized, lang),\n\t};\n}\n\nfunction updateWriteHighlightCacheIncremental(\n\tcache: WriteHighlightCache | undefined,\n\trawPath: string | null,\n\tfileContent: string,\n): WriteHighlightCache | undefined {\n\tconst lang = rawPath ? getLanguageFromPath(rawPath) : undefined;\n\tif (!lang) return undefined;\n\tif (!cache) return rebuildWriteHighlightCacheFull(rawPath, fileContent);\n\tif (cache.lang !== lang || cache.rawPath !== rawPath) return rebuildWriteHighlightCacheFull(rawPath, fileContent);\n\tif (!fileContent.startsWith(cache.rawContent)) return rebuildWriteHighlightCacheFull(rawPath, fileContent);\n\tif (fileContent.length === cache.rawContent.length) return cache;\n\n\tconst deltaRaw = fileContent.slice(cache.rawContent.length);\n\tconst deltaDisplay = normalizeDisplayText(deltaRaw);\n\tconst deltaNormalized = replaceTabs(deltaDisplay);\n\tcache.rawContent = fileContent;\n\tif (cache.normalizedLines.length === 0) {\n\t\tcache.normalizedLines.push(\"\");\n\t\tcache.highlightedLines.push(\"\");\n\t}\n\n\tconst segments = deltaNormalized.split(\"\\n\");\n\tconst lastIndex = cache.normalizedLines.length - 1;\n\tcache.normalizedLines[lastIndex] += segments[0];\n\tcache.highlightedLines[lastIndex] = highlightSingleLine(cache.normalizedLines[lastIndex], cache.lang);\n\tfor (let i = 1; i < segments.length; i++) {\n\t\tcache.normalizedLines.push(segments[i]);\n\t\tcache.highlightedLines.push(highlightSingleLine(segments[i], cache.lang));\n\t}\n\trefreshWriteHighlightPrefix(cache);\n\treturn cache;\n}\n\nfunction trimTrailingEmptyLines(lines: string[]): string[] {\n\tlet end = lines.length;\n\twhile (end > 0 && lines[end - 1] === \"\") {\n\t\tend--;\n\t}\n\treturn lines.slice(0, end);\n}\n\nfunction formatWriteCall(\n\targs: { path?: string; file_path?: string; content?: string } | undefined,\n\toptions: ToolRenderResultOptions,\n\ttheme: Theme,\n\tcache: WriteHighlightCache | undefined,\n\tcwd: string,\n): string {\n\tconst rawPath = str(args?.file_path ?? args?.path);\n\tconst fileContent = str(args?.content);\n\tconst pathDisplay = renderToolPath(rawPath, theme, cwd);\n\tlet text = `${theme.fg(\"toolTitle\", theme.bold(\"write\"))} ${pathDisplay}`;\n\n\tif (fileContent === null) {\n\t\ttext += `\\n\\n${theme.fg(\"error\", \"[invalid content arg - expected string]\")}`;\n\t} else if (fileContent) {\n\t\tconst lang = rawPath ? getLanguageFromPath(rawPath) : undefined;\n\t\tconst renderedLines = lang\n\t\t\t? (cache?.highlightedLines ?? highlightCode(replaceTabs(normalizeDisplayText(fileContent)), lang))\n\t\t\t: normalizeDisplayText(fileContent).split(\"\\n\");\n\t\tconst lines = trimTrailingEmptyLines(renderedLines);\n\t\tconst totalLines = lines.length;\n\t\tconst maxLines = options.expanded ? lines.length : 10;\n\t\tconst displayLines = lines.slice(0, maxLines);\n\t\tconst remaining = lines.length - maxLines;\n\t\ttext += `\\n\\n${displayLines.map((line) => (lang ? line : theme.fg(\"toolOutput\", replaceTabs(line)))).join(\"\\n\")}`;\n\t\tif (remaining > 0) {\n\t\t\ttext += `${theme.fg(\"muted\", `\\n... (${remaining} more lines, ${totalLines} total,`)} ${keyHint(\"app.tools.expand\", \"to expand\")}${theme.fg(\"muted\", \")\")}`;\n\t\t}\n\t}\n\n\treturn text;\n}\n\nfunction formatWriteResult(\n\tresult: { content: Array<{ type: string; text?: string; data?: string; mimeType?: string }>; isError?: boolean },\n\ttheme: Theme,\n): string | undefined {\n\tif (!result.isError) {\n\t\treturn undefined;\n\t}\n\tconst output = result.content\n\t\t.filter((c) => c.type === \"text\")\n\t\t.map((c) => c.text || \"\")\n\t\t.join(\"\\n\");\n\tif (!output) {\n\t\treturn undefined;\n\t}\n\treturn `\\n${theme.fg(\"error\", output)}`;\n}\n\nexport function createWriteToolDefinition(\n\tcwd: string,\n\toptions?: WriteToolOptions,\n): ToolDefinition<typeof writeSchema, undefined> {\n\tconst ops = options?.operations ?? defaultWriteOperations;\n\treturn {\n\t\tname: \"write\",\n\t\tlabel: \"write\",\n\t\tdescription:\n\t\t\t\"Write content to a file. Creates the file if it doesn't exist, overwrites if it does. Automatically creates parent directories.\",\n\t\tpromptSnippet: \"Create or overwrite files\",\n\t\tpromptGuidelines: [\"Use write only for new files or complete rewrites.\"],\n\t\tparameters: writeSchema,\n\t\tasync execute(\n\t\t\t_toolCallId,\n\t\t\t{ path, content }: { path: string; content: string },\n\t\t\tsignal?: AbortSignal,\n\t\t\t_onUpdate?,\n\t\t\t_ctx?,\n\t\t) {\n\t\t\tconst absolutePath = resolveToCwd(path, cwd);\n\t\t\tconst dir = dirname(absolutePath);\n\t\t\treturn withFileMutationQueue(absolutePath, async () => {\n\t\t\t\t// Do not reject from an abort event listener here: that would release the\n\t\t\t\t// mutation queue while an in-flight filesystem operation may still finish.\n\t\t\t\t// Checking signal.aborted after each await observes the same aborts while\n\t\t\t\t// keeping the queue locked until the current operation has settled.\n\t\t\t\tconst throwIfAborted = (): void => {\n\t\t\t\t\tif (signal?.aborted) throw new Error(\"Operation aborted\");\n\t\t\t\t};\n\n\t\t\t\tthrowIfAborted();\n\t\t\t\t// Create parent directories if needed.\n\t\t\t\tawait ops.mkdir(dir);\n\t\t\t\tthrowIfAborted();\n\n\t\t\t\t// Write the file contents.\n\t\t\t\tawait ops.writeFile(absolutePath, content);\n\t\t\t\tthrowIfAborted();\n\n\t\t\t\treturn {\n\t\t\t\t\tcontent: [{ type: \"text\", text: `Successfully wrote ${content.length} bytes to ${path}` }],\n\t\t\t\t\tdetails: undefined,\n\t\t\t\t};\n\t\t\t});\n\t\t},\n\t\trenderCall(args, theme, context) {\n\t\t\tconst renderArgs = args as { path?: string; file_path?: string; content?: string } | undefined;\n\t\t\tconst rawPath = str(renderArgs?.file_path ?? renderArgs?.path);\n\t\t\tconst fileContent = str(renderArgs?.content);\n\t\t\tconst component =\n\t\t\t\t(context.lastComponent as WriteCallRenderComponent | undefined) ?? new WriteCallRenderComponent();\n\t\t\tif (fileContent !== null) {\n\t\t\t\tcomponent.cache = context.argsComplete\n\t\t\t\t\t? rebuildWriteHighlightCacheFull(rawPath, fileContent)\n\t\t\t\t\t: updateWriteHighlightCacheIncremental(component.cache, rawPath, fileContent);\n\t\t\t} else {\n\t\t\t\tcomponent.cache = undefined;\n\t\t\t}\n\t\t\tcomponent.setText(\n\t\t\t\tformatWriteCall(\n\t\t\t\t\trenderArgs,\n\t\t\t\t\t{ expanded: context.expanded, isPartial: context.isPartial },\n\t\t\t\t\ttheme,\n\t\t\t\t\tcomponent.cache,\n\t\t\t\t\tcontext.cwd,\n\t\t\t\t),\n\t\t\t);\n\t\t\treturn component;\n\t\t},\n\t\trenderResult(result, _options, theme, context) {\n\t\t\tconst output = formatWriteResult({ ...result, isError: context.isError }, theme);\n\t\t\tif (!output) {\n\t\t\t\tconst component = (context.lastComponent as Container | undefined) ?? new Container();\n\t\t\t\tcomponent.clear();\n\t\t\t\treturn component;\n\t\t\t}\n\t\t\tconst text = (context.lastComponent as Text | undefined) ?? new Text(\"\", 0, 0);\n\t\t\ttext.setText(output);\n\t\t\treturn text;\n\t\t},\n\t};\n}\n\nexport function createWriteTool(cwd: string, options?: WriteToolOptions): AgentTool<typeof writeSchema> {\n\treturn wrapToolDefinition(createWriteToolDefinition(cwd, options));\n}\n"]}
1
+ {"version":3,"file":"write.d.ts","sourceRoot":"","sources":["../../../src/core/tools/write.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,+BAA+B,CAAC;AAI/D,OAAO,EAAE,KAAK,MAAM,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AAE5C,OAAO,KAAK,EAAE,cAAc,EAA2B,MAAM,wBAAwB,CAAC;AAMtF,QAAA,MAAM,WAAW;;;EAGf,CAAC;AAEH,MAAM,MAAM,cAAc,GAAG,MAAM,CAAC,OAAO,WAAW,CAAC,CAAC;AAExD;;;GAGG;AACH,MAAM,WAAW,eAAe;IAC/B,8BAA8B;IAC9B,SAAS,EAAE,CAAC,YAAY,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACpE,mCAAmC;IACnC,KAAK,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACtC;AAOD,MAAM,WAAW,gBAAgB;IAChC,oEAAoE;IACpE,UAAU,CAAC,EAAE,eAAe,CAAC;CAC7B;AA6ID,wBAAgB,yBAAyB,CACxC,GAAG,EAAE,MAAM,EACX,OAAO,CAAC,EAAE,gBAAgB,GACxB,cAAc,CAAC,OAAO,WAAW,EAAE,SAAS,CAAC,CA+E/C;AAED,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,gBAAgB,GAAG,SAAS,CAAC,OAAO,WAAW,CAAC,CAEtG","sourcesContent":["import type { AgentTool } from \"@earendil-works/pi-agent-core\";\nimport { Container, Text } from \"@earendil-works/pi-tui\";\nimport { mkdir as fsMkdir, writeFile as fsWriteFile } from \"fs/promises\";\nimport { dirname } from \"path\";\nimport { type Static, Type } from \"typebox\";\nimport { getLanguageFromPath, highlightCode, type Theme } from \"../../modes/interactive/theme/theme.ts\";\nimport type { ToolDefinition, ToolRenderResultOptions } from \"../extensions/types.ts\";\nimport { withFileMutationQueue } from \"./file-mutation-queue.ts\";\nimport { resolveToCwd } from \"./path-utils.ts\";\nimport { collapsedLinesHint, normalizeDisplayText, renderToolPath, replaceTabs, str } from \"./render-utils.ts\";\nimport { wrapToolDefinition } from \"./tool-definition-wrapper.ts\";\n\nconst writeSchema = Type.Object({\n\tpath: Type.String({ description: \"Path to the file to write (relative or absolute)\" }),\n\tcontent: Type.String({ description: \"Content to write to the file\" }),\n});\n\nexport type WriteToolInput = Static<typeof writeSchema>;\n\n/**\n * Pluggable operations for the write tool.\n * Override these to delegate file writing to remote systems (for example SSH).\n */\nexport interface WriteOperations {\n\t/** Write content to a file */\n\twriteFile: (absolutePath: string, content: string) => Promise<void>;\n\t/** Create directory recursively */\n\tmkdir: (dir: string) => Promise<void>;\n}\n\nconst defaultWriteOperations: WriteOperations = {\n\twriteFile: (path, content) => fsWriteFile(path, content, \"utf-8\"),\n\tmkdir: (dir) => fsMkdir(dir, { recursive: true }).then(() => {}),\n};\n\nexport interface WriteToolOptions {\n\t/** Custom operations for file writing. Default: local filesystem */\n\toperations?: WriteOperations;\n}\n\ntype WriteHighlightCache = {\n\trawPath: string | null;\n\tlang: string;\n\trawContent: string;\n\tnormalizedLines: string[];\n\thighlightedLines: string[];\n};\n\nclass WriteCallRenderComponent extends Text {\n\tcache?: WriteHighlightCache;\n\n\tconstructor() {\n\t\tsuper(\"\", 0, 0);\n\t}\n}\n\nconst WRITE_PARTIAL_FULL_HIGHLIGHT_LINES = 50;\n\nfunction highlightSingleLine(line: string, lang: string): string {\n\tconst highlighted = highlightCode(line, lang);\n\treturn highlighted[0] ?? \"\";\n}\n\nfunction refreshWriteHighlightPrefix(cache: WriteHighlightCache): void {\n\tconst prefixCount = Math.min(WRITE_PARTIAL_FULL_HIGHLIGHT_LINES, cache.normalizedLines.length);\n\tif (prefixCount === 0) return;\n\tconst prefixSource = cache.normalizedLines.slice(0, prefixCount).join(\"\\n\");\n\tconst prefixHighlighted = highlightCode(prefixSource, cache.lang);\n\tfor (let i = 0; i < prefixCount; i++) {\n\t\tcache.highlightedLines[i] =\n\t\t\tprefixHighlighted[i] ?? highlightSingleLine(cache.normalizedLines[i] ?? \"\", cache.lang);\n\t}\n}\n\nfunction rebuildWriteHighlightCacheFull(rawPath: string | null, fileContent: string): WriteHighlightCache | undefined {\n\tconst lang = rawPath ? getLanguageFromPath(rawPath) : undefined;\n\tif (!lang) return undefined;\n\tconst displayContent = normalizeDisplayText(fileContent);\n\tconst normalized = replaceTabs(displayContent);\n\treturn {\n\t\trawPath,\n\t\tlang,\n\t\trawContent: fileContent,\n\t\tnormalizedLines: normalized.split(\"\\n\"),\n\t\thighlightedLines: highlightCode(normalized, lang),\n\t};\n}\n\nfunction updateWriteHighlightCacheIncremental(\n\tcache: WriteHighlightCache | undefined,\n\trawPath: string | null,\n\tfileContent: string,\n): WriteHighlightCache | undefined {\n\tconst lang = rawPath ? getLanguageFromPath(rawPath) : undefined;\n\tif (!lang) return undefined;\n\tif (!cache) return rebuildWriteHighlightCacheFull(rawPath, fileContent);\n\tif (cache.lang !== lang || cache.rawPath !== rawPath) return rebuildWriteHighlightCacheFull(rawPath, fileContent);\n\tif (!fileContent.startsWith(cache.rawContent)) return rebuildWriteHighlightCacheFull(rawPath, fileContent);\n\tif (fileContent.length === cache.rawContent.length) return cache;\n\n\tconst deltaRaw = fileContent.slice(cache.rawContent.length);\n\tconst deltaDisplay = normalizeDisplayText(deltaRaw);\n\tconst deltaNormalized = replaceTabs(deltaDisplay);\n\tcache.rawContent = fileContent;\n\tif (cache.normalizedLines.length === 0) {\n\t\tcache.normalizedLines.push(\"\");\n\t\tcache.highlightedLines.push(\"\");\n\t}\n\n\tconst segments = deltaNormalized.split(\"\\n\");\n\tconst lastIndex = cache.normalizedLines.length - 1;\n\tcache.normalizedLines[lastIndex] += segments[0];\n\tcache.highlightedLines[lastIndex] = highlightSingleLine(cache.normalizedLines[lastIndex], cache.lang);\n\tfor (let i = 1; i < segments.length; i++) {\n\t\tcache.normalizedLines.push(segments[i]);\n\t\tcache.highlightedLines.push(highlightSingleLine(segments[i], cache.lang));\n\t}\n\trefreshWriteHighlightPrefix(cache);\n\treturn cache;\n}\n\nfunction trimTrailingEmptyLines(lines: string[]): string[] {\n\tlet end = lines.length;\n\twhile (end > 0 && lines[end - 1] === \"\") {\n\t\tend--;\n\t}\n\treturn lines.slice(0, end);\n}\n\nfunction formatWriteCall(\n\targs: { path?: string; file_path?: string; content?: string } | undefined,\n\toptions: ToolRenderResultOptions,\n\ttheme: Theme,\n\tcache: WriteHighlightCache | undefined,\n\tcwd: string,\n): string {\n\tconst rawPath = str(args?.file_path ?? args?.path);\n\tconst fileContent = str(args?.content);\n\tconst pathDisplay = renderToolPath(rawPath, theme, cwd);\n\tlet text = `${theme.fg(\"toolTitle\", theme.bold(\"write\"))} ${pathDisplay}`;\n\n\tif (fileContent === null) {\n\t\ttext += `\\n\\n${theme.fg(\"error\", \"[invalid content arg - expected string]\")}`;\n\t} else if (fileContent) {\n\t\tconst lang = rawPath ? getLanguageFromPath(rawPath) : undefined;\n\t\tconst renderedLines = lang\n\t\t\t? (cache?.highlightedLines ?? highlightCode(replaceTabs(normalizeDisplayText(fileContent)), lang))\n\t\t\t: normalizeDisplayText(fileContent).split(\"\\n\");\n\t\tconst lines = trimTrailingEmptyLines(renderedLines);\n\t\tconst totalLines = lines.length;\n\t\tconst maxLines = options.expanded ? lines.length : 10;\n\t\tconst displayLines = lines.slice(0, maxLines);\n\t\tconst remaining = lines.length - maxLines;\n\t\ttext += `\\n\\n${displayLines.map((line) => (lang ? line : theme.fg(\"toolOutput\", replaceTabs(line)))).join(\"\\n\")}`;\n\t\tif (remaining > 0) {\n\t\t\ttext += `\\n${collapsedLinesHint(theme, remaining, \"more\", { total: totalLines })}`;\n\t\t}\n\t}\n\n\treturn text;\n}\n\nfunction formatWriteResult(\n\tresult: { content: Array<{ type: string; text?: string; data?: string; mimeType?: string }>; isError?: boolean },\n\ttheme: Theme,\n): string | undefined {\n\tif (!result.isError) {\n\t\treturn undefined;\n\t}\n\tconst output = result.content\n\t\t.filter((c) => c.type === \"text\")\n\t\t.map((c) => c.text || \"\")\n\t\t.join(\"\\n\");\n\tif (!output) {\n\t\treturn undefined;\n\t}\n\treturn `\\n${theme.fg(\"error\", output)}`;\n}\n\nexport function createWriteToolDefinition(\n\tcwd: string,\n\toptions?: WriteToolOptions,\n): ToolDefinition<typeof writeSchema, undefined> {\n\tconst ops = options?.operations ?? defaultWriteOperations;\n\treturn {\n\t\tname: \"write\",\n\t\tlabel: \"write\",\n\t\tdescription:\n\t\t\t\"Write content to a file. Creates the file if it doesn't exist, overwrites if it does. Automatically creates parent directories.\",\n\t\tpromptSnippet: \"Create or overwrite files\",\n\t\tpromptGuidelines: [\"Use write only for new files or complete rewrites.\"],\n\t\tparameters: writeSchema,\n\t\tasync execute(\n\t\t\t_toolCallId,\n\t\t\t{ path, content }: { path: string; content: string },\n\t\t\tsignal?: AbortSignal,\n\t\t\t_onUpdate?,\n\t\t\t_ctx?,\n\t\t) {\n\t\t\tconst absolutePath = resolveToCwd(path, cwd);\n\t\t\tconst dir = dirname(absolutePath);\n\t\t\treturn withFileMutationQueue(absolutePath, async () => {\n\t\t\t\t// Do not reject from an abort event listener here: that would release the\n\t\t\t\t// mutation queue while an in-flight filesystem operation may still finish.\n\t\t\t\t// Checking signal.aborted after each await observes the same aborts while\n\t\t\t\t// keeping the queue locked until the current operation has settled.\n\t\t\t\tconst throwIfAborted = (): void => {\n\t\t\t\t\tif (signal?.aborted) throw new Error(\"Operation aborted\");\n\t\t\t\t};\n\n\t\t\t\tthrowIfAborted();\n\t\t\t\t// Create parent directories if needed.\n\t\t\t\tawait ops.mkdir(dir);\n\t\t\t\tthrowIfAborted();\n\n\t\t\t\t// Write the file contents.\n\t\t\t\tawait ops.writeFile(absolutePath, content);\n\t\t\t\tthrowIfAborted();\n\n\t\t\t\treturn {\n\t\t\t\t\tcontent: [{ type: \"text\", text: `Successfully wrote ${content.length} bytes to ${path}` }],\n\t\t\t\t\tdetails: undefined,\n\t\t\t\t};\n\t\t\t});\n\t\t},\n\t\trenderCall(args, theme, context) {\n\t\t\tconst renderArgs = args as { path?: string; file_path?: string; content?: string } | undefined;\n\t\t\tconst rawPath = str(renderArgs?.file_path ?? renderArgs?.path);\n\t\t\tconst fileContent = str(renderArgs?.content);\n\t\t\tconst component =\n\t\t\t\t(context.lastComponent as WriteCallRenderComponent | undefined) ?? new WriteCallRenderComponent();\n\t\t\tif (fileContent !== null) {\n\t\t\t\tcomponent.cache = context.argsComplete\n\t\t\t\t\t? rebuildWriteHighlightCacheFull(rawPath, fileContent)\n\t\t\t\t\t: updateWriteHighlightCacheIncremental(component.cache, rawPath, fileContent);\n\t\t\t} else {\n\t\t\t\tcomponent.cache = undefined;\n\t\t\t}\n\t\t\tcomponent.setText(\n\t\t\t\tformatWriteCall(\n\t\t\t\t\trenderArgs,\n\t\t\t\t\t{ expanded: context.expanded, isPartial: context.isPartial },\n\t\t\t\t\ttheme,\n\t\t\t\t\tcomponent.cache,\n\t\t\t\t\tcontext.cwd,\n\t\t\t\t),\n\t\t\t);\n\t\t\treturn component;\n\t\t},\n\t\trenderResult(result, _options, theme, context) {\n\t\t\tconst output = formatWriteResult({ ...result, isError: context.isError }, theme);\n\t\t\tif (!output) {\n\t\t\t\tconst component = (context.lastComponent as Container | undefined) ?? new Container();\n\t\t\t\tcomponent.clear();\n\t\t\t\treturn component;\n\t\t\t}\n\t\t\tconst text = (context.lastComponent as Text | undefined) ?? new Text(\"\", 0, 0);\n\t\t\ttext.setText(output);\n\t\t\treturn text;\n\t\t},\n\t};\n}\n\nexport function createWriteTool(cwd: string, options?: WriteToolOptions): AgentTool<typeof writeSchema> {\n\treturn wrapToolDefinition(createWriteToolDefinition(cwd, options));\n}\n"]}
@@ -2,11 +2,10 @@ import { Container, Text } from "@earendil-works/pi-tui";
2
2
  import { mkdir as fsMkdir, writeFile as fsWriteFile } from "fs/promises";
3
3
  import { dirname } from "path";
4
4
  import { Type } from "typebox";
5
- import { keyHint } from "../../modes/interactive/components/keybinding-hints.js";
6
5
  import { getLanguageFromPath, highlightCode } from "../../modes/interactive/theme/theme.js";
7
6
  import { withFileMutationQueue } from "./file-mutation-queue.js";
8
7
  import { resolveToCwd } from "./path-utils.js";
9
- import { normalizeDisplayText, renderToolPath, replaceTabs, str } from "./render-utils.js";
8
+ import { collapsedLinesHint, normalizeDisplayText, renderToolPath, replaceTabs, str } from "./render-utils.js";
10
9
  import { wrapToolDefinition } from "./tool-definition-wrapper.js";
11
10
  const writeSchema = Type.Object({
12
11
  path: Type.String({ description: "Path to the file to write (relative or absolute)" }),
@@ -110,7 +109,7 @@ function formatWriteCall(args, options, theme, cache, cwd) {
110
109
  const remaining = lines.length - maxLines;
111
110
  text += `\n\n${displayLines.map((line) => (lang ? line : theme.fg("toolOutput", replaceTabs(line)))).join("\n")}`;
112
111
  if (remaining > 0) {
113
- text += `${theme.fg("muted", `\n... (${remaining} more lines, ${totalLines} total,`)} ${keyHint("app.tools.expand", "to expand")}${theme.fg("muted", ")")}`;
112
+ text += `\n${collapsedLinesHint(theme, remaining, "more", { total: totalLines })}`;
114
113
  }
115
114
  }
116
115
  return text;
@@ -1 +1 @@
1
- {"version":3,"file":"write.js","sourceRoot":"","sources":["../../../src/core/tools/write.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AgentTool } from \"@earendil-works/pi-agent-core\";\nimport { Container, Text } from \"@earendil-works/pi-tui\";\nimport { mkdir as fsMkdir, writeFile as fsWriteFile } from \"fs/promises\";\nimport { dirname } from \"path\";\nimport { type Static, Type } from \"typebox\";\nimport { keyHint } from \"../../modes/interactive/components/keybinding-hints.ts\";\nimport { getLanguageFromPath, highlightCode, type Theme } from \"../../modes/interactive/theme/theme.ts\";\nimport type { ToolDefinition, ToolRenderResultOptions } from \"../extensions/types.ts\";\nimport { withFileMutationQueue } from \"./file-mutation-queue.ts\";\nimport { resolveToCwd } from \"./path-utils.ts\";\nimport { normalizeDisplayText, renderToolPath, replaceTabs, str } from \"./render-utils.ts\";\nimport { wrapToolDefinition } from \"./tool-definition-wrapper.ts\";\n\nconst writeSchema = Type.Object({\n\tpath: Type.String({ description: \"Path to the file to write (relative or absolute)\" }),\n\tcontent: Type.String({ description: \"Content to write to the file\" }),\n});\n\nexport type WriteToolInput = Static<typeof writeSchema>;\n\n/**\n * Pluggable operations for the write tool.\n * Override these to delegate file writing to remote systems (for example SSH).\n */\nexport interface WriteOperations {\n\t/** Write content to a file */\n\twriteFile: (absolutePath: string, content: string) => Promise<void>;\n\t/** Create directory recursively */\n\tmkdir: (dir: string) => Promise<void>;\n}\n\nconst defaultWriteOperations: WriteOperations = {\n\twriteFile: (path, content) => fsWriteFile(path, content, \"utf-8\"),\n\tmkdir: (dir) => fsMkdir(dir, { recursive: true }).then(() => {}),\n};\n\nexport interface WriteToolOptions {\n\t/** Custom operations for file writing. Default: local filesystem */\n\toperations?: WriteOperations;\n}\n\ntype WriteHighlightCache = {\n\trawPath: string | null;\n\tlang: string;\n\trawContent: string;\n\tnormalizedLines: string[];\n\thighlightedLines: string[];\n};\n\nclass WriteCallRenderComponent extends Text {\n\tcache?: WriteHighlightCache;\n\n\tconstructor() {\n\t\tsuper(\"\", 0, 0);\n\t}\n}\n\nconst WRITE_PARTIAL_FULL_HIGHLIGHT_LINES = 50;\n\nfunction highlightSingleLine(line: string, lang: string): string {\n\tconst highlighted = highlightCode(line, lang);\n\treturn highlighted[0] ?? \"\";\n}\n\nfunction refreshWriteHighlightPrefix(cache: WriteHighlightCache): void {\n\tconst prefixCount = Math.min(WRITE_PARTIAL_FULL_HIGHLIGHT_LINES, cache.normalizedLines.length);\n\tif (prefixCount === 0) return;\n\tconst prefixSource = cache.normalizedLines.slice(0, prefixCount).join(\"\\n\");\n\tconst prefixHighlighted = highlightCode(prefixSource, cache.lang);\n\tfor (let i = 0; i < prefixCount; i++) {\n\t\tcache.highlightedLines[i] =\n\t\t\tprefixHighlighted[i] ?? highlightSingleLine(cache.normalizedLines[i] ?? \"\", cache.lang);\n\t}\n}\n\nfunction rebuildWriteHighlightCacheFull(rawPath: string | null, fileContent: string): WriteHighlightCache | undefined {\n\tconst lang = rawPath ? getLanguageFromPath(rawPath) : undefined;\n\tif (!lang) return undefined;\n\tconst displayContent = normalizeDisplayText(fileContent);\n\tconst normalized = replaceTabs(displayContent);\n\treturn {\n\t\trawPath,\n\t\tlang,\n\t\trawContent: fileContent,\n\t\tnormalizedLines: normalized.split(\"\\n\"),\n\t\thighlightedLines: highlightCode(normalized, lang),\n\t};\n}\n\nfunction updateWriteHighlightCacheIncremental(\n\tcache: WriteHighlightCache | undefined,\n\trawPath: string | null,\n\tfileContent: string,\n): WriteHighlightCache | undefined {\n\tconst lang = rawPath ? getLanguageFromPath(rawPath) : undefined;\n\tif (!lang) return undefined;\n\tif (!cache) return rebuildWriteHighlightCacheFull(rawPath, fileContent);\n\tif (cache.lang !== lang || cache.rawPath !== rawPath) return rebuildWriteHighlightCacheFull(rawPath, fileContent);\n\tif (!fileContent.startsWith(cache.rawContent)) return rebuildWriteHighlightCacheFull(rawPath, fileContent);\n\tif (fileContent.length === cache.rawContent.length) return cache;\n\n\tconst deltaRaw = fileContent.slice(cache.rawContent.length);\n\tconst deltaDisplay = normalizeDisplayText(deltaRaw);\n\tconst deltaNormalized = replaceTabs(deltaDisplay);\n\tcache.rawContent = fileContent;\n\tif (cache.normalizedLines.length === 0) {\n\t\tcache.normalizedLines.push(\"\");\n\t\tcache.highlightedLines.push(\"\");\n\t}\n\n\tconst segments = deltaNormalized.split(\"\\n\");\n\tconst lastIndex = cache.normalizedLines.length - 1;\n\tcache.normalizedLines[lastIndex] += segments[0];\n\tcache.highlightedLines[lastIndex] = highlightSingleLine(cache.normalizedLines[lastIndex], cache.lang);\n\tfor (let i = 1; i < segments.length; i++) {\n\t\tcache.normalizedLines.push(segments[i]);\n\t\tcache.highlightedLines.push(highlightSingleLine(segments[i], cache.lang));\n\t}\n\trefreshWriteHighlightPrefix(cache);\n\treturn cache;\n}\n\nfunction trimTrailingEmptyLines(lines: string[]): string[] {\n\tlet end = lines.length;\n\twhile (end > 0 && lines[end - 1] === \"\") {\n\t\tend--;\n\t}\n\treturn lines.slice(0, end);\n}\n\nfunction formatWriteCall(\n\targs: { path?: string; file_path?: string; content?: string } | undefined,\n\toptions: ToolRenderResultOptions,\n\ttheme: Theme,\n\tcache: WriteHighlightCache | undefined,\n\tcwd: string,\n): string {\n\tconst rawPath = str(args?.file_path ?? args?.path);\n\tconst fileContent = str(args?.content);\n\tconst pathDisplay = renderToolPath(rawPath, theme, cwd);\n\tlet text = `${theme.fg(\"toolTitle\", theme.bold(\"write\"))} ${pathDisplay}`;\n\n\tif (fileContent === null) {\n\t\ttext += `\\n\\n${theme.fg(\"error\", \"[invalid content arg - expected string]\")}`;\n\t} else if (fileContent) {\n\t\tconst lang = rawPath ? getLanguageFromPath(rawPath) : undefined;\n\t\tconst renderedLines = lang\n\t\t\t? (cache?.highlightedLines ?? highlightCode(replaceTabs(normalizeDisplayText(fileContent)), lang))\n\t\t\t: normalizeDisplayText(fileContent).split(\"\\n\");\n\t\tconst lines = trimTrailingEmptyLines(renderedLines);\n\t\tconst totalLines = lines.length;\n\t\tconst maxLines = options.expanded ? lines.length : 10;\n\t\tconst displayLines = lines.slice(0, maxLines);\n\t\tconst remaining = lines.length - maxLines;\n\t\ttext += `\\n\\n${displayLines.map((line) => (lang ? line : theme.fg(\"toolOutput\", replaceTabs(line)))).join(\"\\n\")}`;\n\t\tif (remaining > 0) {\n\t\t\ttext += `${theme.fg(\"muted\", `\\n... (${remaining} more lines, ${totalLines} total,`)} ${keyHint(\"app.tools.expand\", \"to expand\")}${theme.fg(\"muted\", \")\")}`;\n\t\t}\n\t}\n\n\treturn text;\n}\n\nfunction formatWriteResult(\n\tresult: { content: Array<{ type: string; text?: string; data?: string; mimeType?: string }>; isError?: boolean },\n\ttheme: Theme,\n): string | undefined {\n\tif (!result.isError) {\n\t\treturn undefined;\n\t}\n\tconst output = result.content\n\t\t.filter((c) => c.type === \"text\")\n\t\t.map((c) => c.text || \"\")\n\t\t.join(\"\\n\");\n\tif (!output) {\n\t\treturn undefined;\n\t}\n\treturn `\\n${theme.fg(\"error\", output)}`;\n}\n\nexport function createWriteToolDefinition(\n\tcwd: string,\n\toptions?: WriteToolOptions,\n): ToolDefinition<typeof writeSchema, undefined> {\n\tconst ops = options?.operations ?? defaultWriteOperations;\n\treturn {\n\t\tname: \"write\",\n\t\tlabel: \"write\",\n\t\tdescription:\n\t\t\t\"Write content to a file. Creates the file if it doesn't exist, overwrites if it does. Automatically creates parent directories.\",\n\t\tpromptSnippet: \"Create or overwrite files\",\n\t\tpromptGuidelines: [\"Use write only for new files or complete rewrites.\"],\n\t\tparameters: writeSchema,\n\t\tasync execute(\n\t\t\t_toolCallId,\n\t\t\t{ path, content }: { path: string; content: string },\n\t\t\tsignal?: AbortSignal,\n\t\t\t_onUpdate?,\n\t\t\t_ctx?,\n\t\t) {\n\t\t\tconst absolutePath = resolveToCwd(path, cwd);\n\t\t\tconst dir = dirname(absolutePath);\n\t\t\treturn withFileMutationQueue(absolutePath, async () => {\n\t\t\t\t// Do not reject from an abort event listener here: that would release the\n\t\t\t\t// mutation queue while an in-flight filesystem operation may still finish.\n\t\t\t\t// Checking signal.aborted after each await observes the same aborts while\n\t\t\t\t// keeping the queue locked until the current operation has settled.\n\t\t\t\tconst throwIfAborted = (): void => {\n\t\t\t\t\tif (signal?.aborted) throw new Error(\"Operation aborted\");\n\t\t\t\t};\n\n\t\t\t\tthrowIfAborted();\n\t\t\t\t// Create parent directories if needed.\n\t\t\t\tawait ops.mkdir(dir);\n\t\t\t\tthrowIfAborted();\n\n\t\t\t\t// Write the file contents.\n\t\t\t\tawait ops.writeFile(absolutePath, content);\n\t\t\t\tthrowIfAborted();\n\n\t\t\t\treturn {\n\t\t\t\t\tcontent: [{ type: \"text\", text: `Successfully wrote ${content.length} bytes to ${path}` }],\n\t\t\t\t\tdetails: undefined,\n\t\t\t\t};\n\t\t\t});\n\t\t},\n\t\trenderCall(args, theme, context) {\n\t\t\tconst renderArgs = args as { path?: string; file_path?: string; content?: string } | undefined;\n\t\t\tconst rawPath = str(renderArgs?.file_path ?? renderArgs?.path);\n\t\t\tconst fileContent = str(renderArgs?.content);\n\t\t\tconst component =\n\t\t\t\t(context.lastComponent as WriteCallRenderComponent | undefined) ?? new WriteCallRenderComponent();\n\t\t\tif (fileContent !== null) {\n\t\t\t\tcomponent.cache = context.argsComplete\n\t\t\t\t\t? rebuildWriteHighlightCacheFull(rawPath, fileContent)\n\t\t\t\t\t: updateWriteHighlightCacheIncremental(component.cache, rawPath, fileContent);\n\t\t\t} else {\n\t\t\t\tcomponent.cache = undefined;\n\t\t\t}\n\t\t\tcomponent.setText(\n\t\t\t\tformatWriteCall(\n\t\t\t\t\trenderArgs,\n\t\t\t\t\t{ expanded: context.expanded, isPartial: context.isPartial },\n\t\t\t\t\ttheme,\n\t\t\t\t\tcomponent.cache,\n\t\t\t\t\tcontext.cwd,\n\t\t\t\t),\n\t\t\t);\n\t\t\treturn component;\n\t\t},\n\t\trenderResult(result, _options, theme, context) {\n\t\t\tconst output = formatWriteResult({ ...result, isError: context.isError }, theme);\n\t\t\tif (!output) {\n\t\t\t\tconst component = (context.lastComponent as Container | undefined) ?? new Container();\n\t\t\t\tcomponent.clear();\n\t\t\t\treturn component;\n\t\t\t}\n\t\t\tconst text = (context.lastComponent as Text | undefined) ?? new Text(\"\", 0, 0);\n\t\t\ttext.setText(output);\n\t\t\treturn text;\n\t\t},\n\t};\n}\n\nexport function createWriteTool(cwd: string, options?: WriteToolOptions): AgentTool<typeof writeSchema> {\n\treturn wrapToolDefinition(createWriteToolDefinition(cwd, options));\n}\n"]}
1
+ {"version":3,"file":"write.js","sourceRoot":"","sources":["../../../src/core/tools/write.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AgentTool } from \"@earendil-works/pi-agent-core\";\nimport { Container, Text } from \"@earendil-works/pi-tui\";\nimport { mkdir as fsMkdir, writeFile as fsWriteFile } from \"fs/promises\";\nimport { dirname } from \"path\";\nimport { type Static, Type } from \"typebox\";\nimport { getLanguageFromPath, highlightCode, type Theme } from \"../../modes/interactive/theme/theme.ts\";\nimport type { ToolDefinition, ToolRenderResultOptions } from \"../extensions/types.ts\";\nimport { withFileMutationQueue } from \"./file-mutation-queue.ts\";\nimport { resolveToCwd } from \"./path-utils.ts\";\nimport { collapsedLinesHint, normalizeDisplayText, renderToolPath, replaceTabs, str } from \"./render-utils.ts\";\nimport { wrapToolDefinition } from \"./tool-definition-wrapper.ts\";\n\nconst writeSchema = Type.Object({\n\tpath: Type.String({ description: \"Path to the file to write (relative or absolute)\" }),\n\tcontent: Type.String({ description: \"Content to write to the file\" }),\n});\n\nexport type WriteToolInput = Static<typeof writeSchema>;\n\n/**\n * Pluggable operations for the write tool.\n * Override these to delegate file writing to remote systems (for example SSH).\n */\nexport interface WriteOperations {\n\t/** Write content to a file */\n\twriteFile: (absolutePath: string, content: string) => Promise<void>;\n\t/** Create directory recursively */\n\tmkdir: (dir: string) => Promise<void>;\n}\n\nconst defaultWriteOperations: WriteOperations = {\n\twriteFile: (path, content) => fsWriteFile(path, content, \"utf-8\"),\n\tmkdir: (dir) => fsMkdir(dir, { recursive: true }).then(() => {}),\n};\n\nexport interface WriteToolOptions {\n\t/** Custom operations for file writing. Default: local filesystem */\n\toperations?: WriteOperations;\n}\n\ntype WriteHighlightCache = {\n\trawPath: string | null;\n\tlang: string;\n\trawContent: string;\n\tnormalizedLines: string[];\n\thighlightedLines: string[];\n};\n\nclass WriteCallRenderComponent extends Text {\n\tcache?: WriteHighlightCache;\n\n\tconstructor() {\n\t\tsuper(\"\", 0, 0);\n\t}\n}\n\nconst WRITE_PARTIAL_FULL_HIGHLIGHT_LINES = 50;\n\nfunction highlightSingleLine(line: string, lang: string): string {\n\tconst highlighted = highlightCode(line, lang);\n\treturn highlighted[0] ?? \"\";\n}\n\nfunction refreshWriteHighlightPrefix(cache: WriteHighlightCache): void {\n\tconst prefixCount = Math.min(WRITE_PARTIAL_FULL_HIGHLIGHT_LINES, cache.normalizedLines.length);\n\tif (prefixCount === 0) return;\n\tconst prefixSource = cache.normalizedLines.slice(0, prefixCount).join(\"\\n\");\n\tconst prefixHighlighted = highlightCode(prefixSource, cache.lang);\n\tfor (let i = 0; i < prefixCount; i++) {\n\t\tcache.highlightedLines[i] =\n\t\t\tprefixHighlighted[i] ?? highlightSingleLine(cache.normalizedLines[i] ?? \"\", cache.lang);\n\t}\n}\n\nfunction rebuildWriteHighlightCacheFull(rawPath: string | null, fileContent: string): WriteHighlightCache | undefined {\n\tconst lang = rawPath ? getLanguageFromPath(rawPath) : undefined;\n\tif (!lang) return undefined;\n\tconst displayContent = normalizeDisplayText(fileContent);\n\tconst normalized = replaceTabs(displayContent);\n\treturn {\n\t\trawPath,\n\t\tlang,\n\t\trawContent: fileContent,\n\t\tnormalizedLines: normalized.split(\"\\n\"),\n\t\thighlightedLines: highlightCode(normalized, lang),\n\t};\n}\n\nfunction updateWriteHighlightCacheIncremental(\n\tcache: WriteHighlightCache | undefined,\n\trawPath: string | null,\n\tfileContent: string,\n): WriteHighlightCache | undefined {\n\tconst lang = rawPath ? getLanguageFromPath(rawPath) : undefined;\n\tif (!lang) return undefined;\n\tif (!cache) return rebuildWriteHighlightCacheFull(rawPath, fileContent);\n\tif (cache.lang !== lang || cache.rawPath !== rawPath) return rebuildWriteHighlightCacheFull(rawPath, fileContent);\n\tif (!fileContent.startsWith(cache.rawContent)) return rebuildWriteHighlightCacheFull(rawPath, fileContent);\n\tif (fileContent.length === cache.rawContent.length) return cache;\n\n\tconst deltaRaw = fileContent.slice(cache.rawContent.length);\n\tconst deltaDisplay = normalizeDisplayText(deltaRaw);\n\tconst deltaNormalized = replaceTabs(deltaDisplay);\n\tcache.rawContent = fileContent;\n\tif (cache.normalizedLines.length === 0) {\n\t\tcache.normalizedLines.push(\"\");\n\t\tcache.highlightedLines.push(\"\");\n\t}\n\n\tconst segments = deltaNormalized.split(\"\\n\");\n\tconst lastIndex = cache.normalizedLines.length - 1;\n\tcache.normalizedLines[lastIndex] += segments[0];\n\tcache.highlightedLines[lastIndex] = highlightSingleLine(cache.normalizedLines[lastIndex], cache.lang);\n\tfor (let i = 1; i < segments.length; i++) {\n\t\tcache.normalizedLines.push(segments[i]);\n\t\tcache.highlightedLines.push(highlightSingleLine(segments[i], cache.lang));\n\t}\n\trefreshWriteHighlightPrefix(cache);\n\treturn cache;\n}\n\nfunction trimTrailingEmptyLines(lines: string[]): string[] {\n\tlet end = lines.length;\n\twhile (end > 0 && lines[end - 1] === \"\") {\n\t\tend--;\n\t}\n\treturn lines.slice(0, end);\n}\n\nfunction formatWriteCall(\n\targs: { path?: string; file_path?: string; content?: string } | undefined,\n\toptions: ToolRenderResultOptions,\n\ttheme: Theme,\n\tcache: WriteHighlightCache | undefined,\n\tcwd: string,\n): string {\n\tconst rawPath = str(args?.file_path ?? args?.path);\n\tconst fileContent = str(args?.content);\n\tconst pathDisplay = renderToolPath(rawPath, theme, cwd);\n\tlet text = `${theme.fg(\"toolTitle\", theme.bold(\"write\"))} ${pathDisplay}`;\n\n\tif (fileContent === null) {\n\t\ttext += `\\n\\n${theme.fg(\"error\", \"[invalid content arg - expected string]\")}`;\n\t} else if (fileContent) {\n\t\tconst lang = rawPath ? getLanguageFromPath(rawPath) : undefined;\n\t\tconst renderedLines = lang\n\t\t\t? (cache?.highlightedLines ?? highlightCode(replaceTabs(normalizeDisplayText(fileContent)), lang))\n\t\t\t: normalizeDisplayText(fileContent).split(\"\\n\");\n\t\tconst lines = trimTrailingEmptyLines(renderedLines);\n\t\tconst totalLines = lines.length;\n\t\tconst maxLines = options.expanded ? lines.length : 10;\n\t\tconst displayLines = lines.slice(0, maxLines);\n\t\tconst remaining = lines.length - maxLines;\n\t\ttext += `\\n\\n${displayLines.map((line) => (lang ? line : theme.fg(\"toolOutput\", replaceTabs(line)))).join(\"\\n\")}`;\n\t\tif (remaining > 0) {\n\t\t\ttext += `\\n${collapsedLinesHint(theme, remaining, \"more\", { total: totalLines })}`;\n\t\t}\n\t}\n\n\treturn text;\n}\n\nfunction formatWriteResult(\n\tresult: { content: Array<{ type: string; text?: string; data?: string; mimeType?: string }>; isError?: boolean },\n\ttheme: Theme,\n): string | undefined {\n\tif (!result.isError) {\n\t\treturn undefined;\n\t}\n\tconst output = result.content\n\t\t.filter((c) => c.type === \"text\")\n\t\t.map((c) => c.text || \"\")\n\t\t.join(\"\\n\");\n\tif (!output) {\n\t\treturn undefined;\n\t}\n\treturn `\\n${theme.fg(\"error\", output)}`;\n}\n\nexport function createWriteToolDefinition(\n\tcwd: string,\n\toptions?: WriteToolOptions,\n): ToolDefinition<typeof writeSchema, undefined> {\n\tconst ops = options?.operations ?? defaultWriteOperations;\n\treturn {\n\t\tname: \"write\",\n\t\tlabel: \"write\",\n\t\tdescription:\n\t\t\t\"Write content to a file. Creates the file if it doesn't exist, overwrites if it does. Automatically creates parent directories.\",\n\t\tpromptSnippet: \"Create or overwrite files\",\n\t\tpromptGuidelines: [\"Use write only for new files or complete rewrites.\"],\n\t\tparameters: writeSchema,\n\t\tasync execute(\n\t\t\t_toolCallId,\n\t\t\t{ path, content }: { path: string; content: string },\n\t\t\tsignal?: AbortSignal,\n\t\t\t_onUpdate?,\n\t\t\t_ctx?,\n\t\t) {\n\t\t\tconst absolutePath = resolveToCwd(path, cwd);\n\t\t\tconst dir = dirname(absolutePath);\n\t\t\treturn withFileMutationQueue(absolutePath, async () => {\n\t\t\t\t// Do not reject from an abort event listener here: that would release the\n\t\t\t\t// mutation queue while an in-flight filesystem operation may still finish.\n\t\t\t\t// Checking signal.aborted after each await observes the same aborts while\n\t\t\t\t// keeping the queue locked until the current operation has settled.\n\t\t\t\tconst throwIfAborted = (): void => {\n\t\t\t\t\tif (signal?.aborted) throw new Error(\"Operation aborted\");\n\t\t\t\t};\n\n\t\t\t\tthrowIfAborted();\n\t\t\t\t// Create parent directories if needed.\n\t\t\t\tawait ops.mkdir(dir);\n\t\t\t\tthrowIfAborted();\n\n\t\t\t\t// Write the file contents.\n\t\t\t\tawait ops.writeFile(absolutePath, content);\n\t\t\t\tthrowIfAborted();\n\n\t\t\t\treturn {\n\t\t\t\t\tcontent: [{ type: \"text\", text: `Successfully wrote ${content.length} bytes to ${path}` }],\n\t\t\t\t\tdetails: undefined,\n\t\t\t\t};\n\t\t\t});\n\t\t},\n\t\trenderCall(args, theme, context) {\n\t\t\tconst renderArgs = args as { path?: string; file_path?: string; content?: string } | undefined;\n\t\t\tconst rawPath = str(renderArgs?.file_path ?? renderArgs?.path);\n\t\t\tconst fileContent = str(renderArgs?.content);\n\t\t\tconst component =\n\t\t\t\t(context.lastComponent as WriteCallRenderComponent | undefined) ?? new WriteCallRenderComponent();\n\t\t\tif (fileContent !== null) {\n\t\t\t\tcomponent.cache = context.argsComplete\n\t\t\t\t\t? rebuildWriteHighlightCacheFull(rawPath, fileContent)\n\t\t\t\t\t: updateWriteHighlightCacheIncremental(component.cache, rawPath, fileContent);\n\t\t\t} else {\n\t\t\t\tcomponent.cache = undefined;\n\t\t\t}\n\t\t\tcomponent.setText(\n\t\t\t\tformatWriteCall(\n\t\t\t\t\trenderArgs,\n\t\t\t\t\t{ expanded: context.expanded, isPartial: context.isPartial },\n\t\t\t\t\ttheme,\n\t\t\t\t\tcomponent.cache,\n\t\t\t\t\tcontext.cwd,\n\t\t\t\t),\n\t\t\t);\n\t\t\treturn component;\n\t\t},\n\t\trenderResult(result, _options, theme, context) {\n\t\t\tconst output = formatWriteResult({ ...result, isError: context.isError }, theme);\n\t\t\tif (!output) {\n\t\t\t\tconst component = (context.lastComponent as Container | undefined) ?? new Container();\n\t\t\t\tcomponent.clear();\n\t\t\t\treturn component;\n\t\t\t}\n\t\t\tconst text = (context.lastComponent as Text | undefined) ?? new Text(\"\", 0, 0);\n\t\t\ttext.setText(output);\n\t\t\treturn text;\n\t\t},\n\t};\n}\n\nexport function createWriteTool(cwd: string, options?: WriteToolOptions): AgentTool<typeof writeSchema> {\n\treturn wrapToolDefinition(createWriteToolDefinition(cwd, options));\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../../../src/extensions/deepwiki/render.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAAE,KAAK,SAAS,EAAqC,MAAM,wBAAwB,CAAC;AAC3F,OAAO,KAAK,EAAE,eAAe,EAAE,uBAAuB,EAAE,MAAM,gCAAgC,CAAC;AAE/F,OAAO,EAAoB,KAAK,KAAK,EAAE,MAAM,wCAAwC,CAAC;AAItF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AACpD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAoHlD,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,cAAc,EAAE,KAAK,EAAE,KAAK,GAAG,SAAS,CAUhF;AASD,wBAAgB,oBAAoB,CACnC,MAAM,EAAE,eAAe,CAAC,eAAe,CAAC,EACxC,OAAO,EAAE,uBAAuB,EAChC,KAAK,EAAE,KAAK,EACZ,OAAO,EAAE,OAAO,GACd,SAAS,CAqBX","sourcesContent":["/**\n * TUI rendering for the deepwiki tool (collapse / expand).\n *\n * Pi's fallback renderer dumps full `content` text and ignores `expanded`; this\n * extension returns large wiki payloads, so a private renderer stays local here.\n */\nimport { type Component, Container, Markdown, Spacer, Text } from \"@earendil-works/pi-tui\";\nimport type { AgentToolResult, ToolRenderResultOptions } from \"../../core/extensions/types.ts\";\nimport { keyHint } from \"../../modes/interactive/components/keybinding-hints.ts\";\nimport { getMarkdownTheme, type Theme } from \"../../modes/interactive/theme/theme.ts\";\n\nimport { extractContentPages, extractStructureSections } from \"./client.ts\";\nimport { DEEPWIKI_LABEL } from \"./constants.ts\";\nimport type { DeepWikiDetails } from \"./execute.ts\";\nimport type { DeepWikiParams } from \"./schema.ts\";\n\nfunction truncateText(text: string, maxLength: number, options?: { word?: boolean }): string {\n\tif (text.length <= maxLength) return text;\n\tif (options?.word) {\n\t\tconst prefix = text.slice(0, maxLength - 3);\n\t\tconst lastSpace = prefix.lastIndexOf(\" \");\n\t\tif (lastSpace >= 40) return `${prefix.slice(0, lastSpace)}...`;\n\t}\n\treturn `${text.slice(0, maxLength - 3)}...`;\n}\n\nfunction firstText(result: AgentToolResult<DeepWikiDetails>): string {\n\tfor (const part of result.content ?? []) {\n\t\tif (part.type === \"text\" && typeof part.text === \"string\") return part.text;\n\t}\n\treturn \"\";\n}\n\nfunction firstContentLine(text: string): string {\n\treturn (\n\t\ttext\n\t\t\t.split(\"\\n\")\n\t\t\t.map((line) => line.trim())\n\t\t\t.find((line) => line.length > 0) ?? \"DeepWiki response\"\n\t);\n}\n\nfunction formatPageList(pages: string[], maxItems = 4): string {\n\tconst shown = pages.slice(0, maxItems).join(\", \");\n\treturn pages.length > maxItems ? `${shown}...` : shown;\n}\n\nfunction pageTitlesFromResult(text: string, details: DeepWikiDetails | undefined): string[] {\n\tif (details?.pageTitles?.length) return details.pageTitles;\n\tif (details?.sectionTitles?.length) return details.sectionTitles;\n\tconst fromContent = extractContentPages(text);\n\tif (fromContent.length) return fromContent;\n\treturn extractStructureSections(text);\n}\n\nfunction summarizeStructure(text: string, details: DeepWikiDetails | undefined): string {\n\tconst pages = pageTitlesFromResult(text, details);\n\tif (pages.length === 0) return truncateText(firstContentLine(text), 120);\n\tconst count = details?.pageCount ?? pages.length;\n\treturn `${count} pages · ${formatPageList(pages)}`;\n}\n\nfunction summarizeContents(text: string, details: DeepWikiDetails | undefined): string {\n\tif (details?.requestedPage) {\n\t\tconst position =\n\t\t\tdetails.pageIndex !== undefined && details.pageCount ? `${details.pageIndex}/${details.pageCount} · ` : \"\";\n\t\tconst cut = details.truncatedChars ? \" (truncated)\" : \"\";\n\t\treturn `Page ${position}${details.requestedPage}${cut}`;\n\t}\n\tconst pages = pageTitlesFromResult(text, details);\n\tif (details?.shownPages !== undefined) {\n\t\tconst total = details.pageCount ?? pages.length;\n\t\tconst pagesPart = total > 0 ? `${details.shownPages}/${total} pages` : \"partial\";\n\t\treturn `Wiki · ${pagesPart} shown (truncated)`;\n\t}\n\tif (pages.length === 0) return \"Wiki loaded\";\n\treturn `Wiki · ${details?.pageCount ?? pages.length} pages`;\n}\n\nfunction stripDeepWikiTail(text: string): string {\n\tconst tailIndex = text.search(/\\n(?:#+\\s*)?(?:Wiki pages you might want to explore|View this search)/i);\n\tif (tailIndex < 0) return text;\n\treturn text.slice(0, tailIndex);\n}\n\nfunction summarizeQuestion(text: string): string {\n\tconst cleaned = stripDeepWikiTail(text)\n\t\t.replace(/\\[([^\\]]+)\\]\\([^)]+\\)/g, \"$1\")\n\t\t.replace(/[`*_]/g, \"\")\n\t\t.replace(/\\s+/g, \" \")\n\t\t.trim();\n\tif (!cleaned) return \"Answer ready\";\n\tconst sentenceMatch = cleaned.match(/^.{40,}?[.!?](?=\\s|$)/);\n\treturn truncateText(sentenceMatch?.[0] ?? cleaned, 140, { word: true });\n}\n\nfunction summarizeCollapsed(text: string, details: DeepWikiDetails | undefined): string {\n\tif (details?.action === \"structure\") return summarizeStructure(text, details);\n\tif (details?.action === \"contents\") return summarizeContents(text, details);\n\tif (details?.action === \"question\") return summarizeQuestion(text);\n\treturn truncateText(firstContentLine(text), 120);\n}\n\nfunction repoLabel(repoName: DeepWikiParams[\"repoName\"] | undefined): string {\n\tif (Array.isArray(repoName)) {\n\t\tif (repoName.length === 0) return \"repo\";\n\t\tif (repoName.length === 1) return repoName[0];\n\t\treturn `${repoName.length} repos: ${repoName.slice(0, 2).join(\", \")}${repoName.length > 2 ? \"...\" : \"\"}`;\n\t}\n\treturn repoName ?? \"repo\";\n}\n\n/** renderCall sees raw tool args; normalize JSON-array strings for display. */\nfunction repoLabelForCall(repoName: unknown): string {\n\tif (Array.isArray(repoName)) return repoLabel(repoName as DeepWikiParams[\"repoName\"]);\n\tif (typeof repoName === \"string\") {\n\t\tconst trimmed = repoName.trim();\n\t\tif (trimmed.startsWith(\"[\")) {\n\t\t\ttry {\n\t\t\t\tconst parsed: unknown = JSON.parse(trimmed);\n\t\t\t\tif (Array.isArray(parsed)) return repoLabel(parsed as string[]);\n\t\t\t} catch {\n\t\t\t\t/* fall through */\n\t\t\t}\n\t\t}\n\t\treturn repoLabel(repoName);\n\t}\n\treturn repoLabel(undefined);\n}\n\nexport function renderDeepWikiCall(args: DeepWikiParams, theme: Theme): Component {\n\tlet line = theme.fg(\"toolTitle\", theme.bold(`${DEEPWIKI_LABEL} `));\n\tline += theme.fg(\"muted\", args.action);\n\tline += ` ${theme.fg(\"accent\", repoLabelForCall(args.repoName))}`;\n\tif (args.action === \"question\" && args.question) {\n\t\tline += ` ${theme.fg(\"dim\", truncateText(args.question, 64))}`;\n\t} else if (args.action === \"contents\" && args.page !== undefined) {\n\t\tline += ` ${theme.fg(\"dim\", truncateText(String(args.page), 40))}`;\n\t}\n\treturn new Text(line, 0, 0);\n}\n\nfunction markdownBlock(text: string): Component {\n\tconst container = new Container();\n\tcontainer.addChild(new Spacer(1));\n\tcontainer.addChild(new Markdown(text, 1, 0, getMarkdownTheme()));\n\treturn container;\n}\n\nexport function renderDeepWikiResult(\n\tresult: AgentToolResult<DeepWikiDetails>,\n\toptions: ToolRenderResultOptions,\n\ttheme: Theme,\n\tisError: boolean,\n): Component {\n\tif (options.isPartial) {\n\t\tconst repo = result.details?.repoName;\n\t\tconst label = repo ? `Querying ${repo}...` : \"Querying...\";\n\t\treturn new Text(theme.fg(\"warning\", label), 0, 0);\n\t}\n\n\tconst text = firstText(result);\n\tif (isError || result.details?.errorMessage) {\n\t\tconst msg = result.details?.errorMessage ?? firstContentLine(text);\n\t\tconst line = truncateText(msg, 200);\n\t\treturn new Text(theme.fg(\"error\", `failed · ${line}`), 0, 0);\n\t}\n\n\tif (!options.expanded) {\n\t\tconst summary = theme.fg(\"accent\", summarizeCollapsed(text, result.details));\n\t\tconst hint = `${theme.fg(\"muted\", \"(\")}${keyHint(\"app.tools.expand\", \"to expand\")}${theme.fg(\"muted\", \")\")}`;\n\t\treturn new Text(`${summary}\\n${hint}`, 0, 0);\n\t}\n\n\treturn markdownBlock(text);\n}\n"]}
1
+ {"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../../../src/extensions/deepwiki/render.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAAE,KAAK,SAAS,EAAqC,MAAM,wBAAwB,CAAC;AAC3F,OAAO,KAAK,EAAE,eAAe,EAAE,uBAAuB,EAAE,MAAM,gCAAgC,CAAC;AAE/F,OAAO,EAAoB,KAAK,KAAK,EAAE,MAAM,wCAAwC,CAAC;AAItF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AACpD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAgIlD,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,cAAc,EAAE,KAAK,EAAE,KAAK,GAAG,SAAS,CAUhF;AASD,wBAAgB,oBAAoB,CACnC,MAAM,EAAE,eAAe,CAAC,eAAe,CAAC,EACxC,OAAO,EAAE,uBAAuB,EAChC,KAAK,EAAE,KAAK,EACZ,OAAO,EAAE,OAAO,GACd,SAAS,CAqBX","sourcesContent":["/**\n * TUI rendering for the deepwiki tool (collapse / expand).\n *\n * Pi's fallback renderer dumps full `content` text and ignores `expanded`; this\n * extension returns large wiki payloads, so a private renderer stays local here.\n */\nimport { type Component, Container, Markdown, Spacer, Text } from \"@earendil-works/pi-tui\";\nimport type { AgentToolResult, ToolRenderResultOptions } from \"../../core/extensions/types.ts\";\nimport { keyHint } from \"../../modes/interactive/components/keybinding-hints.ts\";\nimport { getMarkdownTheme, type Theme } from \"../../modes/interactive/theme/theme.ts\";\n\nimport { extractContentPages, extractStructureSections } from \"./client.ts\";\nimport { DEEPWIKI_LABEL } from \"./constants.ts\";\nimport type { DeepWikiDetails } from \"./execute.ts\";\nimport type { DeepWikiParams } from \"./schema.ts\";\n\nfunction truncateText(text: string, maxLength: number, options?: { word?: boolean }): string {\n\tif (text.length <= maxLength) return text;\n\tif (options?.word) {\n\t\tconst prefix = text.slice(0, maxLength - 3);\n\t\tconst lastSpace = prefix.lastIndexOf(\" \");\n\t\tif (lastSpace >= 40) return `${prefix.slice(0, lastSpace)}...`;\n\t}\n\treturn `${text.slice(0, maxLength - 3)}...`;\n}\n\nfunction singleLine(text: unknown): string {\n\treturn typeof text === \"string\" ? text.replace(/\\s+/g, \" \").trim() : \"\";\n}\n\nfunction firstText(result: AgentToolResult<DeepWikiDetails>): string {\n\tfor (const part of result.content ?? []) {\n\t\tif (part.type === \"text\" && typeof part.text === \"string\") return part.text;\n\t}\n\treturn \"\";\n}\n\nfunction firstContentLine(text: string): string {\n\treturn (\n\t\ttext\n\t\t\t.split(\"\\n\")\n\t\t\t.map((line) => line.trim())\n\t\t\t.find((line) => line.length > 0) ?? \"DeepWiki response\"\n\t);\n}\n\nfunction formatPageList(pages: string[], maxItems = 4): string {\n\tconst shown = pages.slice(0, maxItems).map(singleLine).join(\", \");\n\treturn pages.length > maxItems ? `${shown}...` : shown;\n}\n\nfunction pageTitlesFromResult(text: string, details: DeepWikiDetails | undefined): string[] {\n\tif (Array.isArray(details?.pageTitles)) {\n\t\tconst titles = details.pageTitles.filter((title) => typeof title === \"string\");\n\t\tif (titles.length) return titles;\n\t}\n\tif (Array.isArray(details?.sectionTitles)) {\n\t\tconst titles = details.sectionTitles.filter((title) => typeof title === \"string\");\n\t\tif (titles.length) return titles;\n\t}\n\tconst fromContent = extractContentPages(text);\n\tif (fromContent.length) return fromContent;\n\treturn extractStructureSections(text);\n}\n\nfunction summarizeStructure(text: string, details: DeepWikiDetails | undefined): string {\n\tconst pages = pageTitlesFromResult(text, details);\n\tif (pages.length === 0) return truncateText(firstContentLine(text), 120);\n\tconst count = details?.pageCount ?? pages.length;\n\treturn `${count} pages · ${formatPageList(pages)}`;\n}\n\nfunction summarizeContents(text: string, details: DeepWikiDetails | undefined): string {\n\tif (typeof details?.requestedPage === \"string\" && details.requestedPage.trim()) {\n\t\tconst position =\n\t\t\tdetails.pageIndex !== undefined && details.pageCount ? `${details.pageIndex}/${details.pageCount} · ` : \"\";\n\t\tconst cut = details.truncatedChars ? \" (truncated)\" : \"\";\n\t\treturn `Page ${position}${singleLine(details.requestedPage)}${cut}`;\n\t}\n\tconst pages = pageTitlesFromResult(text, details);\n\tif (details?.shownPages !== undefined) {\n\t\tconst total = details.pageCount ?? pages.length;\n\t\tconst pagesPart = total > 0 ? `${details.shownPages}/${total} pages` : \"partial\";\n\t\treturn `Wiki · ${pagesPart} shown (truncated)`;\n\t}\n\tif (pages.length === 0) return \"Wiki loaded\";\n\treturn `Wiki · ${details?.pageCount ?? pages.length} pages`;\n}\n\nfunction stripDeepWikiTail(text: string): string {\n\tconst tailIndex = text.search(/\\n(?:#+\\s*)?(?:Wiki pages you might want to explore|View this search)/i);\n\tif (tailIndex < 0) return text;\n\treturn text.slice(0, tailIndex);\n}\n\nfunction summarizeQuestion(text: string): string {\n\tconst cleaned = stripDeepWikiTail(text)\n\t\t.replace(/\\[([^\\]]+)\\]\\([^)]+\\)/g, \"$1\")\n\t\t.replace(/[`*_]/g, \"\")\n\t\t.replace(/\\s+/g, \" \")\n\t\t.trim();\n\tif (!cleaned) return \"Answer ready\";\n\tconst sentenceMatch = cleaned.match(/^.{40,}?[.!?](?=\\s|$)/);\n\treturn truncateText(sentenceMatch?.[0] ?? cleaned, 140, { word: true });\n}\n\nfunction summarizeCollapsed(text: string, details: DeepWikiDetails | undefined): string {\n\tlet summary: string;\n\tif (details?.action === \"structure\") summary = summarizeStructure(text, details);\n\telse if (details?.action === \"contents\") summary = summarizeContents(text, details);\n\telse if (details?.action === \"question\") summary = summarizeQuestion(text);\n\telse summary = truncateText(firstContentLine(text), 120);\n\treturn truncateText(singleLine(summary), 180, { word: true });\n}\n\nfunction repoLabel(repoName: DeepWikiParams[\"repoName\"] | undefined): string {\n\tif (Array.isArray(repoName)) {\n\t\tif (repoName.length === 0) return \"repo\";\n\t\tif (repoName.length === 1) return repoName[0];\n\t\treturn `${repoName.length} repos: ${repoName.slice(0, 2).join(\", \")}${repoName.length > 2 ? \"...\" : \"\"}`;\n\t}\n\treturn repoName ?? \"repo\";\n}\n\n/** renderCall sees raw tool args; normalize JSON-array strings for display. */\nfunction repoLabelForCall(repoName: unknown): string {\n\tif (Array.isArray(repoName)) return repoLabel(repoName as DeepWikiParams[\"repoName\"]);\n\tif (typeof repoName === \"string\") {\n\t\tconst trimmed = repoName.trim();\n\t\tif (trimmed.startsWith(\"[\")) {\n\t\t\ttry {\n\t\t\t\tconst parsed: unknown = JSON.parse(trimmed);\n\t\t\t\tif (Array.isArray(parsed)) return repoLabel(parsed as string[]);\n\t\t\t} catch {\n\t\t\t\t/* fall through */\n\t\t\t}\n\t\t}\n\t\treturn repoLabel(repoName);\n\t}\n\treturn repoLabel(undefined);\n}\n\nexport function renderDeepWikiCall(args: DeepWikiParams, theme: Theme): Component {\n\tlet line = theme.fg(\"toolTitle\", theme.bold(`${DEEPWIKI_LABEL} `));\n\tline += theme.fg(\"muted\", args.action);\n\tline += ` ${theme.fg(\"accent\", repoLabelForCall(args.repoName))}`;\n\tif (args.action === \"question\" && args.question) {\n\t\tline += ` ${theme.fg(\"dim\", truncateText(singleLine(args.question), 64))}`;\n\t} else if (args.action === \"contents\" && args.page !== undefined) {\n\t\tline += ` ${theme.fg(\"dim\", truncateText(singleLine(String(args.page)), 40))}`;\n\t}\n\treturn new Text(line, 0, 0);\n}\n\nfunction markdownBlock(text: string): Component {\n\tconst container = new Container();\n\tcontainer.addChild(new Spacer(1));\n\tcontainer.addChild(new Markdown(text, 1, 0, getMarkdownTheme()));\n\treturn container;\n}\n\nexport function renderDeepWikiResult(\n\tresult: AgentToolResult<DeepWikiDetails>,\n\toptions: ToolRenderResultOptions,\n\ttheme: Theme,\n\tisError: boolean,\n): Component {\n\tif (options.isPartial) {\n\t\tconst repo = typeof result.details?.repoName === \"string\" ? singleLine(result.details.repoName) : \"\";\n\t\tconst label = repo ? `Querying ${repo}...` : \"Querying...\";\n\t\treturn new Text(theme.fg(\"warning\", label), 0, 0);\n\t}\n\n\tconst text = firstText(result);\n\tconst errorMessage = typeof result.details?.errorMessage === \"string\" ? result.details.errorMessage : undefined;\n\tif (isError || errorMessage) {\n\t\tconst line = truncateText(errorMessage ?? firstContentLine(text), 200);\n\t\treturn new Text(theme.fg(\"error\", `failed · ${line}`), 0, 0);\n\t}\n\n\tif (!options.expanded) {\n\t\tconst summary = theme.fg(\"toolOutput\", summarizeCollapsed(text, result.details));\n\t\tconst hint = `${theme.fg(\"muted\", \" (\")}${keyHint(\"app.tools.expand\", \"to expand\")}${theme.fg(\"muted\", \")\")}`;\n\t\treturn new Text(`${summary}${hint}`, 0, 0);\n\t}\n\n\treturn markdownBlock(text);\n}\n"]}
@@ -20,6 +20,9 @@ function truncateText(text, maxLength, options) {
20
20
  }
21
21
  return `${text.slice(0, maxLength - 3)}...`;
22
22
  }
23
+ function singleLine(text) {
24
+ return typeof text === "string" ? text.replace(/\s+/g, " ").trim() : "";
25
+ }
23
26
  function firstText(result) {
24
27
  for (const part of result.content ?? []) {
25
28
  if (part.type === "text" && typeof part.text === "string")
@@ -34,14 +37,20 @@ function firstContentLine(text) {
34
37
  .find((line) => line.length > 0) ?? "DeepWiki response");
35
38
  }
36
39
  function formatPageList(pages, maxItems = 4) {
37
- const shown = pages.slice(0, maxItems).join(", ");
40
+ const shown = pages.slice(0, maxItems).map(singleLine).join(", ");
38
41
  return pages.length > maxItems ? `${shown}...` : shown;
39
42
  }
40
43
  function pageTitlesFromResult(text, details) {
41
- if (details?.pageTitles?.length)
42
- return details.pageTitles;
43
- if (details?.sectionTitles?.length)
44
- return details.sectionTitles;
44
+ if (Array.isArray(details?.pageTitles)) {
45
+ const titles = details.pageTitles.filter((title) => typeof title === "string");
46
+ if (titles.length)
47
+ return titles;
48
+ }
49
+ if (Array.isArray(details?.sectionTitles)) {
50
+ const titles = details.sectionTitles.filter((title) => typeof title === "string");
51
+ if (titles.length)
52
+ return titles;
53
+ }
45
54
  const fromContent = extractContentPages(text);
46
55
  if (fromContent.length)
47
56
  return fromContent;
@@ -55,10 +64,10 @@ function summarizeStructure(text, details) {
55
64
  return `${count} pages · ${formatPageList(pages)}`;
56
65
  }
57
66
  function summarizeContents(text, details) {
58
- if (details?.requestedPage) {
67
+ if (typeof details?.requestedPage === "string" && details.requestedPage.trim()) {
59
68
  const position = details.pageIndex !== undefined && details.pageCount ? `${details.pageIndex}/${details.pageCount} · ` : "";
60
69
  const cut = details.truncatedChars ? " (truncated)" : "";
61
- return `Page ${position}${details.requestedPage}${cut}`;
70
+ return `Page ${position}${singleLine(details.requestedPage)}${cut}`;
62
71
  }
63
72
  const pages = pageTitlesFromResult(text, details);
64
73
  if (details?.shownPages !== undefined) {
@@ -88,13 +97,16 @@ function summarizeQuestion(text) {
88
97
  return truncateText(sentenceMatch?.[0] ?? cleaned, 140, { word: true });
89
98
  }
90
99
  function summarizeCollapsed(text, details) {
100
+ let summary;
91
101
  if (details?.action === "structure")
92
- return summarizeStructure(text, details);
93
- if (details?.action === "contents")
94
- return summarizeContents(text, details);
95
- if (details?.action === "question")
96
- return summarizeQuestion(text);
97
- return truncateText(firstContentLine(text), 120);
102
+ summary = summarizeStructure(text, details);
103
+ else if (details?.action === "contents")
104
+ summary = summarizeContents(text, details);
105
+ else if (details?.action === "question")
106
+ summary = summarizeQuestion(text);
107
+ else
108
+ summary = truncateText(firstContentLine(text), 120);
109
+ return truncateText(singleLine(summary), 180, { word: true });
98
110
  }
99
111
  function repoLabel(repoName) {
100
112
  if (Array.isArray(repoName)) {
@@ -131,10 +143,10 @@ export function renderDeepWikiCall(args, theme) {
131
143
  line += theme.fg("muted", args.action);
132
144
  line += ` ${theme.fg("accent", repoLabelForCall(args.repoName))}`;
133
145
  if (args.action === "question" && args.question) {
134
- line += ` ${theme.fg("dim", truncateText(args.question, 64))}`;
146
+ line += ` ${theme.fg("dim", truncateText(singleLine(args.question), 64))}`;
135
147
  }
136
148
  else if (args.action === "contents" && args.page !== undefined) {
137
- line += ` ${theme.fg("dim", truncateText(String(args.page), 40))}`;
149
+ line += ` ${theme.fg("dim", truncateText(singleLine(String(args.page)), 40))}`;
138
150
  }
139
151
  return new Text(line, 0, 0);
140
152
  }
@@ -146,20 +158,20 @@ function markdownBlock(text) {
146
158
  }
147
159
  export function renderDeepWikiResult(result, options, theme, isError) {
148
160
  if (options.isPartial) {
149
- const repo = result.details?.repoName;
161
+ const repo = typeof result.details?.repoName === "string" ? singleLine(result.details.repoName) : "";
150
162
  const label = repo ? `Querying ${repo}...` : "Querying...";
151
163
  return new Text(theme.fg("warning", label), 0, 0);
152
164
  }
153
165
  const text = firstText(result);
154
- if (isError || result.details?.errorMessage) {
155
- const msg = result.details?.errorMessage ?? firstContentLine(text);
156
- const line = truncateText(msg, 200);
166
+ const errorMessage = typeof result.details?.errorMessage === "string" ? result.details.errorMessage : undefined;
167
+ if (isError || errorMessage) {
168
+ const line = truncateText(errorMessage ?? firstContentLine(text), 200);
157
169
  return new Text(theme.fg("error", `failed · ${line}`), 0, 0);
158
170
  }
159
171
  if (!options.expanded) {
160
- const summary = theme.fg("accent", summarizeCollapsed(text, result.details));
161
- const hint = `${theme.fg("muted", "(")}${keyHint("app.tools.expand", "to expand")}${theme.fg("muted", ")")}`;
162
- return new Text(`${summary}\n${hint}`, 0, 0);
172
+ const summary = theme.fg("toolOutput", summarizeCollapsed(text, result.details));
173
+ const hint = `${theme.fg("muted", " (")}${keyHint("app.tools.expand", "to expand")}${theme.fg("muted", ")")}`;
174
+ return new Text(`${summary}${hint}`, 0, 0);
163
175
  }
164
176
  return markdownBlock(text);
165
177
  }
@@ -1 +1 @@
1
- {"version":3,"file":"render.js","sourceRoot":"","sources":["../../../src/extensions/deepwiki/render.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * TUI rendering for the deepwiki tool (collapse / expand).\n *\n * Pi's fallback renderer dumps full `content` text and ignores `expanded`; this\n * extension returns large wiki payloads, so a private renderer stays local here.\n */\nimport { type Component, Container, Markdown, Spacer, Text } from \"@earendil-works/pi-tui\";\nimport type { AgentToolResult, ToolRenderResultOptions } from \"../../core/extensions/types.ts\";\nimport { keyHint } from \"../../modes/interactive/components/keybinding-hints.ts\";\nimport { getMarkdownTheme, type Theme } from \"../../modes/interactive/theme/theme.ts\";\n\nimport { extractContentPages, extractStructureSections } from \"./client.ts\";\nimport { DEEPWIKI_LABEL } from \"./constants.ts\";\nimport type { DeepWikiDetails } from \"./execute.ts\";\nimport type { DeepWikiParams } from \"./schema.ts\";\n\nfunction truncateText(text: string, maxLength: number, options?: { word?: boolean }): string {\n\tif (text.length <= maxLength) return text;\n\tif (options?.word) {\n\t\tconst prefix = text.slice(0, maxLength - 3);\n\t\tconst lastSpace = prefix.lastIndexOf(\" \");\n\t\tif (lastSpace >= 40) return `${prefix.slice(0, lastSpace)}...`;\n\t}\n\treturn `${text.slice(0, maxLength - 3)}...`;\n}\n\nfunction firstText(result: AgentToolResult<DeepWikiDetails>): string {\n\tfor (const part of result.content ?? []) {\n\t\tif (part.type === \"text\" && typeof part.text === \"string\") return part.text;\n\t}\n\treturn \"\";\n}\n\nfunction firstContentLine(text: string): string {\n\treturn (\n\t\ttext\n\t\t\t.split(\"\\n\")\n\t\t\t.map((line) => line.trim())\n\t\t\t.find((line) => line.length > 0) ?? \"DeepWiki response\"\n\t);\n}\n\nfunction formatPageList(pages: string[], maxItems = 4): string {\n\tconst shown = pages.slice(0, maxItems).join(\", \");\n\treturn pages.length > maxItems ? `${shown}...` : shown;\n}\n\nfunction pageTitlesFromResult(text: string, details: DeepWikiDetails | undefined): string[] {\n\tif (details?.pageTitles?.length) return details.pageTitles;\n\tif (details?.sectionTitles?.length) return details.sectionTitles;\n\tconst fromContent = extractContentPages(text);\n\tif (fromContent.length) return fromContent;\n\treturn extractStructureSections(text);\n}\n\nfunction summarizeStructure(text: string, details: DeepWikiDetails | undefined): string {\n\tconst pages = pageTitlesFromResult(text, details);\n\tif (pages.length === 0) return truncateText(firstContentLine(text), 120);\n\tconst count = details?.pageCount ?? pages.length;\n\treturn `${count} pages · ${formatPageList(pages)}`;\n}\n\nfunction summarizeContents(text: string, details: DeepWikiDetails | undefined): string {\n\tif (details?.requestedPage) {\n\t\tconst position =\n\t\t\tdetails.pageIndex !== undefined && details.pageCount ? `${details.pageIndex}/${details.pageCount} · ` : \"\";\n\t\tconst cut = details.truncatedChars ? \" (truncated)\" : \"\";\n\t\treturn `Page ${position}${details.requestedPage}${cut}`;\n\t}\n\tconst pages = pageTitlesFromResult(text, details);\n\tif (details?.shownPages !== undefined) {\n\t\tconst total = details.pageCount ?? pages.length;\n\t\tconst pagesPart = total > 0 ? `${details.shownPages}/${total} pages` : \"partial\";\n\t\treturn `Wiki · ${pagesPart} shown (truncated)`;\n\t}\n\tif (pages.length === 0) return \"Wiki loaded\";\n\treturn `Wiki · ${details?.pageCount ?? pages.length} pages`;\n}\n\nfunction stripDeepWikiTail(text: string): string {\n\tconst tailIndex = text.search(/\\n(?:#+\\s*)?(?:Wiki pages you might want to explore|View this search)/i);\n\tif (tailIndex < 0) return text;\n\treturn text.slice(0, tailIndex);\n}\n\nfunction summarizeQuestion(text: string): string {\n\tconst cleaned = stripDeepWikiTail(text)\n\t\t.replace(/\\[([^\\]]+)\\]\\([^)]+\\)/g, \"$1\")\n\t\t.replace(/[`*_]/g, \"\")\n\t\t.replace(/\\s+/g, \" \")\n\t\t.trim();\n\tif (!cleaned) return \"Answer ready\";\n\tconst sentenceMatch = cleaned.match(/^.{40,}?[.!?](?=\\s|$)/);\n\treturn truncateText(sentenceMatch?.[0] ?? cleaned, 140, { word: true });\n}\n\nfunction summarizeCollapsed(text: string, details: DeepWikiDetails | undefined): string {\n\tif (details?.action === \"structure\") return summarizeStructure(text, details);\n\tif (details?.action === \"contents\") return summarizeContents(text, details);\n\tif (details?.action === \"question\") return summarizeQuestion(text);\n\treturn truncateText(firstContentLine(text), 120);\n}\n\nfunction repoLabel(repoName: DeepWikiParams[\"repoName\"] | undefined): string {\n\tif (Array.isArray(repoName)) {\n\t\tif (repoName.length === 0) return \"repo\";\n\t\tif (repoName.length === 1) return repoName[0];\n\t\treturn `${repoName.length} repos: ${repoName.slice(0, 2).join(\", \")}${repoName.length > 2 ? \"...\" : \"\"}`;\n\t}\n\treturn repoName ?? \"repo\";\n}\n\n/** renderCall sees raw tool args; normalize JSON-array strings for display. */\nfunction repoLabelForCall(repoName: unknown): string {\n\tif (Array.isArray(repoName)) return repoLabel(repoName as DeepWikiParams[\"repoName\"]);\n\tif (typeof repoName === \"string\") {\n\t\tconst trimmed = repoName.trim();\n\t\tif (trimmed.startsWith(\"[\")) {\n\t\t\ttry {\n\t\t\t\tconst parsed: unknown = JSON.parse(trimmed);\n\t\t\t\tif (Array.isArray(parsed)) return repoLabel(parsed as string[]);\n\t\t\t} catch {\n\t\t\t\t/* fall through */\n\t\t\t}\n\t\t}\n\t\treturn repoLabel(repoName);\n\t}\n\treturn repoLabel(undefined);\n}\n\nexport function renderDeepWikiCall(args: DeepWikiParams, theme: Theme): Component {\n\tlet line = theme.fg(\"toolTitle\", theme.bold(`${DEEPWIKI_LABEL} `));\n\tline += theme.fg(\"muted\", args.action);\n\tline += ` ${theme.fg(\"accent\", repoLabelForCall(args.repoName))}`;\n\tif (args.action === \"question\" && args.question) {\n\t\tline += ` ${theme.fg(\"dim\", truncateText(args.question, 64))}`;\n\t} else if (args.action === \"contents\" && args.page !== undefined) {\n\t\tline += ` ${theme.fg(\"dim\", truncateText(String(args.page), 40))}`;\n\t}\n\treturn new Text(line, 0, 0);\n}\n\nfunction markdownBlock(text: string): Component {\n\tconst container = new Container();\n\tcontainer.addChild(new Spacer(1));\n\tcontainer.addChild(new Markdown(text, 1, 0, getMarkdownTheme()));\n\treturn container;\n}\n\nexport function renderDeepWikiResult(\n\tresult: AgentToolResult<DeepWikiDetails>,\n\toptions: ToolRenderResultOptions,\n\ttheme: Theme,\n\tisError: boolean,\n): Component {\n\tif (options.isPartial) {\n\t\tconst repo = result.details?.repoName;\n\t\tconst label = repo ? `Querying ${repo}...` : \"Querying...\";\n\t\treturn new Text(theme.fg(\"warning\", label), 0, 0);\n\t}\n\n\tconst text = firstText(result);\n\tif (isError || result.details?.errorMessage) {\n\t\tconst msg = result.details?.errorMessage ?? firstContentLine(text);\n\t\tconst line = truncateText(msg, 200);\n\t\treturn new Text(theme.fg(\"error\", `failed · ${line}`), 0, 0);\n\t}\n\n\tif (!options.expanded) {\n\t\tconst summary = theme.fg(\"accent\", summarizeCollapsed(text, result.details));\n\t\tconst hint = `${theme.fg(\"muted\", \"(\")}${keyHint(\"app.tools.expand\", \"to expand\")}${theme.fg(\"muted\", \")\")}`;\n\t\treturn new Text(`${summary}\\n${hint}`, 0, 0);\n\t}\n\n\treturn markdownBlock(text);\n}\n"]}
1
+ {"version":3,"file":"render.js","sourceRoot":"","sources":["../../../src/extensions/deepwiki/render.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * TUI rendering for the deepwiki tool (collapse / expand).\n *\n * Pi's fallback renderer dumps full `content` text and ignores `expanded`; this\n * extension returns large wiki payloads, so a private renderer stays local here.\n */\nimport { type Component, Container, Markdown, Spacer, Text } from \"@earendil-works/pi-tui\";\nimport type { AgentToolResult, ToolRenderResultOptions } from \"../../core/extensions/types.ts\";\nimport { keyHint } from \"../../modes/interactive/components/keybinding-hints.ts\";\nimport { getMarkdownTheme, type Theme } from \"../../modes/interactive/theme/theme.ts\";\n\nimport { extractContentPages, extractStructureSections } from \"./client.ts\";\nimport { DEEPWIKI_LABEL } from \"./constants.ts\";\nimport type { DeepWikiDetails } from \"./execute.ts\";\nimport type { DeepWikiParams } from \"./schema.ts\";\n\nfunction truncateText(text: string, maxLength: number, options?: { word?: boolean }): string {\n\tif (text.length <= maxLength) return text;\n\tif (options?.word) {\n\t\tconst prefix = text.slice(0, maxLength - 3);\n\t\tconst lastSpace = prefix.lastIndexOf(\" \");\n\t\tif (lastSpace >= 40) return `${prefix.slice(0, lastSpace)}...`;\n\t}\n\treturn `${text.slice(0, maxLength - 3)}...`;\n}\n\nfunction singleLine(text: unknown): string {\n\treturn typeof text === \"string\" ? text.replace(/\\s+/g, \" \").trim() : \"\";\n}\n\nfunction firstText(result: AgentToolResult<DeepWikiDetails>): string {\n\tfor (const part of result.content ?? []) {\n\t\tif (part.type === \"text\" && typeof part.text === \"string\") return part.text;\n\t}\n\treturn \"\";\n}\n\nfunction firstContentLine(text: string): string {\n\treturn (\n\t\ttext\n\t\t\t.split(\"\\n\")\n\t\t\t.map((line) => line.trim())\n\t\t\t.find((line) => line.length > 0) ?? \"DeepWiki response\"\n\t);\n}\n\nfunction formatPageList(pages: string[], maxItems = 4): string {\n\tconst shown = pages.slice(0, maxItems).map(singleLine).join(\", \");\n\treturn pages.length > maxItems ? `${shown}...` : shown;\n}\n\nfunction pageTitlesFromResult(text: string, details: DeepWikiDetails | undefined): string[] {\n\tif (Array.isArray(details?.pageTitles)) {\n\t\tconst titles = details.pageTitles.filter((title) => typeof title === \"string\");\n\t\tif (titles.length) return titles;\n\t}\n\tif (Array.isArray(details?.sectionTitles)) {\n\t\tconst titles = details.sectionTitles.filter((title) => typeof title === \"string\");\n\t\tif (titles.length) return titles;\n\t}\n\tconst fromContent = extractContentPages(text);\n\tif (fromContent.length) return fromContent;\n\treturn extractStructureSections(text);\n}\n\nfunction summarizeStructure(text: string, details: DeepWikiDetails | undefined): string {\n\tconst pages = pageTitlesFromResult(text, details);\n\tif (pages.length === 0) return truncateText(firstContentLine(text), 120);\n\tconst count = details?.pageCount ?? pages.length;\n\treturn `${count} pages · ${formatPageList(pages)}`;\n}\n\nfunction summarizeContents(text: string, details: DeepWikiDetails | undefined): string {\n\tif (typeof details?.requestedPage === \"string\" && details.requestedPage.trim()) {\n\t\tconst position =\n\t\t\tdetails.pageIndex !== undefined && details.pageCount ? `${details.pageIndex}/${details.pageCount} · ` : \"\";\n\t\tconst cut = details.truncatedChars ? \" (truncated)\" : \"\";\n\t\treturn `Page ${position}${singleLine(details.requestedPage)}${cut}`;\n\t}\n\tconst pages = pageTitlesFromResult(text, details);\n\tif (details?.shownPages !== undefined) {\n\t\tconst total = details.pageCount ?? pages.length;\n\t\tconst pagesPart = total > 0 ? `${details.shownPages}/${total} pages` : \"partial\";\n\t\treturn `Wiki · ${pagesPart} shown (truncated)`;\n\t}\n\tif (pages.length === 0) return \"Wiki loaded\";\n\treturn `Wiki · ${details?.pageCount ?? pages.length} pages`;\n}\n\nfunction stripDeepWikiTail(text: string): string {\n\tconst tailIndex = text.search(/\\n(?:#+\\s*)?(?:Wiki pages you might want to explore|View this search)/i);\n\tif (tailIndex < 0) return text;\n\treturn text.slice(0, tailIndex);\n}\n\nfunction summarizeQuestion(text: string): string {\n\tconst cleaned = stripDeepWikiTail(text)\n\t\t.replace(/\\[([^\\]]+)\\]\\([^)]+\\)/g, \"$1\")\n\t\t.replace(/[`*_]/g, \"\")\n\t\t.replace(/\\s+/g, \" \")\n\t\t.trim();\n\tif (!cleaned) return \"Answer ready\";\n\tconst sentenceMatch = cleaned.match(/^.{40,}?[.!?](?=\\s|$)/);\n\treturn truncateText(sentenceMatch?.[0] ?? cleaned, 140, { word: true });\n}\n\nfunction summarizeCollapsed(text: string, details: DeepWikiDetails | undefined): string {\n\tlet summary: string;\n\tif (details?.action === \"structure\") summary = summarizeStructure(text, details);\n\telse if (details?.action === \"contents\") summary = summarizeContents(text, details);\n\telse if (details?.action === \"question\") summary = summarizeQuestion(text);\n\telse summary = truncateText(firstContentLine(text), 120);\n\treturn truncateText(singleLine(summary), 180, { word: true });\n}\n\nfunction repoLabel(repoName: DeepWikiParams[\"repoName\"] | undefined): string {\n\tif (Array.isArray(repoName)) {\n\t\tif (repoName.length === 0) return \"repo\";\n\t\tif (repoName.length === 1) return repoName[0];\n\t\treturn `${repoName.length} repos: ${repoName.slice(0, 2).join(\", \")}${repoName.length > 2 ? \"...\" : \"\"}`;\n\t}\n\treturn repoName ?? \"repo\";\n}\n\n/** renderCall sees raw tool args; normalize JSON-array strings for display. */\nfunction repoLabelForCall(repoName: unknown): string {\n\tif (Array.isArray(repoName)) return repoLabel(repoName as DeepWikiParams[\"repoName\"]);\n\tif (typeof repoName === \"string\") {\n\t\tconst trimmed = repoName.trim();\n\t\tif (trimmed.startsWith(\"[\")) {\n\t\t\ttry {\n\t\t\t\tconst parsed: unknown = JSON.parse(trimmed);\n\t\t\t\tif (Array.isArray(parsed)) return repoLabel(parsed as string[]);\n\t\t\t} catch {\n\t\t\t\t/* fall through */\n\t\t\t}\n\t\t}\n\t\treturn repoLabel(repoName);\n\t}\n\treturn repoLabel(undefined);\n}\n\nexport function renderDeepWikiCall(args: DeepWikiParams, theme: Theme): Component {\n\tlet line = theme.fg(\"toolTitle\", theme.bold(`${DEEPWIKI_LABEL} `));\n\tline += theme.fg(\"muted\", args.action);\n\tline += ` ${theme.fg(\"accent\", repoLabelForCall(args.repoName))}`;\n\tif (args.action === \"question\" && args.question) {\n\t\tline += ` ${theme.fg(\"dim\", truncateText(singleLine(args.question), 64))}`;\n\t} else if (args.action === \"contents\" && args.page !== undefined) {\n\t\tline += ` ${theme.fg(\"dim\", truncateText(singleLine(String(args.page)), 40))}`;\n\t}\n\treturn new Text(line, 0, 0);\n}\n\nfunction markdownBlock(text: string): Component {\n\tconst container = new Container();\n\tcontainer.addChild(new Spacer(1));\n\tcontainer.addChild(new Markdown(text, 1, 0, getMarkdownTheme()));\n\treturn container;\n}\n\nexport function renderDeepWikiResult(\n\tresult: AgentToolResult<DeepWikiDetails>,\n\toptions: ToolRenderResultOptions,\n\ttheme: Theme,\n\tisError: boolean,\n): Component {\n\tif (options.isPartial) {\n\t\tconst repo = typeof result.details?.repoName === \"string\" ? singleLine(result.details.repoName) : \"\";\n\t\tconst label = repo ? `Querying ${repo}...` : \"Querying...\";\n\t\treturn new Text(theme.fg(\"warning\", label), 0, 0);\n\t}\n\n\tconst text = firstText(result);\n\tconst errorMessage = typeof result.details?.errorMessage === \"string\" ? result.details.errorMessage : undefined;\n\tif (isError || errorMessage) {\n\t\tconst line = truncateText(errorMessage ?? firstContentLine(text), 200);\n\t\treturn new Text(theme.fg(\"error\", `failed · ${line}`), 0, 0);\n\t}\n\n\tif (!options.expanded) {\n\t\tconst summary = theme.fg(\"toolOutput\", summarizeCollapsed(text, result.details));\n\t\tconst hint = `${theme.fg(\"muted\", \" (\")}${keyHint(\"app.tools.expand\", \"to expand\")}${theme.fg(\"muted\", \")\")}`;\n\t\treturn new Text(`${summary}${hint}`, 0, 0);\n\t}\n\n\treturn markdownBlock(text);\n}\n"]}
@@ -13,12 +13,31 @@ export declare const PLAN_FLAG_NAME = "plan";
13
13
  export declare const PLAN_ENTRY_TYPE = "plan-mode";
14
14
  export declare const PLAN_STATUS_KEY = "plan-mode";
15
15
  /**
16
- * Tools removed while planning. edit/write/bash grant write access directly;
17
- * subagent spawns child sessions that do not inherit the restricted tool set,
18
- * so leaving it active would let the model write through a child agent.
19
- * Other extension tools (question, todo, deepwiki, ...) stay available.
16
+ * customType of the post-compaction kickoff message. Sent via sendMessage so
17
+ * the LLM receives the full plan while the TUI renders a collapsed card.
18
+ */
19
+ export declare const PLAN_KICKOFF_MESSAGE_TYPE = "plan-kickoff";
20
+ /**
21
+ * Tools blocked while planning: edit/write grant write access directly.
22
+ * bash stays available and is constrained to read-only inspection via the
23
+ * plan-mode system prompt (same contract as the explorer subagent), and
24
+ * subagent stays available but is limited to the built-in explorer profile
25
+ * by the tool_call guard. Other extension tools (question, todo, deepwiki,
26
+ * ...) stay available.
20
27
  */
21
28
  export declare const PLAN_BLOCKED_TOOLS: ReadonlySet<string>;
29
+ /**
30
+ * Exploration tools activated on entry. Mirrors the explorer subagent's
31
+ * EXPLORER_TOOLS (kept as a separate constant so plan does not depend on the
32
+ * subagent extension): the standalone search tools plus bash for read-only
33
+ * inspection. grep/find/ls are registered but not active in the default
34
+ * session tool set, so plan mode must activate them explicitly.
35
+ */
36
+ export declare const PLAN_EXPLORE_TOOLS: readonly string[];
37
+ /** Only subagent profile allowed while planning (read-only exploration). */
38
+ export declare const PLAN_ALLOWED_SUBAGENT = "explorer";
39
+ /** Name of the subagent extension tool, for the plan-mode tool_call guard. */
40
+ export declare const PLAN_SUBAGENT_TOOL_NAME = "subagent";
22
41
  /** Max characters of plan markdown echoed into the post-compaction kickoff message. */
23
42
  export declare const PLAN_EMBED_MAX_CHARS = 24000;
24
43
  export declare const MENU_EXECUTE = "Start executing";
@@ -32,6 +51,15 @@ export declare const MENU_KEEP_PLANNING = "Keep planning";
32
51
  export declare const EXIT_PLAN_MENU_OPTIONS: readonly SelectOption[];
33
52
  /** Esc maps to "keep planning", so the dismiss hint must not read as "discard". */
34
53
  export declare const EXIT_PLAN_CANCEL_HINT = "keep planning";
54
+ /**
55
+ * /plan panel (shown when the command runs while planning): exit is the first
56
+ * option, saved plans follow. Esc keeps planning with no side effects.
57
+ */
58
+ export declare const PLAN_PANEL_TITLE = "Plan mode";
59
+ export declare const PLAN_PANEL_EXIT = "Exit plan mode";
60
+ export declare const PLAN_PANEL_EXIT_DESCRIPTION = "restore full tool access";
61
+ export declare const PLAN_PANEL_CANCEL_HINT = "keep planning";
62
+ export declare const PLAN_PANEL_MAX_FILES = 20;
35
63
  export declare const EXIT_PLAN_TOOL_DESCRIPTION = "Submit the finished plan and ask the user to approve leaving plan mode. Present the complete plan as normal response text first, then call this tool with a short title and the same plan markdown; on approval the plan is saved to disk. The user chooses between executing with full context, compacting the context before executing, or continuing to plan. Only available while plan mode is active.";
36
64
  export declare const EXIT_PLAN_PROMPT_SNIPPET = "Submit the finished plan for user approval and leave plan mode";
37
65
  export declare const EXIT_PLAN_PROMPT_GUIDELINES: string[];
@@ -1 +1 @@
1
- {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../../src/extensions/plan/constants.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gCAAgC,CAAC;AAEnE,eAAO,MAAM,mBAAmB,cAAc,CAAC;AAC/C,eAAO,MAAM,oBAAoB,cAAc,CAAC;AAChD,eAAO,MAAM,iBAAiB,SAAS,CAAC;AACxC,eAAO,MAAM,cAAc,SAAS,CAAC;AACrC,eAAO,MAAM,eAAe,cAAc,CAAC;AAC3C,eAAO,MAAM,eAAe,cAAc,CAAC;AAE3C;;;;;GAKG;AACH,eAAO,MAAM,kBAAkB,EAAE,WAAW,CAAC,MAAM,CAAkD,CAAC;AAEtG,uFAAuF;AACvF,eAAO,MAAM,oBAAoB,QAAS,CAAC;AAE3C,eAAO,MAAM,YAAY,oBAAoB,CAAC;AAC9C,eAAO,MAAM,oBAAoB,kCAAkC,CAAC;AACpE,eAAO,MAAM,kBAAkB,kBAAkB,CAAC;AAElD;;;;GAIG;AACH,eAAO,MAAM,sBAAsB,EAAE,SAAS,YAAY,EAIzD,CAAC;AAEF,mFAAmF;AACnF,eAAO,MAAM,qBAAqB,kBAAkB,CAAC;AAErD,eAAO,MAAM,0BAA0B,+YACsW,CAAC;AAE9Y,eAAO,MAAM,wBAAwB,mEAAmE,CAAC;AAEzG,eAAO,MAAM,2BAA2B,UAGvC,CAAC;AAiBF,4FAA4F;AAC5F,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,MAAM,CAQ/D","sourcesContent":["/**\n * plan/constants.ts — tool identity, command names, and prompt copy.\n *\n * Name/label/command live here so `index.ts` only assembles the extension,\n * matching the todo/question constants.ts pattern. Prompt copy is the\n * model-facing contract; keep it stable.\n */\n\nimport type { SelectOption } from \"../../core/extensions/types.ts\";\n\nexport const EXIT_PLAN_TOOL_NAME = \"exit_plan\";\nexport const EXIT_PLAN_TOOL_LABEL = \"Exit Plan\";\nexport const PLAN_COMMAND_NAME = \"plan\";\nexport const PLAN_FLAG_NAME = \"plan\";\nexport const PLAN_ENTRY_TYPE = \"plan-mode\";\nexport const PLAN_STATUS_KEY = \"plan-mode\";\n\n/**\n * Tools removed while planning. edit/write/bash grant write access directly;\n * subagent spawns child sessions that do not inherit the restricted tool set,\n * so leaving it active would let the model write through a child agent.\n * Other extension tools (question, todo, deepwiki, ...) stay available.\n */\nexport const PLAN_BLOCKED_TOOLS: ReadonlySet<string> = new Set([\"edit\", \"write\", \"bash\", \"subagent\"]);\n\n/** Max characters of plan markdown echoed into the post-compaction kickoff message. */\nexport const PLAN_EMBED_MAX_CHARS = 24_000;\n\nexport const MENU_EXECUTE = \"Start executing\";\nexport const MENU_COMPACT_EXECUTE = \"Compact context, then execute\";\nexport const MENU_KEEP_PLANNING = \"Keep planning\";\n\n/**\n * Approval choices. The label is the decision, the description is the\n * trade-off, so the three options can be told apart without reading to the\n * end of a sentence. select() resolves to the label.\n */\nexport const EXIT_PLAN_MENU_OPTIONS: readonly SelectOption[] = [\n\t{ label: MENU_EXECUTE, description: \"keep full context\" },\n\t{ label: MENU_COMPACT_EXECUTE, description: \"best for long tasks\" },\n\t{ label: MENU_KEEP_PLANNING, description: \"refine before running\" },\n];\n\n/** Esc maps to \"keep planning\", so the dismiss hint must not read as \"discard\". */\nexport const EXIT_PLAN_CANCEL_HINT = \"keep planning\";\n\nexport const EXIT_PLAN_TOOL_DESCRIPTION =\n\t\"Submit the finished plan and ask the user to approve leaving plan mode. Present the complete plan as normal response text first, then call this tool with a short title and the same plan markdown; on approval the plan is saved to disk. The user chooses between executing with full context, compacting the context before executing, or continuing to plan. Only available while plan mode is active.\";\n\nexport const EXIT_PLAN_PROMPT_SNIPPET = \"Submit the finished plan for user approval and leave plan mode\";\n\nexport const EXIT_PLAN_PROMPT_GUIDELINES = [\n\t\"Call `exit_plan` only after presenting the complete plan as response text; make it the only tool call in that step and stop after it returns.\",\n\t\"`exit_plan` results are authoritative for plan-mode state: follow them even when the plan-mode system prompt block is still present later in the run.\",\n];\n\nconst PLAN_MODE_PROMPT = `[PLAN MODE]\nYou are in plan mode: a read-only phase for exploring the codebase and agreeing on a plan with the user before any changes are made.\n\nRestrictions:\n- edit, write, bash, and subagent are unavailable; do not attempt changes or workarounds. Explore with read, grep, find, and ls instead.\n\nWorking style:\n- Answer questions from the code, not from the user: explore before asking.\n- When product intent, scope, or risk tolerance is unclear, interview the user with the question tool. Focus on one decision per exchange (batch only tightly coupled decisions) and always include your recommendation and the trade-offs.\n- Skip the interview for small, clear tasks. Pick sensible defaults for low-risk details and state them in the plan.\n\nDelivering the plan:\n- When the plan is ready, present it in full as normal response text (goal, steps, verification criteria) so the user can read it, then call exit_plan with a short title and the same markdown as the plan parameter.\n- The exit_plan tool result is authoritative for mode state: this block can outlive plan mode within a run, so if the result says plan mode has ended, it has ended.`;\n\n/** Build the plan-mode system prompt block, listing plans already saved for this branch. */\nexport function buildPlanModePrompt(planFiles: string[]): string {\n\tif (!planFiles.length) return PLAN_MODE_PROMPT;\n\tconst listing = planFiles.map((path) => `- ${path}`).join(\"\\n\");\n\treturn `${PLAN_MODE_PROMPT}\n\nPlans already saved for this session (read them with the read tool):\n${listing}\nWhen revising a saved plan, read the latest one first, account for work already completed, and produce a revision that states what changed and why; set the revises parameter to the file name of the plan being revised.`;\n}\n"]}
1
+ {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../../src/extensions/plan/constants.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gCAAgC,CAAC;AAEnE,eAAO,MAAM,mBAAmB,cAAc,CAAC;AAC/C,eAAO,MAAM,oBAAoB,cAAc,CAAC;AAChD,eAAO,MAAM,iBAAiB,SAAS,CAAC;AACxC,eAAO,MAAM,cAAc,SAAS,CAAC;AACrC,eAAO,MAAM,eAAe,cAAc,CAAC;AAC3C,eAAO,MAAM,eAAe,cAAc,CAAC;AAE3C;;;GAGG;AACH,eAAO,MAAM,yBAAyB,iBAAiB,CAAC;AAExD;;;;;;;GAOG;AACH,eAAO,MAAM,kBAAkB,EAAE,WAAW,CAAC,MAAM,CAA8B,CAAC;AAElF;;;;;;GAMG;AACH,eAAO,MAAM,kBAAkB,EAAE,SAAS,MAAM,EAA2C,CAAC;AAE5F,4EAA4E;AAC5E,eAAO,MAAM,qBAAqB,aAAa,CAAC;AAEhD,8EAA8E;AAC9E,eAAO,MAAM,uBAAuB,aAAa,CAAC;AAElD,uFAAuF;AACvF,eAAO,MAAM,oBAAoB,QAAS,CAAC;AAE3C,eAAO,MAAM,YAAY,oBAAoB,CAAC;AAC9C,eAAO,MAAM,oBAAoB,kCAAkC,CAAC;AACpE,eAAO,MAAM,kBAAkB,kBAAkB,CAAC;AAElD;;;;GAIG;AACH,eAAO,MAAM,sBAAsB,EAAE,SAAS,YAAY,EAIzD,CAAC;AAEF,mFAAmF;AACnF,eAAO,MAAM,qBAAqB,kBAAkB,CAAC;AAErD;;;GAGG;AACH,eAAO,MAAM,gBAAgB,cAAc,CAAC;AAC5C,eAAO,MAAM,eAAe,mBAAmB,CAAC;AAChD,eAAO,MAAM,2BAA2B,6BAA6B,CAAC;AACtE,eAAO,MAAM,sBAAsB,kBAAkB,CAAC;AACtD,eAAO,MAAM,oBAAoB,KAAK,CAAC;AAEvC,eAAO,MAAM,0BAA0B,+YACsW,CAAC;AAE9Y,eAAO,MAAM,wBAAwB,mEAAmE,CAAC;AAEzG,eAAO,MAAM,2BAA2B,UAGvC,CAAC;AAoBF,4FAA4F;AAC5F,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,MAAM,CAQ/D","sourcesContent":["/**\n * plan/constants.ts — tool identity, command names, and prompt copy.\n *\n * Name/label/command live here so `index.ts` only assembles the extension,\n * matching the todo/question constants.ts pattern. Prompt copy is the\n * model-facing contract; keep it stable.\n */\n\nimport type { SelectOption } from \"../../core/extensions/types.ts\";\n\nexport const EXIT_PLAN_TOOL_NAME = \"exit_plan\";\nexport const EXIT_PLAN_TOOL_LABEL = \"Exit Plan\";\nexport const PLAN_COMMAND_NAME = \"plan\";\nexport const PLAN_FLAG_NAME = \"plan\";\nexport const PLAN_ENTRY_TYPE = \"plan-mode\";\nexport const PLAN_STATUS_KEY = \"plan-mode\";\n\n/**\n * customType of the post-compaction kickoff message. Sent via sendMessage so\n * the LLM receives the full plan while the TUI renders a collapsed card.\n */\nexport const PLAN_KICKOFF_MESSAGE_TYPE = \"plan-kickoff\";\n\n/**\n * Tools blocked while planning: edit/write grant write access directly.\n * bash stays available and is constrained to read-only inspection via the\n * plan-mode system prompt (same contract as the explorer subagent), and\n * subagent stays available but is limited to the built-in explorer profile\n * by the tool_call guard. Other extension tools (question, todo, deepwiki,\n * ...) stay available.\n */\nexport const PLAN_BLOCKED_TOOLS: ReadonlySet<string> = new Set([\"edit\", \"write\"]);\n\n/**\n * Exploration tools activated on entry. Mirrors the explorer subagent's\n * EXPLORER_TOOLS (kept as a separate constant so plan does not depend on the\n * subagent extension): the standalone search tools plus bash for read-only\n * inspection. grep/find/ls are registered but not active in the default\n * session tool set, so plan mode must activate them explicitly.\n */\nexport const PLAN_EXPLORE_TOOLS: readonly string[] = [\"read\", \"grep\", \"find\", \"ls\", \"bash\"];\n\n/** Only subagent profile allowed while planning (read-only exploration). */\nexport const PLAN_ALLOWED_SUBAGENT = \"explorer\";\n\n/** Name of the subagent extension tool, for the plan-mode tool_call guard. */\nexport const PLAN_SUBAGENT_TOOL_NAME = \"subagent\";\n\n/** Max characters of plan markdown echoed into the post-compaction kickoff message. */\nexport const PLAN_EMBED_MAX_CHARS = 24_000;\n\nexport const MENU_EXECUTE = \"Start executing\";\nexport const MENU_COMPACT_EXECUTE = \"Compact context, then execute\";\nexport const MENU_KEEP_PLANNING = \"Keep planning\";\n\n/**\n * Approval choices. The label is the decision, the description is the\n * trade-off, so the three options can be told apart without reading to the\n * end of a sentence. select() resolves to the label.\n */\nexport const EXIT_PLAN_MENU_OPTIONS: readonly SelectOption[] = [\n\t{ label: MENU_EXECUTE, description: \"keep full context\" },\n\t{ label: MENU_COMPACT_EXECUTE, description: \"best for long tasks\" },\n\t{ label: MENU_KEEP_PLANNING, description: \"refine before running\" },\n];\n\n/** Esc maps to \"keep planning\", so the dismiss hint must not read as \"discard\". */\nexport const EXIT_PLAN_CANCEL_HINT = \"keep planning\";\n\n/**\n * /plan panel (shown when the command runs while planning): exit is the first\n * option, saved plans follow. Esc keeps planning with no side effects.\n */\nexport const PLAN_PANEL_TITLE = \"Plan mode\";\nexport const PLAN_PANEL_EXIT = \"Exit plan mode\";\nexport const PLAN_PANEL_EXIT_DESCRIPTION = \"restore full tool access\";\nexport const PLAN_PANEL_CANCEL_HINT = \"keep planning\";\nexport const PLAN_PANEL_MAX_FILES = 20;\n\nexport const EXIT_PLAN_TOOL_DESCRIPTION =\n\t\"Submit the finished plan and ask the user to approve leaving plan mode. Present the complete plan as normal response text first, then call this tool with a short title and the same plan markdown; on approval the plan is saved to disk. The user chooses between executing with full context, compacting the context before executing, or continuing to plan. Only available while plan mode is active.\";\n\nexport const EXIT_PLAN_PROMPT_SNIPPET = \"Submit the finished plan for user approval and leave plan mode\";\n\nexport const EXIT_PLAN_PROMPT_GUIDELINES = [\n\t\"Call `exit_plan` only after presenting the complete plan as response text; make it the only tool call in that step and stop after it returns.\",\n\t\"`exit_plan` results are authoritative for plan-mode state: follow them even when the plan-mode system prompt block is still present later in the run.\",\n];\n\nconst PLAN_MODE_PROMPT = `[PLAN MODE]\nYou are in plan mode: a read-only phase for exploring the codebase and agreeing on a plan with the user before any changes are made.\n\nRestrictions:\n- edit and write are unavailable; do not attempt changes or workarounds.\n- bash is for read-only inspection only: git log/diff/blame/show/status, ls, wc, head, tail, cat, and similar. Never run anything that modifies state: no file creation or deletion, no redirect (>, >>) or heredoc writes, no temp files, no git commands that write (add, commit, checkout, restore, stash, clean), no installs, no network access.\n- subagent is limited to the read-only explorer profile: always pass agent: \"${PLAN_ALLOWED_SUBAGENT}\"; other profiles are blocked while planning.\n- Explore with read, grep, find, ls, read-only bash, and explorer subagents.\n\nWorking style:\n- Answer questions from the code, not from the user: explore before asking.\n- When product intent, scope, or risk tolerance is unclear, interview the user with the question tool. Focus on one decision per exchange (batch only tightly coupled decisions) and always include your recommendation and the trade-offs.\n- Skip the interview for small, clear tasks. Pick sensible defaults for low-risk details and state them in the plan.\n\nDelivering the plan:\n- When the plan is ready, present it in full as normal response text (goal, steps, verification criteria) so the user can read it, then call exit_plan with a short title and the same markdown as the plan parameter.\n- The exit_plan tool result is authoritative for mode state: this block can outlive plan mode within a run, so if the result says plan mode has ended, it has ended.`;\n\n/** Build the plan-mode system prompt block, listing plans already saved for this branch. */\nexport function buildPlanModePrompt(planFiles: string[]): string {\n\tif (!planFiles.length) return PLAN_MODE_PROMPT;\n\tconst listing = planFiles.map((path) => `- ${path}`).join(\"\\n\");\n\treturn `${PLAN_MODE_PROMPT}\n\nPlans already saved for this session (read them with the read tool):\n${listing}\nWhen revising a saved plan, read the latest one first, account for work already completed, and produce a revision that states what changed and why; set the revises parameter to the file name of the plan being revised.`;\n}\n"]}