@caupulican/pi-adaptative 0.81.32 → 0.81.35

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 (237) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/dist/bundled-resources/extensions/tmux-agent-manager/README.md +69 -0
  3. package/dist/bundled-resources/extensions/tmux-agent-manager/index.d.ts +53 -0
  4. package/dist/bundled-resources/extensions/tmux-agent-manager/index.d.ts.map +1 -0
  5. package/dist/bundled-resources/extensions/tmux-agent-manager/index.js +1282 -0
  6. package/dist/bundled-resources/extensions/tmux-agent-manager/index.js.map +1 -0
  7. package/dist/bundled-resources/extensions/tmux-agent-manager/index.ts +1453 -0
  8. package/dist/bundled-resources/extensions/tmux-agent-manager/templates/builder-validator.json +14 -0
  9. package/dist/bundled-resources/extensions/tmux-agent-manager/templates/claude-trio.json +32 -0
  10. package/dist/bundled-resources/extensions/tmux-agent-manager/templates/full-provider-review.json +15 -0
  11. package/dist/bundled-resources/extensions/tmux-agent-manager/templates/provider-prompt-smoke.json +14 -0
  12. package/dist/bundled-resources/prompts/extensionify.md +5 -4
  13. package/dist/bundled-resources/prompts/skillify.md +5 -5
  14. package/dist/bundled-resources/skills/pi-harness-learning/SKILL.md +6 -3
  15. package/dist/config.d.ts +5 -1
  16. package/dist/config.d.ts.map +1 -1
  17. package/dist/config.js +10 -2
  18. package/dist/config.js.map +1 -1
  19. package/dist/core/agent-session.d.ts +7 -0
  20. package/dist/core/agent-session.d.ts.map +1 -1
  21. package/dist/core/agent-session.js +25 -2
  22. package/dist/core/agent-session.js.map +1 -1
  23. package/dist/core/background-lane-controller.d.ts +12 -3
  24. package/dist/core/background-lane-controller.d.ts.map +1 -1
  25. package/dist/core/background-lane-controller.js +67 -26
  26. package/dist/core/background-lane-controller.js.map +1 -1
  27. package/dist/core/bash-executor.d.ts.map +1 -1
  28. package/dist/core/bash-executor.js +7 -3
  29. package/dist/core/bash-executor.js.map +1 -1
  30. package/dist/core/context/context-store-retention.d.ts +4 -1
  31. package/dist/core/context/context-store-retention.d.ts.map +1 -1
  32. package/dist/core/context/context-store-retention.js +42 -151
  33. package/dist/core/context/context-store-retention.js.map +1 -1
  34. package/dist/core/context-pipeline.d.ts.map +1 -1
  35. package/dist/core/context-pipeline.js +6 -6
  36. package/dist/core/context-pipeline.js.map +1 -1
  37. package/dist/core/exec.d.ts.map +1 -1
  38. package/dist/core/exec.js +25 -39
  39. package/dist/core/exec.js.map +1 -1
  40. package/dist/core/extensions/loader.d.ts +9 -2
  41. package/dist/core/extensions/loader.d.ts.map +1 -1
  42. package/dist/core/extensions/loader.js +105 -21
  43. package/dist/core/extensions/loader.js.map +1 -1
  44. package/dist/core/footer-data-provider.d.ts.map +1 -1
  45. package/dist/core/footer-data-provider.js +2 -0
  46. package/dist/core/footer-data-provider.js.map +1 -1
  47. package/dist/core/goals/goal-state.d.ts +10 -0
  48. package/dist/core/goals/goal-state.d.ts.map +1 -1
  49. package/dist/core/goals/goal-state.js +37 -0
  50. package/dist/core/goals/goal-state.js.map +1 -1
  51. package/dist/core/goals/goal-tool-core.d.ts +11 -0
  52. package/dist/core/goals/goal-tool-core.d.ts.map +1 -1
  53. package/dist/core/goals/goal-tool-core.js +51 -1
  54. package/dist/core/goals/goal-tool-core.js.map +1 -1
  55. package/dist/core/improvement-loop.d.ts.map +1 -1
  56. package/dist/core/improvement-loop.js +26 -5
  57. package/dist/core/improvement-loop.js.map +1 -1
  58. package/dist/core/models/fitness-store.d.ts.map +1 -1
  59. package/dist/core/models/fitness-store.js +1 -1
  60. package/dist/core/models/fitness-store.js.map +1 -1
  61. package/dist/core/models/local-runtime.d.ts +6 -4
  62. package/dist/core/models/local-runtime.d.ts.map +1 -1
  63. package/dist/core/models/local-runtime.js +108 -36
  64. package/dist/core/models/local-runtime.js.map +1 -1
  65. package/dist/core/output-guard.d.ts.map +1 -1
  66. package/dist/core/output-guard.js +23 -8
  67. package/dist/core/output-guard.js.map +1 -1
  68. package/dist/core/package-manager.d.ts.map +1 -1
  69. package/dist/core/package-manager.js +44 -59
  70. package/dist/core/package-manager.js.map +1 -1
  71. package/dist/core/profile-resource-selection.d.ts +16 -0
  72. package/dist/core/profile-resource-selection.d.ts.map +1 -1
  73. package/dist/core/profile-resource-selection.js +41 -0
  74. package/dist/core/profile-resource-selection.js.map +1 -1
  75. package/dist/core/reflection-controller.d.ts.map +1 -1
  76. package/dist/core/reflection-controller.js +18 -1
  77. package/dist/core/reflection-controller.js.map +1 -1
  78. package/dist/core/reload-blockers.d.ts +1 -0
  79. package/dist/core/reload-blockers.d.ts.map +1 -1
  80. package/dist/core/reload-blockers.js +6 -2
  81. package/dist/core/reload-blockers.js.map +1 -1
  82. package/dist/core/resource-loader.d.ts +2 -0
  83. package/dist/core/resource-loader.d.ts.map +1 -1
  84. package/dist/core/resource-loader.js +50 -28
  85. package/dist/core/resource-loader.js.map +1 -1
  86. package/dist/core/settings-manager.d.ts.map +1 -1
  87. package/dist/core/settings-manager.js +1 -1
  88. package/dist/core/settings-manager.js.map +1 -1
  89. package/dist/core/slash-commands.d.ts.map +1 -1
  90. package/dist/core/slash-commands.js +1 -1
  91. package/dist/core/slash-commands.js.map +1 -1
  92. package/dist/core/toolkit/script-runner.d.ts.map +1 -1
  93. package/dist/core/toolkit/script-runner.js +58 -24
  94. package/dist/core/toolkit/script-runner.js.map +1 -1
  95. package/dist/core/tools/bash.d.ts.map +1 -1
  96. package/dist/core/tools/bash.js +13 -26
  97. package/dist/core/tools/bash.js.map +1 -1
  98. package/dist/core/tools/extensionify.d.ts +2 -1
  99. package/dist/core/tools/extensionify.d.ts.map +1 -1
  100. package/dist/core/tools/extensionify.js +11 -8
  101. package/dist/core/tools/extensionify.js.map +1 -1
  102. package/dist/core/tools/find.d.ts.map +1 -1
  103. package/dist/core/tools/find.js +54 -48
  104. package/dist/core/tools/find.js.map +1 -1
  105. package/dist/core/tools/git-filter.d.ts.map +1 -1
  106. package/dist/core/tools/git-filter.js +20 -32
  107. package/dist/core/tools/git-filter.js.map +1 -1
  108. package/dist/core/tools/goal.d.ts +1 -1
  109. package/dist/core/tools/goal.d.ts.map +1 -1
  110. package/dist/core/tools/goal.js +10 -3
  111. package/dist/core/tools/goal.js.map +1 -1
  112. package/dist/core/tools/grep.d.ts.map +1 -1
  113. package/dist/core/tools/grep.js +89 -80
  114. package/dist/core/tools/grep.js.map +1 -1
  115. package/dist/core/tools/output-accumulator.d.ts +2 -0
  116. package/dist/core/tools/output-accumulator.d.ts.map +1 -1
  117. package/dist/core/tools/output-accumulator.js +7 -4
  118. package/dist/core/tools/output-accumulator.js.map +1 -1
  119. package/dist/core/tools/read.d.ts.map +1 -1
  120. package/dist/core/tools/read.js +4 -3
  121. package/dist/core/tools/read.js.map +1 -1
  122. package/dist/index.d.ts +2 -1
  123. package/dist/index.d.ts.map +1 -1
  124. package/dist/index.js +2 -1
  125. package/dist/index.js.map +1 -1
  126. package/dist/main.d.ts +2 -0
  127. package/dist/main.d.ts.map +1 -1
  128. package/dist/main.js +23 -4
  129. package/dist/main.js.map +1 -1
  130. package/dist/migrations.d.ts.map +1 -1
  131. package/dist/migrations.js +18 -4
  132. package/dist/migrations.js.map +1 -1
  133. package/dist/modes/interactive/auth-dialogs-controller.d.ts +10 -0
  134. package/dist/modes/interactive/auth-dialogs-controller.d.ts.map +1 -1
  135. package/dist/modes/interactive/auth-dialogs-controller.js +94 -27
  136. package/dist/modes/interactive/auth-dialogs-controller.js.map +1 -1
  137. package/dist/modes/interactive/auto-learn-controller.d.ts.map +1 -1
  138. package/dist/modes/interactive/auto-learn-controller.js +2 -1
  139. package/dist/modes/interactive/auto-learn-controller.js.map +1 -1
  140. package/dist/modes/interactive/components/bordered-loader.d.ts +1 -0
  141. package/dist/modes/interactive/components/bordered-loader.d.ts.map +1 -1
  142. package/dist/modes/interactive/components/bordered-loader.js +8 -0
  143. package/dist/modes/interactive/components/bordered-loader.js.map +1 -1
  144. package/dist/modes/interactive/components/extension-editor.d.ts.map +1 -1
  145. package/dist/modes/interactive/components/extension-editor.js +6 -15
  146. package/dist/modes/interactive/components/extension-editor.js.map +1 -1
  147. package/dist/modes/interactive/components/login-dialog.d.ts +3 -1
  148. package/dist/modes/interactive/components/login-dialog.d.ts.map +1 -1
  149. package/dist/modes/interactive/components/login-dialog.js +20 -8
  150. package/dist/modes/interactive/components/login-dialog.js.map +1 -1
  151. package/dist/modes/interactive/components/profile-resource-editor.d.ts +4 -1
  152. package/dist/modes/interactive/components/profile-resource-editor.d.ts.map +1 -1
  153. package/dist/modes/interactive/components/profile-resource-editor.js +22 -39
  154. package/dist/modes/interactive/components/profile-resource-editor.js.map +1 -1
  155. package/dist/modes/interactive/components/session-selector.d.ts.map +1 -1
  156. package/dist/modes/interactive/components/session-selector.js +1 -1
  157. package/dist/modes/interactive/components/session-selector.js.map +1 -1
  158. package/dist/modes/interactive/config-backup.d.ts +1 -0
  159. package/dist/modes/interactive/config-backup.d.ts.map +1 -1
  160. package/dist/modes/interactive/config-backup.js +14 -3
  161. package/dist/modes/interactive/config-backup.js.map +1 -1
  162. package/dist/modes/interactive/editor-overlay-host.d.ts +12 -0
  163. package/dist/modes/interactive/editor-overlay-host.d.ts.map +1 -1
  164. package/dist/modes/interactive/editor-overlay-host.js +55 -0
  165. package/dist/modes/interactive/editor-overlay-host.js.map +1 -1
  166. package/dist/modes/interactive/extension-ui-host.d.ts +2 -1
  167. package/dist/modes/interactive/extension-ui-host.d.ts.map +1 -1
  168. package/dist/modes/interactive/extension-ui-host.js +161 -96
  169. package/dist/modes/interactive/extension-ui-host.js.map +1 -1
  170. package/dist/modes/interactive/external-editor.d.ts.map +1 -1
  171. package/dist/modes/interactive/external-editor.js +23 -28
  172. package/dist/modes/interactive/external-editor.js.map +1 -1
  173. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  174. package/dist/modes/interactive/interactive-mode.js +14 -8
  175. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  176. package/dist/modes/interactive/profile-menu-controller.d.ts +5 -1
  177. package/dist/modes/interactive/profile-menu-controller.d.ts.map +1 -1
  178. package/dist/modes/interactive/profile-menu-controller.js +118 -24
  179. package/dist/modes/interactive/profile-menu-controller.js.map +1 -1
  180. package/dist/modes/interactive/resource-shell-commands.d.ts +1 -0
  181. package/dist/modes/interactive/resource-shell-commands.d.ts.map +1 -1
  182. package/dist/modes/interactive/resource-shell-commands.js +5 -1
  183. package/dist/modes/interactive/resource-shell-commands.js.map +1 -1
  184. package/dist/modes/interactive/session-flow-commands.d.ts.map +1 -1
  185. package/dist/modes/interactive/session-flow-commands.js +100 -7
  186. package/dist/modes/interactive/session-flow-commands.js.map +1 -1
  187. package/dist/modes/interactive/session-io-commands.d.ts.map +1 -1
  188. package/dist/modes/interactive/session-io-commands.js +80 -50
  189. package/dist/modes/interactive/session-io-commands.js.map +1 -1
  190. package/dist/modes/interactive/startup-checks.d.ts.map +1 -1
  191. package/dist/modes/interactive/startup-checks.js +14 -17
  192. package/dist/modes/interactive/startup-checks.js.map +1 -1
  193. package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
  194. package/dist/modes/rpc/rpc-client.js +20 -19
  195. package/dist/modes/rpc/rpc-client.js.map +1 -1
  196. package/dist/package-manager-cli.d.ts.map +1 -1
  197. package/dist/package-manager-cli.js +10 -19
  198. package/dist/package-manager-cli.js.map +1 -1
  199. package/dist/utils/child-process.d.ts +13 -7
  200. package/dist/utils/child-process.d.ts.map +1 -1
  201. package/dist/utils/child-process.js +56 -3
  202. package/dist/utils/child-process.js.map +1 -1
  203. package/dist/utils/clipboard-image.d.ts.map +1 -1
  204. package/dist/utils/clipboard-image.js +3 -2
  205. package/dist/utils/clipboard-image.js.map +1 -1
  206. package/dist/utils/clipboard.d.ts.map +1 -1
  207. package/dist/utils/clipboard.js +17 -10
  208. package/dist/utils/clipboard.js.map +1 -1
  209. package/dist/utils/external-editor-command.d.ts +8 -0
  210. package/dist/utils/external-editor-command.d.ts.map +1 -0
  211. package/dist/utils/external-editor-command.js +60 -0
  212. package/dist/utils/external-editor-command.js.map +1 -0
  213. package/dist/utils/paths.d.ts.map +1 -1
  214. package/dist/utils/paths.js +10 -2
  215. package/dist/utils/paths.js.map +1 -1
  216. package/dist/utils/tools-manager.d.ts.map +1 -1
  217. package/dist/utils/tools-manager.js +23 -10
  218. package/dist/utils/tools-manager.js.map +1 -1
  219. package/dist/utils/work-directory.d.ts +36 -0
  220. package/dist/utils/work-directory.d.ts.map +1 -0
  221. package/dist/utils/work-directory.js +462 -0
  222. package/dist/utils/work-directory.js.map +1 -0
  223. package/docs/index.md +2 -0
  224. package/docs/resources.md +2 -2
  225. package/docs/rpc.md +1 -1
  226. package/docs/tmux-agent-manager.md +48 -0
  227. package/docs/windows.md +100 -7
  228. package/docs/work-directory.md +41 -0
  229. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  230. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  231. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  232. package/examples/extensions/sandbox/package-lock.json +2 -2
  233. package/examples/extensions/sandbox/package.json +1 -1
  234. package/examples/extensions/with-deps/package-lock.json +2 -2
  235. package/examples/extensions/with-deps/package.json +1 -1
  236. package/npm-shrinkwrap.json +12 -12
  237. package/package.json +4 -4
@@ -1 +1 @@
1
- {"version":3,"file":"external-editor.d.ts","sourceRoot":"","sources":["../../../src/modes/interactive/external-editor.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAMH,OAAO,KAAK,EAAE,eAAe,EAAE,GAAG,EAAE,MAAM,oBAAoB,CAAC;AAE/D,MAAM,WAAW,kBAAkB;IAClC,QAAQ,CAAC,MAAM,EAAE,eAAe,CAAC;IACjC,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,GAAG,eAAe,CAAC,CAAC;IAC3D,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CACnC;AAMD,wBAAsB,kBAAkB,CAAC,IAAI,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC,CAqDhF;AAED,wBAAsB,iBAAiB,CAAC,IAAI,EAAE,kBAAkB,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAiDpG","sourcesContent":["/**\n * External-editor launch flows extracted from interactive-mode.\n *\n * `openExternalEditor` edits the current editor buffer in `$VISUAL`/`$EDITOR`;\n * `openEditorForPath` opens an arbitrary file (falling back to `vi`). Both stop\n * the TUI to release the terminal, spawn the editor, and restart the TUI with a\n * forced full re-render. They operate through a narrow `ExternalEditorHost` seam;\n * interactive-mode keeps thin delegating wrappers.\n */\n\nimport { spawn } from \"node:child_process\";\nimport * as fs from \"node:fs\";\nimport * as os from \"node:os\";\nimport * as path from \"node:path\";\nimport type { EditorComponent, TUI } from \"@caupulican/pi-tui\";\n\nexport interface ExternalEditorHost {\n\treadonly editor: EditorComponent;\n\treadonly ui: Pick<TUI, \"stop\" | \"start\" | \"requestRender\">;\n\tshowWarning(message: string): void;\n}\n\nfunction resolveEditorCommand(): string | undefined {\n\treturn process.env.VISUAL || process.env.EDITOR;\n}\n\nexport async function openExternalEditor(host: ExternalEditorHost): Promise<void> {\n\tconst editorCmd = resolveEditorCommand();\n\tif (!editorCmd) {\n\t\thost.showWarning(\"No editor configured. Set $VISUAL or $EDITOR environment variable.\");\n\t\treturn;\n\t}\n\n\tconst currentText = host.editor.getExpandedText?.() ?? host.editor.getText();\n\tconst tmpFile = path.join(os.tmpdir(), `pi-editor-${Date.now()}.pi.md`);\n\n\ttry {\n\t\t// Write current content to temp file\n\t\tfs.writeFileSync(tmpFile, currentText, \"utf-8\");\n\n\t\t// Stop TUI to release terminal\n\t\thost.ui.stop();\n\n\t\t// Split by space to support editor arguments (e.g., \"code --wait\")\n\t\tconst [editor, ...editorArgs] = editorCmd.split(\" \");\n\n\t\tprocess.stdout.write(`Launching external editor: ${editorCmd}\\nPi will resume when the editor exits.\\n`);\n\n\t\t// Do not use spawnSync here. On Windows, synchronous child_process calls can keep\n\t\t// Node/libuv's console input read active after ui.stop() pauses stdin, racing\n\t\t// vim/nvim for the console input buffer until Ctrl+C cancels the pending read.\n\t\tconst status = await new Promise<number | null>((resolve) => {\n\t\t\tconst child = spawn(editor, [...editorArgs, tmpFile], {\n\t\t\t\tstdio: \"inherit\",\n\t\t\t\tshell: process.platform === \"win32\",\n\t\t\t});\n\t\t\tchild.on(\"error\", () => resolve(null));\n\t\t\tchild.on(\"close\", (code) => resolve(code));\n\t\t});\n\n\t\t// On successful exit (status 0), replace editor content\n\t\tif (status === 0) {\n\t\t\tconst newContent = fs.readFileSync(tmpFile, \"utf-8\").replace(/\\n$/, \"\");\n\t\t\thost.editor.setText(newContent);\n\t\t}\n\t\t// On non-zero exit, keep original text (no action needed)\n\t} finally {\n\t\t// Clean up temp file\n\t\ttry {\n\t\t\tfs.unlinkSync(tmpFile);\n\t\t} catch {\n\t\t\t// Ignore cleanup errors\n\t\t}\n\n\t\t// Restart TUI\n\t\thost.ui.start();\n\t\t// Force full re-render since external editor uses alternate screen\n\t\thost.ui.requestRender(true);\n\t}\n}\n\nexport async function openEditorForPath(host: ExternalEditorHost, filePath: string): Promise<boolean> {\n\tlet editorCmd = resolveEditorCommand();\n\tlet isFallback = false;\n\tif (!editorCmd) {\n\t\teditorCmd = \"vi\";\n\t\tisFallback = true;\n\t}\n\n\ttry {\n\t\t// Stop TUI to release terminal\n\t\thost.ui.stop();\n\n\t\t// Split by space to support editor arguments (e.g., \"code --wait\")\n\t\tconst [editor, ...editorArgs] = editorCmd.split(\" \");\n\n\t\tprocess.stdout.write(\n\t\t\t`Launching external editor: ${editorCmd} ${filePath}\\nPi will resume when the editor exits.\\n`,\n\t\t);\n\n\t\tconst status = await new Promise<number | null>((resolve) => {\n\t\t\tconst child = spawn(editor, [...editorArgs, filePath], {\n\t\t\t\tstdio: \"inherit\",\n\t\t\t\tshell: process.platform === \"win32\",\n\t\t\t});\n\t\t\tchild.on(\"error\", () => resolve(null));\n\t\t\tchild.on(\"close\", (code) => resolve(code));\n\t\t});\n\n\t\tif (status === null) {\n\t\t\tif (isFallback) {\n\t\t\t\tprocess.stdout.write(`\\nError: Failed to launch fallback editor \"vi\".\\n`);\n\t\t\t} else {\n\t\t\t\tprocess.stdout.write(`\\nError: Failed to launch editor \"${editorCmd}\".\\n`);\n\t\t\t}\n\t\t\tprocess.stdout.write(`Please set the $EDITOR or $VISUAL environment variable to edit inline.\\n`);\n\t\t\tprocess.stdout.write(`Absolute file path: ${filePath}\\n\\nPress Enter to return to Pi...`);\n\t\t\t// Wait for enter key\n\t\t\tawait new Promise<void>((resolve) => {\n\t\t\t\tprocess.stdin.once(\"data\", () => resolve());\n\t\t\t});\n\t\t}\n\n\t\treturn status === 0;\n\t} finally {\n\t\t// Restart TUI\n\t\thost.ui.start();\n\t\t// Force full re-render since external editor uses alternate screen\n\t\thost.ui.requestRender(true);\n\t}\n}\n"]}
1
+ {"version":3,"file":"external-editor.d.ts","sourceRoot":"","sources":["../../../src/modes/interactive/external-editor.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAKH,OAAO,KAAK,EAAE,eAAe,EAAE,GAAG,EAAE,MAAM,oBAAoB,CAAC;AAK/D,MAAM,WAAW,kBAAkB;IAClC,QAAQ,CAAC,MAAM,EAAE,eAAe,CAAC;IACjC,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,GAAG,eAAe,CAAC,CAAC;IAC3D,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CACnC;AAMD,wBAAsB,kBAAkB,CAAC,IAAI,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC,CA6ChF;AAED,wBAAsB,iBAAiB,CAAC,IAAI,EAAE,kBAAkB,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAkDpG","sourcesContent":["/**\n * External-editor launch flows extracted from interactive-mode.\n *\n * `openExternalEditor` edits the current editor buffer in `$VISUAL`/`$EDITOR`;\n * `openEditorForPath` opens an arbitrary file (falling back to `vi`). Both stop\n * the TUI to release the terminal, spawn the editor, and restart the TUI with a\n * forced full re-render. They operate through a narrow `ExternalEditorHost` seam;\n * interactive-mode keeps thin delegating wrappers.\n */\n\nimport { randomUUID } from \"node:crypto\";\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport type { EditorComponent, TUI } from \"@caupulican/pi-tui\";\nimport { getAgentDir } from \"../../config.ts\";\nimport { runExternalEditor } from \"../../utils/external-editor-command.ts\";\nimport { getProcessWorkRun } from \"../../utils/work-directory.ts\";\n\nexport interface ExternalEditorHost {\n\treadonly editor: EditorComponent;\n\treadonly ui: Pick<TUI, \"stop\" | \"start\" | \"requestRender\">;\n\tshowWarning(message: string): void;\n}\n\nfunction resolveEditorCommand(): string | undefined {\n\treturn process.env.VISUAL || process.env.EDITOR;\n}\n\nexport async function openExternalEditor(host: ExternalEditorHost): Promise<void> {\n\tconst editorCmd = resolveEditorCommand();\n\tif (!editorCmd) {\n\t\thost.showWarning(\"No editor configured. Set $VISUAL or $EDITOR environment variable.\");\n\t\treturn;\n\t}\n\n\tconst currentText = host.editor.getExpandedText?.() ?? host.editor.getText();\n\tconst tmpFile = path.join(\n\t\tgetProcessWorkRun(getAgentDir(), \"editors\", \"external\").path,\n\t\t`pi-editor-${randomUUID()}.pi.md`,\n\t);\n\n\ttry {\n\t\t// Write current content to temp file\n\t\tfs.writeFileSync(tmpFile, currentText, \"utf-8\");\n\n\t\t// Stop TUI to release terminal\n\t\thost.ui.stop();\n\n\t\tprocess.stdout.write(`Launching external editor: ${editorCmd}\\nPi will resume when the editor exits.\\n`);\n\n\t\t// Cross-platform executable resolution preserves quoted Windows paths without\n\t\t// asking a shell to reinterpret the temporary file path.\n\t\tconst status = await runExternalEditor(editorCmd, tmpFile);\n\n\t\t// On successful exit (status 0), replace editor content\n\t\tif (status === 0) {\n\t\t\tconst newContent = fs.readFileSync(tmpFile, \"utf-8\").replace(/\\n$/, \"\");\n\t\t\thost.editor.setText(newContent);\n\t\t}\n\t\t// On non-zero exit, keep original text (no action needed)\n\t} finally {\n\t\t// Clean up temp file\n\t\ttry {\n\t\t\tfs.unlinkSync(tmpFile);\n\t\t} catch {\n\t\t\t// Ignore cleanup errors\n\t\t}\n\n\t\t// Restart TUI\n\t\thost.ui.start();\n\t\t// Force full re-render since external editor uses alternate screen\n\t\thost.ui.requestRender(true);\n\t}\n}\n\nexport async function openEditorForPath(host: ExternalEditorHost, filePath: string): Promise<boolean> {\n\tlet editorCmd = resolveEditorCommand();\n\tlet isFallback = false;\n\tif (!editorCmd) {\n\t\teditorCmd = \"vi\";\n\t\tisFallback = true;\n\t}\n\n\ttry {\n\t\t// Stop TUI to release terminal\n\t\thost.ui.stop();\n\n\t\tprocess.stdout.write(\n\t\t\t`Launching external editor: ${editorCmd} ${filePath}\\nPi will resume when the editor exits.\\n`,\n\t\t);\n\n\t\tconst status = await runExternalEditor(editorCmd, filePath);\n\n\t\tif (status === null) {\n\t\t\tif (isFallback) {\n\t\t\t\tprocess.stdout.write(`\\nError: Failed to launch fallback editor \"vi\".\\n`);\n\t\t\t} else {\n\t\t\t\tprocess.stdout.write(`\\nError: Failed to launch editor \"${editorCmd}\".\\n`);\n\t\t\t}\n\t\t\tprocess.stdout.write(`Please set the $EDITOR or $VISUAL environment variable to edit inline.\\n`);\n\t\t\tprocess.stdout.write(`Absolute file path: ${filePath}\\n\\nPress Enter to return to Pi...`);\n\t\t\t// Wait for enter, but do not remain pending if stdin closes during shutdown.\n\t\t\tawait new Promise<void>((resolve) => {\n\t\t\t\tlet settled = false;\n\t\t\t\tconst finish = () => {\n\t\t\t\t\tif (settled) return;\n\t\t\t\t\tsettled = true;\n\t\t\t\t\tprocess.stdin.removeListener(\"data\", finish);\n\t\t\t\t\tprocess.stdin.removeListener(\"end\", finish);\n\t\t\t\t\tprocess.stdin.removeListener(\"error\", finish);\n\t\t\t\t\tresolve();\n\t\t\t\t};\n\t\t\t\tprocess.stdin.once(\"data\", finish);\n\t\t\t\tprocess.stdin.once(\"end\", finish);\n\t\t\t\tprocess.stdin.once(\"error\", finish);\n\t\t\t});\n\t\t}\n\n\t\treturn status === 0;\n\t} finally {\n\t\t// Restart TUI\n\t\thost.ui.start();\n\t\t// Force full re-render since external editor uses alternate screen\n\t\thost.ui.requestRender(true);\n\t}\n}\n"]}
@@ -7,10 +7,12 @@
7
7
  * forced full re-render. They operate through a narrow `ExternalEditorHost` seam;
8
8
  * interactive-mode keeps thin delegating wrappers.
9
9
  */
10
- import { spawn } from "node:child_process";
10
+ import { randomUUID } from "node:crypto";
11
11
  import * as fs from "node:fs";
12
- import * as os from "node:os";
13
12
  import * as path from "node:path";
13
+ import { getAgentDir } from "../../config.js";
14
+ import { runExternalEditor } from "../../utils/external-editor-command.js";
15
+ import { getProcessWorkRun } from "../../utils/work-directory.js";
14
16
  function resolveEditorCommand() {
15
17
  return process.env.VISUAL || process.env.EDITOR;
16
18
  }
@@ -21,26 +23,16 @@ export async function openExternalEditor(host) {
21
23
  return;
22
24
  }
23
25
  const currentText = host.editor.getExpandedText?.() ?? host.editor.getText();
24
- const tmpFile = path.join(os.tmpdir(), `pi-editor-${Date.now()}.pi.md`);
26
+ const tmpFile = path.join(getProcessWorkRun(getAgentDir(), "editors", "external").path, `pi-editor-${randomUUID()}.pi.md`);
25
27
  try {
26
28
  // Write current content to temp file
27
29
  fs.writeFileSync(tmpFile, currentText, "utf-8");
28
30
  // Stop TUI to release terminal
29
31
  host.ui.stop();
30
- // Split by space to support editor arguments (e.g., "code --wait")
31
- const [editor, ...editorArgs] = editorCmd.split(" ");
32
32
  process.stdout.write(`Launching external editor: ${editorCmd}\nPi will resume when the editor exits.\n`);
33
- // Do not use spawnSync here. On Windows, synchronous child_process calls can keep
34
- // Node/libuv's console input read active after ui.stop() pauses stdin, racing
35
- // vim/nvim for the console input buffer until Ctrl+C cancels the pending read.
36
- const status = await new Promise((resolve) => {
37
- const child = spawn(editor, [...editorArgs, tmpFile], {
38
- stdio: "inherit",
39
- shell: process.platform === "win32",
40
- });
41
- child.on("error", () => resolve(null));
42
- child.on("close", (code) => resolve(code));
43
- });
33
+ // Cross-platform executable resolution preserves quoted Windows paths without
34
+ // asking a shell to reinterpret the temporary file path.
35
+ const status = await runExternalEditor(editorCmd, tmpFile);
44
36
  // On successful exit (status 0), replace editor content
45
37
  if (status === 0) {
46
38
  const newContent = fs.readFileSync(tmpFile, "utf-8").replace(/\n$/, "");
@@ -72,17 +64,8 @@ export async function openEditorForPath(host, filePath) {
72
64
  try {
73
65
  // Stop TUI to release terminal
74
66
  host.ui.stop();
75
- // Split by space to support editor arguments (e.g., "code --wait")
76
- const [editor, ...editorArgs] = editorCmd.split(" ");
77
67
  process.stdout.write(`Launching external editor: ${editorCmd} ${filePath}\nPi will resume when the editor exits.\n`);
78
- const status = await new Promise((resolve) => {
79
- const child = spawn(editor, [...editorArgs, filePath], {
80
- stdio: "inherit",
81
- shell: process.platform === "win32",
82
- });
83
- child.on("error", () => resolve(null));
84
- child.on("close", (code) => resolve(code));
85
- });
68
+ const status = await runExternalEditor(editorCmd, filePath);
86
69
  if (status === null) {
87
70
  if (isFallback) {
88
71
  process.stdout.write(`\nError: Failed to launch fallback editor "vi".\n`);
@@ -92,9 +75,21 @@ export async function openEditorForPath(host, filePath) {
92
75
  }
93
76
  process.stdout.write(`Please set the $EDITOR or $VISUAL environment variable to edit inline.\n`);
94
77
  process.stdout.write(`Absolute file path: ${filePath}\n\nPress Enter to return to Pi...`);
95
- // Wait for enter key
78
+ // Wait for enter, but do not remain pending if stdin closes during shutdown.
96
79
  await new Promise((resolve) => {
97
- process.stdin.once("data", () => resolve());
80
+ let settled = false;
81
+ const finish = () => {
82
+ if (settled)
83
+ return;
84
+ settled = true;
85
+ process.stdin.removeListener("data", finish);
86
+ process.stdin.removeListener("end", finish);
87
+ process.stdin.removeListener("error", finish);
88
+ resolve();
89
+ };
90
+ process.stdin.once("data", finish);
91
+ process.stdin.once("end", finish);
92
+ process.stdin.once("error", finish);
98
93
  });
99
94
  }
100
95
  return status === 0;
@@ -1 +1 @@
1
- {"version":3,"file":"external-editor.js","sourceRoot":"","sources":["../../../src/modes/interactive/external-editor.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * External-editor launch flows extracted from interactive-mode.\n *\n * `openExternalEditor` edits the current editor buffer in `$VISUAL`/`$EDITOR`;\n * `openEditorForPath` opens an arbitrary file (falling back to `vi`). Both stop\n * the TUI to release the terminal, spawn the editor, and restart the TUI with a\n * forced full re-render. They operate through a narrow `ExternalEditorHost` seam;\n * interactive-mode keeps thin delegating wrappers.\n */\n\nimport { spawn } from \"node:child_process\";\nimport * as fs from \"node:fs\";\nimport * as os from \"node:os\";\nimport * as path from \"node:path\";\nimport type { EditorComponent, TUI } from \"@caupulican/pi-tui\";\n\nexport interface ExternalEditorHost {\n\treadonly editor: EditorComponent;\n\treadonly ui: Pick<TUI, \"stop\" | \"start\" | \"requestRender\">;\n\tshowWarning(message: string): void;\n}\n\nfunction resolveEditorCommand(): string | undefined {\n\treturn process.env.VISUAL || process.env.EDITOR;\n}\n\nexport async function openExternalEditor(host: ExternalEditorHost): Promise<void> {\n\tconst editorCmd = resolveEditorCommand();\n\tif (!editorCmd) {\n\t\thost.showWarning(\"No editor configured. Set $VISUAL or $EDITOR environment variable.\");\n\t\treturn;\n\t}\n\n\tconst currentText = host.editor.getExpandedText?.() ?? host.editor.getText();\n\tconst tmpFile = path.join(os.tmpdir(), `pi-editor-${Date.now()}.pi.md`);\n\n\ttry {\n\t\t// Write current content to temp file\n\t\tfs.writeFileSync(tmpFile, currentText, \"utf-8\");\n\n\t\t// Stop TUI to release terminal\n\t\thost.ui.stop();\n\n\t\t// Split by space to support editor arguments (e.g., \"code --wait\")\n\t\tconst [editor, ...editorArgs] = editorCmd.split(\" \");\n\n\t\tprocess.stdout.write(`Launching external editor: ${editorCmd}\\nPi will resume when the editor exits.\\n`);\n\n\t\t// Do not use spawnSync here. On Windows, synchronous child_process calls can keep\n\t\t// Node/libuv's console input read active after ui.stop() pauses stdin, racing\n\t\t// vim/nvim for the console input buffer until Ctrl+C cancels the pending read.\n\t\tconst status = await new Promise<number | null>((resolve) => {\n\t\t\tconst child = spawn(editor, [...editorArgs, tmpFile], {\n\t\t\t\tstdio: \"inherit\",\n\t\t\t\tshell: process.platform === \"win32\",\n\t\t\t});\n\t\t\tchild.on(\"error\", () => resolve(null));\n\t\t\tchild.on(\"close\", (code) => resolve(code));\n\t\t});\n\n\t\t// On successful exit (status 0), replace editor content\n\t\tif (status === 0) {\n\t\t\tconst newContent = fs.readFileSync(tmpFile, \"utf-8\").replace(/\\n$/, \"\");\n\t\t\thost.editor.setText(newContent);\n\t\t}\n\t\t// On non-zero exit, keep original text (no action needed)\n\t} finally {\n\t\t// Clean up temp file\n\t\ttry {\n\t\t\tfs.unlinkSync(tmpFile);\n\t\t} catch {\n\t\t\t// Ignore cleanup errors\n\t\t}\n\n\t\t// Restart TUI\n\t\thost.ui.start();\n\t\t// Force full re-render since external editor uses alternate screen\n\t\thost.ui.requestRender(true);\n\t}\n}\n\nexport async function openEditorForPath(host: ExternalEditorHost, filePath: string): Promise<boolean> {\n\tlet editorCmd = resolveEditorCommand();\n\tlet isFallback = false;\n\tif (!editorCmd) {\n\t\teditorCmd = \"vi\";\n\t\tisFallback = true;\n\t}\n\n\ttry {\n\t\t// Stop TUI to release terminal\n\t\thost.ui.stop();\n\n\t\t// Split by space to support editor arguments (e.g., \"code --wait\")\n\t\tconst [editor, ...editorArgs] = editorCmd.split(\" \");\n\n\t\tprocess.stdout.write(\n\t\t\t`Launching external editor: ${editorCmd} ${filePath}\\nPi will resume when the editor exits.\\n`,\n\t\t);\n\n\t\tconst status = await new Promise<number | null>((resolve) => {\n\t\t\tconst child = spawn(editor, [...editorArgs, filePath], {\n\t\t\t\tstdio: \"inherit\",\n\t\t\t\tshell: process.platform === \"win32\",\n\t\t\t});\n\t\t\tchild.on(\"error\", () => resolve(null));\n\t\t\tchild.on(\"close\", (code) => resolve(code));\n\t\t});\n\n\t\tif (status === null) {\n\t\t\tif (isFallback) {\n\t\t\t\tprocess.stdout.write(`\\nError: Failed to launch fallback editor \"vi\".\\n`);\n\t\t\t} else {\n\t\t\t\tprocess.stdout.write(`\\nError: Failed to launch editor \"${editorCmd}\".\\n`);\n\t\t\t}\n\t\t\tprocess.stdout.write(`Please set the $EDITOR or $VISUAL environment variable to edit inline.\\n`);\n\t\t\tprocess.stdout.write(`Absolute file path: ${filePath}\\n\\nPress Enter to return to Pi...`);\n\t\t\t// Wait for enter key\n\t\t\tawait new Promise<void>((resolve) => {\n\t\t\t\tprocess.stdin.once(\"data\", () => resolve());\n\t\t\t});\n\t\t}\n\n\t\treturn status === 0;\n\t} finally {\n\t\t// Restart TUI\n\t\thost.ui.start();\n\t\t// Force full re-render since external editor uses alternate screen\n\t\thost.ui.requestRender(true);\n\t}\n}\n"]}
1
+ {"version":3,"file":"external-editor.js","sourceRoot":"","sources":["../../../src/modes/interactive/external-editor.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * External-editor launch flows extracted from interactive-mode.\n *\n * `openExternalEditor` edits the current editor buffer in `$VISUAL`/`$EDITOR`;\n * `openEditorForPath` opens an arbitrary file (falling back to `vi`). Both stop\n * the TUI to release the terminal, spawn the editor, and restart the TUI with a\n * forced full re-render. They operate through a narrow `ExternalEditorHost` seam;\n * interactive-mode keeps thin delegating wrappers.\n */\n\nimport { randomUUID } from \"node:crypto\";\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport type { EditorComponent, TUI } from \"@caupulican/pi-tui\";\nimport { getAgentDir } from \"../../config.ts\";\nimport { runExternalEditor } from \"../../utils/external-editor-command.ts\";\nimport { getProcessWorkRun } from \"../../utils/work-directory.ts\";\n\nexport interface ExternalEditorHost {\n\treadonly editor: EditorComponent;\n\treadonly ui: Pick<TUI, \"stop\" | \"start\" | \"requestRender\">;\n\tshowWarning(message: string): void;\n}\n\nfunction resolveEditorCommand(): string | undefined {\n\treturn process.env.VISUAL || process.env.EDITOR;\n}\n\nexport async function openExternalEditor(host: ExternalEditorHost): Promise<void> {\n\tconst editorCmd = resolveEditorCommand();\n\tif (!editorCmd) {\n\t\thost.showWarning(\"No editor configured. Set $VISUAL or $EDITOR environment variable.\");\n\t\treturn;\n\t}\n\n\tconst currentText = host.editor.getExpandedText?.() ?? host.editor.getText();\n\tconst tmpFile = path.join(\n\t\tgetProcessWorkRun(getAgentDir(), \"editors\", \"external\").path,\n\t\t`pi-editor-${randomUUID()}.pi.md`,\n\t);\n\n\ttry {\n\t\t// Write current content to temp file\n\t\tfs.writeFileSync(tmpFile, currentText, \"utf-8\");\n\n\t\t// Stop TUI to release terminal\n\t\thost.ui.stop();\n\n\t\tprocess.stdout.write(`Launching external editor: ${editorCmd}\\nPi will resume when the editor exits.\\n`);\n\n\t\t// Cross-platform executable resolution preserves quoted Windows paths without\n\t\t// asking a shell to reinterpret the temporary file path.\n\t\tconst status = await runExternalEditor(editorCmd, tmpFile);\n\n\t\t// On successful exit (status 0), replace editor content\n\t\tif (status === 0) {\n\t\t\tconst newContent = fs.readFileSync(tmpFile, \"utf-8\").replace(/\\n$/, \"\");\n\t\t\thost.editor.setText(newContent);\n\t\t}\n\t\t// On non-zero exit, keep original text (no action needed)\n\t} finally {\n\t\t// Clean up temp file\n\t\ttry {\n\t\t\tfs.unlinkSync(tmpFile);\n\t\t} catch {\n\t\t\t// Ignore cleanup errors\n\t\t}\n\n\t\t// Restart TUI\n\t\thost.ui.start();\n\t\t// Force full re-render since external editor uses alternate screen\n\t\thost.ui.requestRender(true);\n\t}\n}\n\nexport async function openEditorForPath(host: ExternalEditorHost, filePath: string): Promise<boolean> {\n\tlet editorCmd = resolveEditorCommand();\n\tlet isFallback = false;\n\tif (!editorCmd) {\n\t\teditorCmd = \"vi\";\n\t\tisFallback = true;\n\t}\n\n\ttry {\n\t\t// Stop TUI to release terminal\n\t\thost.ui.stop();\n\n\t\tprocess.stdout.write(\n\t\t\t`Launching external editor: ${editorCmd} ${filePath}\\nPi will resume when the editor exits.\\n`,\n\t\t);\n\n\t\tconst status = await runExternalEditor(editorCmd, filePath);\n\n\t\tif (status === null) {\n\t\t\tif (isFallback) {\n\t\t\t\tprocess.stdout.write(`\\nError: Failed to launch fallback editor \"vi\".\\n`);\n\t\t\t} else {\n\t\t\t\tprocess.stdout.write(`\\nError: Failed to launch editor \"${editorCmd}\".\\n`);\n\t\t\t}\n\t\t\tprocess.stdout.write(`Please set the $EDITOR or $VISUAL environment variable to edit inline.\\n`);\n\t\t\tprocess.stdout.write(`Absolute file path: ${filePath}\\n\\nPress Enter to return to Pi...`);\n\t\t\t// Wait for enter, but do not remain pending if stdin closes during shutdown.\n\t\t\tawait new Promise<void>((resolve) => {\n\t\t\t\tlet settled = false;\n\t\t\t\tconst finish = () => {\n\t\t\t\t\tif (settled) return;\n\t\t\t\t\tsettled = true;\n\t\t\t\t\tprocess.stdin.removeListener(\"data\", finish);\n\t\t\t\t\tprocess.stdin.removeListener(\"end\", finish);\n\t\t\t\t\tprocess.stdin.removeListener(\"error\", finish);\n\t\t\t\t\tresolve();\n\t\t\t\t};\n\t\t\t\tprocess.stdin.once(\"data\", finish);\n\t\t\t\tprocess.stdin.once(\"end\", finish);\n\t\t\t\tprocess.stdin.once(\"error\", finish);\n\t\t\t});\n\t\t}\n\n\t\treturn status === 0;\n\t} finally {\n\t\t// Restart TUI\n\t\thost.ui.start();\n\t\t// Force full re-render since external editor uses alternate screen\n\t\thost.ui.requestRender(true);\n\t}\n}\n"]}