@oh-my-pi/pi-coding-agent 15.10.10 → 15.10.12

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 (415) hide show
  1. package/CHANGELOG.md +142 -7
  2. package/dist/cli.js +23108 -0
  3. package/dist/tokenizers.linux-x64-gnu-xcjh3jwk.node +0 -0
  4. package/dist/types/async/job-manager.d.ts +18 -0
  5. package/dist/types/cli/args.d.ts +2 -1
  6. package/dist/types/cli/dry-balance-cli.d.ts +1 -1
  7. package/dist/types/cli/gallery-cli.d.ts +1 -1
  8. package/dist/types/cli/gallery-fixtures/types.d.ts +1 -1
  9. package/dist/types/cli/usage-cli.d.ts +72 -0
  10. package/dist/types/cli-commands.d.ts +12 -0
  11. package/dist/types/commands/launch.d.ts +5 -1
  12. package/dist/types/commands/read.d.ts +1 -1
  13. package/dist/types/commands/usage.d.ts +25 -0
  14. package/dist/types/config/api-key-resolver.d.ts +3 -0
  15. package/dist/types/config/append-only-context-mode.d.ts +2 -1
  16. package/dist/types/config/model-discovery.d.ts +55 -0
  17. package/dist/types/config/model-registry.d.ts +8 -219
  18. package/dist/types/config/model-resolver.d.ts +34 -10
  19. package/dist/types/config/model-roles.d.ts +28 -0
  20. package/dist/types/config/models-config-schema.d.ts +523 -42
  21. package/dist/types/config/models-config.d.ts +385 -0
  22. package/dist/types/config/settings-schema.d.ts +41 -8
  23. package/dist/types/config/settings.d.ts +8 -1
  24. package/dist/types/debug/log-viewer.d.ts +1 -1
  25. package/dist/types/debug/raw-sse.d.ts +1 -1
  26. package/dist/types/edit/hashline/noop-loop-guard.d.ts +72 -0
  27. package/dist/types/eval/backend.d.ts +0 -2
  28. package/dist/types/eval/idle-timeout.d.ts +0 -4
  29. package/dist/types/eval/js/shared/rewrite-imports.d.ts +6 -6
  30. package/dist/types/eval/py/executor.d.ts +5 -0
  31. package/dist/types/eval/py/kernel.d.ts +6 -1
  32. package/dist/types/eval/py/runtime.d.ts +9 -0
  33. package/dist/types/exec/bash-executor.d.ts +2 -0
  34. package/dist/types/export/html/template.generated.d.ts +1 -1
  35. package/dist/types/extensibility/extensions/runner.d.ts +3 -2
  36. package/dist/types/extensibility/extensions/types.d.ts +6 -3
  37. package/dist/types/hindsight/mental-models.d.ts +17 -8
  38. package/dist/types/internal-urls/artifact-protocol.d.ts +2 -2
  39. package/dist/types/internal-urls/types.d.ts +1 -1
  40. package/dist/types/lsp/edits.d.ts +9 -0
  41. package/dist/types/lsp/index.d.ts +2 -2
  42. package/dist/types/lsp/types.d.ts +2 -0
  43. package/dist/types/lsp/utils.d.ts +3 -0
  44. package/dist/types/mcp/json-rpc.d.ts +5 -0
  45. package/dist/types/memory-backend/index.d.ts +1 -0
  46. package/dist/types/memory-backend/runtime.d.ts +4 -0
  47. package/dist/types/memory-backend/types.d.ts +66 -1
  48. package/dist/types/mnemopi/state.d.ts +11 -1
  49. package/dist/types/modes/components/agent-dashboard.d.ts +1 -1
  50. package/dist/types/modes/components/assistant-message.d.ts +3 -1
  51. package/dist/types/modes/components/bash-execution.d.ts +1 -1
  52. package/dist/types/modes/components/copy-selector.d.ts +1 -1
  53. package/dist/types/modes/components/dynamic-border.d.ts +1 -1
  54. package/dist/types/modes/components/extensions/extension-dashboard.d.ts +1 -1
  55. package/dist/types/modes/components/extensions/extension-list.d.ts +1 -1
  56. package/dist/types/modes/components/extensions/inspector-panel.d.ts +1 -1
  57. package/dist/types/modes/components/footer.d.ts +1 -1
  58. package/dist/types/modes/components/hook-editor.d.ts +5 -0
  59. package/dist/types/modes/components/hook-input.d.ts +4 -0
  60. package/dist/types/modes/components/hook-selector.d.ts +1 -1
  61. package/dist/types/modes/components/model-selector.d.ts +1 -1
  62. package/dist/types/modes/components/plan-review-overlay.d.ts +1 -1
  63. package/dist/types/modes/components/session-observer-overlay.d.ts +1 -1
  64. package/dist/types/modes/components/session-selector.d.ts +1 -1
  65. package/dist/types/modes/components/status-line/component.d.ts +1 -1
  66. package/dist/types/modes/components/tiny-title-download-progress.d.ts +1 -1
  67. package/dist/types/modes/components/transcript-container.d.ts +25 -6
  68. package/dist/types/modes/components/tree-selector.d.ts +1 -1
  69. package/dist/types/modes/components/user-message-selector.d.ts +1 -1
  70. package/dist/types/modes/components/user-message.d.ts +2 -1
  71. package/dist/types/modes/components/visual-truncate.d.ts +1 -1
  72. package/dist/types/modes/components/welcome.d.ts +19 -3
  73. package/dist/types/modes/controllers/mcp-command-controller.d.ts +1 -1
  74. package/dist/types/modes/controllers/streaming-reveal.d.ts +1 -1
  75. package/dist/types/modes/index.d.ts +3 -3
  76. package/dist/types/modes/interactive-mode.d.ts +8 -3
  77. package/dist/types/modes/oauth-manual-input.d.ts +7 -0
  78. package/dist/types/modes/rpc/rpc-client.d.ts +39 -2
  79. package/dist/types/modes/rpc/rpc-mode.d.ts +31 -2
  80. package/dist/types/modes/rpc/rpc-subagents.d.ts +24 -0
  81. package/dist/types/modes/rpc/rpc-types.d.ts +75 -1
  82. package/dist/types/modes/setup-wizard/index.d.ts +5 -1
  83. package/dist/types/modes/setup-wizard/lazy.d.ts +2 -0
  84. package/dist/types/modes/setup-wizard/scenes/sign-in.d.ts +1 -1
  85. package/dist/types/modes/setup-wizard/scenes/types.d.ts +1 -1
  86. package/dist/types/modes/setup-wizard/scenes/web-search.d.ts +1 -1
  87. package/dist/types/modes/setup-wizard/wizard-overlay.d.ts +1 -1
  88. package/dist/types/modes/types.d.ts +4 -1
  89. package/dist/types/secrets/index.d.ts +1 -1
  90. package/dist/types/secrets/obfuscator.d.ts +8 -2
  91. package/dist/types/session/agent-session.d.ts +15 -3
  92. package/dist/types/session/auth-broker-config.d.ts +4 -0
  93. package/dist/types/session/session-manager.d.ts +1 -1
  94. package/dist/types/session/streaming-output.d.ts +23 -0
  95. package/dist/types/slash-commands/acp-builtins.d.ts +16 -0
  96. package/dist/types/slash-commands/builtin-registry.d.ts +1 -0
  97. package/dist/types/slash-commands/helpers/stats-dashboard.d.ts +13 -0
  98. package/dist/types/slash-commands/types.d.ts +1 -1
  99. package/dist/types/ssh/connection-manager.d.ts +8 -0
  100. package/dist/types/system-prompt.d.ts +2 -0
  101. package/dist/types/task/executor.d.ts +1 -0
  102. package/dist/types/task/index.d.ts +2 -2
  103. package/dist/types/task/parallel.d.ts +2 -2
  104. package/dist/types/task/types.d.ts +8 -0
  105. package/dist/types/task/worktree.d.ts +2 -0
  106. package/dist/types/thinking.d.ts +4 -0
  107. package/dist/types/tiny/title-client.d.ts +11 -0
  108. package/dist/types/tiny/title-protocol.d.ts +1 -0
  109. package/dist/types/tools/ask.d.ts +4 -0
  110. package/dist/types/tools/conflict-detect.d.ts +16 -0
  111. package/dist/types/tools/github-cache.d.ts +7 -0
  112. package/dist/types/tools/index.d.ts +6 -0
  113. package/dist/types/tools/sqlite-reader.d.ts +3 -0
  114. package/dist/types/tui/output-block.d.ts +3 -3
  115. package/dist/types/utils/changelog.d.ts +8 -0
  116. package/dist/types/utils/git.d.ts +15 -2
  117. package/dist/types/utils/title-generator.d.ts +3 -2
  118. package/dist/types/web/scrapers/readthedocs.d.ts +3 -0
  119. package/dist/types/web/scrapers/types.d.ts +12 -0
  120. package/dist/types/web/search/providers/codex.d.ts +1 -1
  121. package/dist/types/web/search/providers/gemini.d.ts +1 -1
  122. package/examples/extensions/tools.ts +5 -4
  123. package/package.json +14 -11
  124. package/scripts/build-binary.ts +18 -23
  125. package/scripts/bundle-dist.ts +81 -0
  126. package/scripts/{dev-launch → omp} +1 -1
  127. package/scripts/{dev-launch-preload.ts → omp.ts} +1 -1
  128. package/src/async/job-manager.ts +57 -3
  129. package/src/auto-thinking/classifier.ts +1 -0
  130. package/src/autoresearch/dashboard.ts +1 -1
  131. package/src/autoresearch/prompt-setup.md +6 -6
  132. package/src/autoresearch/prompt.md +6 -6
  133. package/src/capability/fs.ts +10 -0
  134. package/src/cli/args.ts +4 -1
  135. package/src/cli/auth-gateway-cli.ts +1 -3
  136. package/src/cli/dry-balance-cli.ts +1 -1
  137. package/src/cli/gallery-cli.ts +1 -1
  138. package/src/cli/gallery-fixtures/fs.ts +1 -1
  139. package/src/cli/gallery-fixtures/types.ts +5 -1
  140. package/src/cli/list-models.ts +2 -1
  141. package/src/cli/usage-cli.ts +603 -0
  142. package/src/cli-commands.ts +30 -0
  143. package/src/cli.ts +76 -13
  144. package/src/commands/complete.ts +1 -1
  145. package/src/commands/launch.ts +5 -1
  146. package/src/commands/read.ts +6 -3
  147. package/src/commands/usage.ts +35 -0
  148. package/src/commit/agentic/agent.ts +1 -1
  149. package/src/commit/model-selection.ts +4 -3
  150. package/src/config/api-key-resolver.ts +8 -6
  151. package/src/config/append-only-context-mode.ts +6 -12
  152. package/src/config/model-discovery.ts +554 -0
  153. package/src/config/model-registry.ts +320 -1041
  154. package/src/config/model-resolver.ts +173 -156
  155. package/src/config/model-roles.ts +74 -0
  156. package/src/config/models-config-schema.ts +57 -8
  157. package/src/config/models-config.ts +129 -0
  158. package/src/config/settings-schema.ts +61 -19
  159. package/src/config/settings.ts +98 -4
  160. package/src/dap/client.ts +124 -37
  161. package/src/dap/session.ts +259 -158
  162. package/src/debug/log-viewer.ts +1 -1
  163. package/src/debug/raw-sse.ts +1 -1
  164. package/src/edit/diff.ts +47 -3
  165. package/src/edit/hashline/block-resolver.ts +20 -1
  166. package/src/edit/hashline/diff.ts +36 -1
  167. package/src/edit/hashline/execute.ts +47 -4
  168. package/src/edit/hashline/noop-loop-guard.ts +99 -0
  169. package/src/edit/index.ts +16 -1
  170. package/src/edit/modes/patch.ts +52 -0
  171. package/src/edit/modes/replace.ts +56 -22
  172. package/src/edit/notebook.ts +22 -2
  173. package/src/edit/renderer.ts +36 -10
  174. package/src/eval/__tests__/completion-bridge.test.ts +1 -1
  175. package/src/eval/backend.ts +0 -2
  176. package/src/eval/completion-bridge.ts +3 -1
  177. package/src/eval/idle-timeout.ts +2 -9
  178. package/src/eval/js/context-manager.ts +6 -8
  179. package/src/eval/js/executor.ts +6 -2
  180. package/src/eval/js/index.ts +0 -2
  181. package/src/eval/js/shared/helpers.ts +5 -6
  182. package/src/eval/js/shared/local-module-loader.ts +1 -1
  183. package/src/eval/js/shared/prelude.txt +62 -1
  184. package/src/eval/js/shared/rewrite-imports.ts +40 -22
  185. package/src/eval/js/shared/runtime.ts +1 -1
  186. package/src/eval/py/executor.ts +29 -7
  187. package/src/eval/py/index.ts +6 -3
  188. package/src/eval/py/kernel.ts +43 -4
  189. package/src/eval/py/runner.py +107 -3
  190. package/src/eval/py/runtime.ts +37 -0
  191. package/src/exec/bash-executor.ts +85 -4
  192. package/src/export/html/template.generated.ts +1 -1
  193. package/src/export/html/template.js +3 -1
  194. package/src/extensibility/extensions/get-commands-handler.ts +2 -1
  195. package/src/extensibility/extensions/runner.ts +6 -1
  196. package/src/extensibility/extensions/types.ts +6 -2
  197. package/src/extensibility/plugins/legacy-pi-compat.ts +20 -3
  198. package/src/hindsight/bank.ts +17 -2
  199. package/src/hindsight/mental-models.ts +59 -12
  200. package/src/hindsight/state.ts +6 -1
  201. package/src/internal-urls/artifact-protocol.ts +11 -2
  202. package/src/internal-urls/docs-index.generated.ts +11 -11
  203. package/src/internal-urls/issue-pr-protocol.ts +12 -5
  204. package/src/internal-urls/router.ts +1 -1
  205. package/src/internal-urls/types.ts +1 -1
  206. package/src/lib/xai-http.ts +1 -1
  207. package/src/lsp/client.ts +118 -38
  208. package/src/lsp/clients/biome-client.ts +101 -39
  209. package/src/lsp/edits.ts +143 -95
  210. package/src/lsp/index.ts +31 -22
  211. package/src/lsp/render.ts +1 -1
  212. package/src/lsp/types.ts +2 -0
  213. package/src/lsp/utils.ts +28 -10
  214. package/src/main.ts +183 -23
  215. package/src/mcp/json-rpc.ts +35 -5
  216. package/src/mcp/transports/stdio.ts +7 -1
  217. package/src/memories/index.ts +4 -1
  218. package/src/memory-backend/index.ts +1 -0
  219. package/src/memory-backend/local-backend.ts +9 -0
  220. package/src/memory-backend/off-backend.ts +9 -0
  221. package/src/memory-backend/runtime.ts +66 -0
  222. package/src/memory-backend/types.ts +81 -1
  223. package/src/mnemopi/backend.ts +176 -7
  224. package/src/mnemopi/state.ts +38 -2
  225. package/src/modes/acp/acp-agent.ts +119 -11
  226. package/src/modes/components/agent-dashboard.ts +10 -7
  227. package/src/modes/components/assistant-message.ts +32 -28
  228. package/src/modes/components/bash-execution.ts +1 -1
  229. package/src/modes/components/copy-selector.ts +1 -1
  230. package/src/modes/components/diff.ts +13 -2
  231. package/src/modes/components/dynamic-border.ts +12 -3
  232. package/src/modes/components/extensions/extension-dashboard.ts +8 -5
  233. package/src/modes/components/extensions/extension-list.ts +1 -1
  234. package/src/modes/components/extensions/inspector-panel.ts +1 -1
  235. package/src/modes/components/footer.ts +4 -2
  236. package/src/modes/components/history-search.ts +1 -1
  237. package/src/modes/components/hook-editor.ts +8 -0
  238. package/src/modes/components/hook-input.ts +8 -0
  239. package/src/modes/components/hook-selector.ts +2 -2
  240. package/src/modes/components/model-selector.ts +4 -2
  241. package/src/modes/components/plan-review-overlay.ts +1 -1
  242. package/src/modes/components/session-observer-overlay.ts +2 -2
  243. package/src/modes/components/session-selector.ts +1 -1
  244. package/src/modes/components/settings-selector.ts +5 -1
  245. package/src/modes/components/status-line/component.ts +119 -35
  246. package/src/modes/components/tiny-title-download-progress.ts +1 -1
  247. package/src/modes/components/transcript-container.ts +258 -53
  248. package/src/modes/components/tree-selector.ts +3 -3
  249. package/src/modes/components/user-message-selector.ts +1 -1
  250. package/src/modes/components/user-message.ts +17 -5
  251. package/src/modes/components/visual-truncate.ts +1 -1
  252. package/src/modes/components/welcome.ts +108 -26
  253. package/src/modes/controllers/command-controller.ts +11 -4
  254. package/src/modes/controllers/event-controller.ts +73 -4
  255. package/src/modes/controllers/input-controller.ts +2 -1
  256. package/src/modes/controllers/mcp-command-controller.ts +39 -4
  257. package/src/modes/controllers/selector-controller.ts +1 -1
  258. package/src/modes/controllers/streaming-reveal.ts +85 -18
  259. package/src/modes/index.ts +3 -21
  260. package/src/modes/interactive-mode.ts +42 -18
  261. package/src/modes/oauth-manual-input.ts +30 -3
  262. package/src/modes/rpc/rpc-client.ts +154 -3
  263. package/src/modes/rpc/rpc-mode.ts +97 -12
  264. package/src/modes/rpc/rpc-subagents.ts +265 -0
  265. package/src/modes/rpc/rpc-types.ts +81 -1
  266. package/src/modes/setup-wizard/index.ts +12 -2
  267. package/src/modes/setup-wizard/lazy.ts +16 -0
  268. package/src/modes/setup-wizard/scenes/glyph.ts +1 -1
  269. package/src/modes/setup-wizard/scenes/providers.ts +1 -1
  270. package/src/modes/setup-wizard/scenes/sign-in.ts +1 -1
  271. package/src/modes/setup-wizard/scenes/theme.ts +1 -1
  272. package/src/modes/setup-wizard/scenes/types.ts +1 -1
  273. package/src/modes/setup-wizard/scenes/web-search.ts +1 -1
  274. package/src/modes/setup-wizard/wizard-overlay.ts +1 -1
  275. package/src/modes/types.ts +4 -1
  276. package/src/prompts/agents/explore.md +2 -2
  277. package/src/prompts/agents/librarian.md +1 -2
  278. package/src/prompts/agents/oracle.md +1 -1
  279. package/src/prompts/agents/plan.md +5 -5
  280. package/src/prompts/agents/task.md +5 -5
  281. package/src/prompts/ci-green-request.md +5 -7
  282. package/src/prompts/goals/goal-budget-limit.md +2 -2
  283. package/src/prompts/goals/goal-continuation.md +4 -4
  284. package/src/prompts/goals/goal-mode-active.md +1 -1
  285. package/src/prompts/memories/read-path.md +1 -1
  286. package/src/prompts/memories/stage_one_system.md +2 -2
  287. package/src/prompts/review-custom-request.md +1 -1
  288. package/src/prompts/system/agent-creation-architect.md +2 -2
  289. package/src/prompts/system/auto-continue.md +1 -1
  290. package/src/prompts/system/background-tan-dispatch.md +1 -1
  291. package/src/prompts/system/btw-user.md +2 -2
  292. package/src/prompts/system/commit-message-system.md +13 -1
  293. package/src/prompts/system/custom-system-prompt.md +1 -1
  294. package/src/prompts/system/eager-todo.md +2 -2
  295. package/src/prompts/system/irc-incoming.md +1 -1
  296. package/src/prompts/system/manual-continue.md +1 -1
  297. package/src/prompts/system/omfg-user.md +3 -4
  298. package/src/prompts/system/orchestrate-notice.md +9 -9
  299. package/src/prompts/system/plan-mode-active.md +4 -4
  300. package/src/prompts/system/plan-mode-subagent.md +4 -5
  301. package/src/prompts/system/plan-mode-tool-decision-reminder.md +1 -1
  302. package/src/prompts/system/project-prompt.md +2 -2
  303. package/src/prompts/system/subagent-system-prompt.md +4 -4
  304. package/src/prompts/system/system-prompt.md +13 -24
  305. package/src/prompts/system/title-system.md +2 -2
  306. package/src/prompts/system/ttsr-tool-reminder.md +1 -1
  307. package/src/prompts/system/workflow-notice.md +1 -1
  308. package/src/prompts/tools/ast-edit.md +1 -1
  309. package/src/prompts/tools/ast-grep.md +2 -2
  310. package/src/prompts/tools/bash.md +5 -7
  311. package/src/prompts/tools/browser.md +7 -7
  312. package/src/prompts/tools/debug.md +1 -1
  313. package/src/prompts/tools/eval.md +3 -3
  314. package/src/prompts/tools/find.md +0 -1
  315. package/src/prompts/tools/github.md +8 -7
  316. package/src/prompts/tools/goal.md +1 -1
  317. package/src/prompts/tools/image-gen.md +1 -1
  318. package/src/prompts/tools/inspect-image-system.md +1 -1
  319. package/src/prompts/tools/irc.md +15 -15
  320. package/src/prompts/tools/lsp.md +2 -2
  321. package/src/prompts/tools/patch.md +2 -2
  322. package/src/prompts/tools/read.md +3 -4
  323. package/src/prompts/tools/recall.md +1 -1
  324. package/src/prompts/tools/reflect.md +1 -1
  325. package/src/prompts/tools/render-mermaid.md +2 -2
  326. package/src/prompts/tools/replace.md +4 -10
  327. package/src/prompts/tools/rewind.md +2 -2
  328. package/src/prompts/tools/search-tool-bm25.md +1 -9
  329. package/src/prompts/tools/search.md +0 -1
  330. package/src/prompts/tools/ssh.md +0 -4
  331. package/src/prompts/tools/task.md +2 -3
  332. package/src/prompts/tools/todo.md +1 -1
  333. package/src/sdk.ts +31 -11
  334. package/src/secrets/index.ts +8 -1
  335. package/src/secrets/obfuscator.ts +39 -18
  336. package/src/session/agent-session.ts +223 -64
  337. package/src/session/auth-broker-config.ts +30 -1
  338. package/src/session/session-manager.ts +2 -2
  339. package/src/session/streaming-output.ts +188 -11
  340. package/src/slash-commands/acp-builtins.ts +24 -0
  341. package/src/slash-commands/builtin-registry.ts +40 -0
  342. package/src/slash-commands/helpers/stats-dashboard.ts +85 -0
  343. package/src/slash-commands/types.ts +1 -1
  344. package/src/ssh/connection-manager.ts +27 -0
  345. package/src/system-prompt.ts +14 -0
  346. package/src/task/commands.ts +2 -1
  347. package/src/task/executor.ts +74 -65
  348. package/src/task/index.ts +146 -68
  349. package/src/task/parallel.ts +3 -3
  350. package/src/task/render.ts +20 -5
  351. package/src/task/types.ts +9 -0
  352. package/src/task/worktree.ts +64 -56
  353. package/src/thinking.ts +9 -1
  354. package/src/tiny/title-client.ts +60 -16
  355. package/src/tiny/title-protocol.ts +1 -1
  356. package/src/tiny/worker.ts +6 -4
  357. package/src/tools/archive-reader.ts +30 -2
  358. package/src/tools/ask.ts +104 -21
  359. package/src/tools/ast-edit.ts +25 -5
  360. package/src/tools/auto-generated-guard.ts +20 -3
  361. package/src/tools/bash-interactive.ts +27 -7
  362. package/src/tools/bash.ts +100 -18
  363. package/src/tools/browser/launch.ts +11 -2
  364. package/src/tools/browser/readable.ts +19 -2
  365. package/src/tools/browser/registry.ts +4 -1
  366. package/src/tools/browser/render.ts +2 -2
  367. package/src/tools/browser/tab-supervisor.ts +55 -16
  368. package/src/tools/conflict-detect.ts +50 -4
  369. package/src/tools/debug.ts +1 -1
  370. package/src/tools/eval-render.ts +5 -5
  371. package/src/tools/eval.ts +0 -2
  372. package/src/tools/fetch.ts +33 -10
  373. package/src/tools/gh-cache-invalidation.ts +63 -8
  374. package/src/tools/gh-renderer.ts +1 -1
  375. package/src/tools/gh.ts +172 -29
  376. package/src/tools/github-cache.ts +70 -6
  377. package/src/tools/image-gen.ts +14 -13
  378. package/src/tools/index.ts +13 -1
  379. package/src/tools/inspect-image.ts +1 -0
  380. package/src/tools/irc.ts +5 -1
  381. package/src/tools/job.ts +1 -1
  382. package/src/tools/read.ts +202 -61
  383. package/src/tools/render-utils.ts +3 -3
  384. package/src/tools/resolve.ts +1 -1
  385. package/src/tools/search.ts +92 -29
  386. package/src/tools/sqlite-reader.ts +17 -5
  387. package/src/tools/ssh.ts +8 -8
  388. package/src/tools/todo.ts +38 -8
  389. package/src/tools/write.ts +118 -18
  390. package/src/tui/output-block.ts +4 -4
  391. package/src/utils/changelog.ts +27 -1
  392. package/src/utils/commit-message-generator.ts +1 -0
  393. package/src/utils/file-mentions.ts +2 -1
  394. package/src/utils/git.ts +267 -13
  395. package/src/utils/title-generator.ts +24 -5
  396. package/src/web/scrapers/arxiv.ts +1 -1
  397. package/src/web/scrapers/go-pkg.ts +1 -1
  398. package/src/web/scrapers/iacr.ts +1 -1
  399. package/src/web/scrapers/readthedocs.ts +1 -1
  400. package/src/web/scrapers/twitter.ts +2 -1
  401. package/src/web/scrapers/types.ts +87 -8
  402. package/src/web/scrapers/wikipedia.ts +1 -1
  403. package/src/web/scrapers/youtube.ts +6 -1
  404. package/src/web/search/index.ts +1 -1
  405. package/src/web/search/providers/codex.ts +2 -1
  406. package/src/web/search/providers/gemini.ts +2 -3
  407. package/src/web/search/render.ts +8 -6
  408. package/dist/types/config/model-equivalence.d.ts +0 -24
  409. package/dist/types/config/model-id-affixes.d.ts +0 -12
  410. package/dist/types/config/model-provider-priority.d.ts +0 -1
  411. package/dist/types/exec/idle-timeout-watchdog.d.ts +0 -18
  412. package/src/config/model-equivalence.ts +0 -875
  413. package/src/config/model-id-affixes.ts +0 -81
  414. package/src/config/model-provider-priority.ts +0 -56
  415. package/src/exec/idle-timeout-watchdog.ts +0 -126
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Memory backend abstraction.
3
3
  *
4
- * Backends are mutually exclusive — `await resolveMemoryBackend(settings)` resolves
4
+ * Backends are mutually exclusive — `await resolveMemoryBackend(settings)` returns
5
5
  * exactly one. Implementations MUST be self-contained: they own the per-session
6
6
  * state they create in `start()` and tear it down on `clear()`.
7
7
  */
@@ -15,6 +15,73 @@ import type { AgentSession } from "../session/agent-session";
15
15
 
16
16
  export type MemoryBackendId = "off" | "local" | "hindsight" | "mnemopi";
17
17
 
18
+ export interface MemoryBackendStatus {
19
+ backend: MemoryBackendId;
20
+ active: boolean;
21
+ writable: boolean;
22
+ searchable: boolean;
23
+ scope?: string;
24
+ retainBank?: string;
25
+ recallBanks?: string[];
26
+ workingCount?: number;
27
+ episodicCount?: number;
28
+ tripleCount?: number;
29
+ lastMemory?: string;
30
+ lastRecall?: boolean;
31
+ database?: string;
32
+ message?: string;
33
+ error?: string;
34
+ }
35
+
36
+ export interface MemoryBackendSearchOptions {
37
+ limit?: number;
38
+ /** Best-effort abort signal. Backends may only observe it before/after an underlying recall call. */
39
+ signal?: AbortSignal;
40
+ }
41
+
42
+ export interface MemoryBackendSearchItem {
43
+ id?: string;
44
+ content: string;
45
+ source?: string;
46
+ timestamp?: string;
47
+ score?: number;
48
+ }
49
+
50
+ export interface MemoryBackendSearchResult {
51
+ backend: MemoryBackendId;
52
+ query: string;
53
+ count: number;
54
+ items: MemoryBackendSearchItem[];
55
+ message?: string;
56
+ }
57
+
58
+ export interface MemoryBackendSaveInput {
59
+ content: string;
60
+ context?: string;
61
+ source?: string;
62
+ importance?: number;
63
+ }
64
+
65
+ export interface MemoryBackendSaveResult {
66
+ backend: MemoryBackendId;
67
+ stored: number;
68
+ ids?: string[];
69
+ queued?: boolean;
70
+ message?: string;
71
+ }
72
+
73
+ export interface MemoryBackendOperationContext {
74
+ agentDir: string;
75
+ cwd: string;
76
+ session?: AgentSession;
77
+ }
78
+
79
+ export interface MemoryRuntimeContext {
80
+ status(): Promise<MemoryBackendStatus>;
81
+ search(query: string, options?: MemoryBackendSearchOptions): Promise<MemoryBackendSearchResult>;
82
+ save(input: string | MemoryBackendSaveInput): Promise<MemoryBackendSaveResult>;
83
+ }
84
+
18
85
  export interface MemoryBackendStartOptions {
19
86
  session: AgentSession;
20
87
  settings: Settings;
@@ -53,6 +120,19 @@ export interface MemoryBackend {
53
120
  /** Force consolidation/retain to happen now (slash `/memory enqueue`). */
54
121
  enqueue(agentDir: string, cwd: string, session?: AgentSession): Promise<void>;
55
122
 
123
+ /** Structured state for UI, slash commands, and extensions. */
124
+ status?(context: MemoryBackendOperationContext): Promise<MemoryBackendStatus>;
125
+
126
+ /** Explicit user-facing semantic/lexical search. */
127
+ search?(
128
+ context: MemoryBackendOperationContext,
129
+ query: string,
130
+ options?: MemoryBackendSearchOptions,
131
+ ): Promise<MemoryBackendSearchResult>;
132
+
133
+ /** Explicit user-facing save operation. */
134
+ save?(context: MemoryBackendOperationContext, input: MemoryBackendSaveInput): Promise<MemoryBackendSaveResult>;
135
+
56
136
  /** Render backend-specific memory statistics as markdown (`/memory stats`). */
57
137
  stats?(agentDir: string, cwd: string, session?: AgentSession): Promise<string | undefined>;
58
138
 
@@ -1,14 +1,19 @@
1
1
  import { rm } from "node:fs/promises";
2
2
  import * as path from "node:path";
3
3
  import { completeSimple } from "@oh-my-pi/pi-ai";
4
- import { Mnemopi } from "@oh-my-pi/pi-mnemopi";
5
- import { BankManager } from "@oh-my-pi/pi-mnemopi/core";
6
- import { type DiagnosticSummary, inspectDatabase } from "@oh-my-pi/pi-mnemopi/diagnose";
4
+ import type { Mnemopi } from "@oh-my-pi/pi-mnemopi";
5
+ import type * as MnemopiDiagnoseNs from "@oh-my-pi/pi-mnemopi/diagnose";
6
+ import type { DiagnosticSummary } from "@oh-my-pi/pi-mnemopi/diagnose";
7
7
  import { logger } from "@oh-my-pi/pi-utils";
8
-
9
8
  import type { ModelRegistry } from "../config/model-registry";
10
9
  import { resolveRoleSelection } from "../config/model-resolver";
11
- import type { MemoryBackend, MemoryBackendStartOptions } from "../memory-backend/types";
10
+ import type {
11
+ MemoryBackend,
12
+ MemoryBackendSaveInput,
13
+ MemoryBackendSearchItem,
14
+ MemoryBackendStartOptions,
15
+ MemoryBackendStatus,
16
+ } from "../memory-backend/types";
12
17
  import memoryConsolidationPrompt from "../prompts/system/memory-consolidation-system.md" with { type: "text" };
13
18
  import memoryExtractionPrompt from "../prompts/system/memory-extraction-system.md" with { type: "text" };
14
19
  import type { AgentSession } from "../session/agent-session";
@@ -25,10 +30,25 @@ import {
25
30
  getMnemopiScopedBanks,
26
31
  getMnemopiScopedDbPaths,
27
32
  getMnemopiSessionState,
33
+ loadMnemopi,
34
+ loadMnemopiCore,
28
35
  MnemopiSessionState,
36
+ requireMnemopi,
37
+ requireMnemopiCore,
29
38
  setMnemopiSessionState,
30
39
  } from "./state";
31
40
 
41
+ // `/diagnose` is the only user of this subpath; load it lazily alongside the
42
+ // loaders in ./state to keep mnemopi off the CLI startup module graph.
43
+ let mnemopiDiagnoseMod: typeof MnemopiDiagnoseNs | undefined;
44
+
45
+ async function loadMnemopiDiagnose(): Promise<typeof MnemopiDiagnoseNs> {
46
+ if (!mnemopiDiagnoseMod) {
47
+ mnemopiDiagnoseMod = await import("@oh-my-pi/pi-mnemopi/diagnose");
48
+ }
49
+ return mnemopiDiagnoseMod;
50
+ }
51
+
32
52
  const STATIC_INSTRUCTIONS = [
33
53
  "# Memory",
34
54
  "This agent has local Mnemopi long-term memory.",
@@ -68,6 +88,7 @@ export const mnemopiBackend: MemoryBackend = {
68
88
 
69
89
  try {
70
90
  const config = await loadMnemopiConfigWithProviders(settings, agentDir, modelRegistry, sessionId);
91
+ await Promise.all([loadMnemopi(), loadMnemopiCore()]);
71
92
  const state = new MnemopiSessionState({ sessionId, config, session });
72
93
  const previous = setMnemopiSessionState(session, state);
73
94
  previous?.dispose();
@@ -97,6 +118,7 @@ export const mnemopiBackend: MemoryBackend = {
97
118
  previous?.dispose();
98
119
  const config = previous?.config ?? (session ? loadMnemopiConfig(session.settings, agentDir) : undefined);
99
120
  if (!config) return;
121
+ await loadMnemopiCore();
100
122
  await removeDbFiles(getMnemopiScopedDbPaths(config));
101
123
  },
102
124
 
@@ -110,6 +132,7 @@ export const mnemopiBackend: MemoryBackend = {
110
132
  session.modelRegistry,
111
133
  session.sessionId,
112
134
  );
135
+ await Promise.all([loadMnemopi(), loadMnemopiCore()]);
113
136
  state = new MnemopiSessionState({ sessionId: session.sessionId, config, session });
114
137
  setMnemopiSessionState(session, state);
115
138
  }
@@ -124,6 +147,7 @@ export const mnemopiBackend: MemoryBackend = {
124
147
  },
125
148
 
126
149
  async stats(agentDir, _cwd, session): Promise<string | undefined> {
150
+ await Promise.all([loadMnemopi(), loadMnemopiCore()]);
127
151
  const { targets, owned } = createStatsTargets(agentDir, session);
128
152
  try {
129
153
  if (targets.length === 0) return undefined;
@@ -137,6 +161,7 @@ export const mnemopiBackend: MemoryBackend = {
137
161
  const state = getMnemopiSessionState(session);
138
162
  const config = state?.config ?? (session ? loadMnemopiConfig(session.settings, agentDir) : undefined);
139
163
  if (!config) return undefined;
164
+ const [{ inspectDatabase }] = await Promise.all([loadMnemopiDiagnose(), loadMnemopiCore()]);
140
165
  const banks = getMnemopiScopedBanks(config);
141
166
  const dbPaths = getMnemopiScopedDbPaths(config);
142
167
  const summaries = dbPaths.map((dbPath, index) => ({
@@ -146,6 +171,101 @@ export const mnemopiBackend: MemoryBackend = {
146
171
  return renderMnemopiDiagnostics(summaries);
147
172
  },
148
173
 
174
+ async status({ agentDir, session }): Promise<MemoryBackendStatus> {
175
+ const state = getMnemopiSessionState(session);
176
+ const primary = state?.aliasOf ?? state;
177
+ if (!primary) {
178
+ return {
179
+ backend: "mnemopi",
180
+ active: false,
181
+ writable: false,
182
+ searchable: false,
183
+ message: "Mnemopi backend is not initialised for this session.",
184
+ };
185
+ }
186
+
187
+ const { targets, owned } = createStatsTargets(agentDir, session);
188
+ try {
189
+ if (targets.length === 0) {
190
+ return {
191
+ backend: "mnemopi",
192
+ active: false,
193
+ writable: false,
194
+ searchable: false,
195
+ message: "Mnemopi backend is configured but not initialised for this session.",
196
+ };
197
+ }
198
+ return summarizeMnemopiStatus(targets, session);
199
+ } finally {
200
+ for (const memory of owned) memory.close();
201
+ }
202
+ },
203
+
204
+ async search({ session }, query, options) {
205
+ const state = getMnemopiSessionState(session);
206
+ const primary = state?.aliasOf ?? state;
207
+ if (!primary) {
208
+ return {
209
+ backend: "mnemopi",
210
+ query,
211
+ count: 0,
212
+ items: [],
213
+ message: "Mnemopi backend is not initialised for this session.",
214
+ };
215
+ }
216
+ if (options?.signal?.aborted) {
217
+ return { backend: "mnemopi", query, count: 0, items: [], message: "Search aborted." };
218
+ }
219
+ const limit = clampLimit(options?.limit);
220
+ const results = (await primary.recallResultsScoped(query)).slice(0, limit);
221
+ if (options?.signal?.aborted) {
222
+ return { backend: "mnemopi", query, count: 0, items: [], message: "Search aborted." };
223
+ }
224
+ const items: MemoryBackendSearchItem[] = results.map(result => ({
225
+ id: result.id,
226
+ content: result.content,
227
+ source: result.source ?? undefined,
228
+ timestamp: result.timestamp ?? undefined,
229
+ score: result.score,
230
+ }));
231
+ return { backend: "mnemopi", query, count: items.length, items };
232
+ },
233
+
234
+ async save({ cwd, session }, input: MemoryBackendSaveInput) {
235
+ const state = getMnemopiSessionState(session);
236
+ const primary = state?.aliasOf ?? state;
237
+ if (!primary) {
238
+ return {
239
+ backend: "mnemopi",
240
+ stored: 0,
241
+ message: "Mnemopi backend is not initialised for this session.",
242
+ };
243
+ }
244
+ const content = input.content.trim();
245
+ if (!content) return { backend: "mnemopi", stored: 0, message: "Memory content is empty." };
246
+ const id = primary.rememberScoped(content, {
247
+ source: input.source || "coding-agent-memory-command",
248
+ importance: normalizeImportance(input.importance),
249
+ metadata: {
250
+ session_id: primary.sessionId,
251
+ cwd,
252
+ context: input.context ?? null,
253
+ operation: "memory.save",
254
+ },
255
+ scope: "bank",
256
+ extract: true,
257
+ extractEntities: true,
258
+ veracity: "user",
259
+ memoryType: "fact",
260
+ });
261
+ return {
262
+ backend: "mnemopi",
263
+ stored: id ? 1 : 0,
264
+ ids: id ? [id] : [],
265
+ message: id ? undefined : "Mnemopi did not return a stored memory id.",
266
+ };
267
+ },
268
+
149
269
  async preCompactionContext(messages, _settings, session): Promise<string | undefined> {
150
270
  const state = getMnemopiSessionState(session);
151
271
  return await state?.recallForCompaction(messages);
@@ -179,6 +299,7 @@ function createStatsTargets(
179
299
 
180
300
  function createStatsMemory(config: MnemopiBackendConfig, bank: string): Mnemopi {
181
301
  const providerOptions = config.providerOptions as Record<string, unknown>;
302
+ const { Mnemopi } = requireMnemopi();
182
303
  return new Mnemopi({
183
304
  dbPath: resolveBankDbPath(config, bank),
184
305
  bank,
@@ -193,6 +314,7 @@ function createStatsMemory(config: MnemopiBackendConfig, bank: string): Mnemopi
193
314
  function resolveBankDbPath(config: MnemopiBackendConfig, bank: string): string {
194
315
  const sharedBank = config.globalBank ?? config.baseBank ?? "default";
195
316
  if (bank === sharedBank) return config.dbPath;
317
+ const { BankManager } = requireMnemopiCore();
196
318
  return new BankManager(path.dirname(config.dbPath)).getBankDbPath(bank);
197
319
  }
198
320
 
@@ -225,6 +347,52 @@ function renderMnemopiStats(targets: readonly MnemopiStatsTarget[]): string {
225
347
  return lines.join("\n");
226
348
  }
227
349
 
350
+ function summarizeMnemopiStatus(
351
+ targets: readonly MnemopiStatsTarget[],
352
+ session: AgentSession | undefined,
353
+ ): MemoryBackendStatus {
354
+ let workingCount = 0;
355
+ let episodicCount = 0;
356
+ let tripleCount = 0;
357
+ let lastMemory: string | undefined;
358
+ let database: string | undefined;
359
+ for (const target of targets) {
360
+ const stats = target.memory.getStats();
361
+ workingCount += statCount(stats.beam.working_memory);
362
+ episodicCount += statCount(stats.beam.episodic_memory);
363
+ tripleCount += stats.beam.triples.total;
364
+ lastMemory ??= stats.last_memory ?? undefined;
365
+ database ??= stats.database ? shortenPath(stats.database) : undefined;
366
+ }
367
+ const state = getMnemopiSessionState(session);
368
+ const primary = state?.aliasOf ?? state;
369
+ return {
370
+ backend: "mnemopi",
371
+ active: true,
372
+ writable: true,
373
+ searchable: true,
374
+ scope: primary?.config.scoping,
375
+ retainBank: primary?.getScopedRetainTarget().bank ?? targets[0]?.bank,
376
+ recallBanks: primary?.getScopedRecallTargets().map(target => target.bank) ?? targets.map(target => target.bank),
377
+ workingCount,
378
+ episodicCount,
379
+ tripleCount,
380
+ lastMemory,
381
+ lastRecall: Boolean(primary?.lastRecallSnippet),
382
+ database,
383
+ };
384
+ }
385
+
386
+ function clampLimit(limit: number | undefined): number {
387
+ if (!Number.isFinite(limit)) return 10;
388
+ return Math.max(1, Math.min(50, Math.trunc(limit ?? 10)));
389
+ }
390
+
391
+ function normalizeImportance(value: number | undefined): number {
392
+ if (!Number.isFinite(value)) return 0.75;
393
+ return Math.max(0, Math.min(1, value ?? 0.75));
394
+ }
395
+
228
396
  function renderMnemopiDiagnostics(entries: readonly { bank: string; summary: DiagnosticSummary }[]): string {
229
397
  const lines = [
230
398
  "# Mnemopi Memory Diagnostics",
@@ -321,8 +489,8 @@ async function resolveMnemopiProviderOptions(
321
489
  return {
322
490
  ...base,
323
491
  llm: async (prompt, opts) => {
324
- const apiKey = await modelRegistry.getApiKey(model, sessionId);
325
- if (!apiKey) {
492
+ const hasApiKey = await modelRegistry.getApiKey(model, sessionId);
493
+ if (!hasApiKey) {
326
494
  logger.warn("Mnemopi: smol completion requested but no current API key is available.", {
327
495
  provider: model.provider,
328
496
  model: model.id,
@@ -338,6 +506,7 @@ async function resolveMnemopiProviderOptions(
338
506
  apiKey: modelRegistry.resolver(model.provider, {
339
507
  sessionId,
340
508
  baseUrl: model.baseUrl,
509
+ modelId: model.id,
341
510
  }),
342
511
  maxTokens: opts?.maxTokens,
343
512
  temperature: opts?.temperature,
@@ -1,7 +1,8 @@
1
1
  import { dirname } from "node:path";
2
2
  import type { AgentMessage } from "@oh-my-pi/pi-agent-core";
3
- import { Mnemopi, type RecallResult } from "@oh-my-pi/pi-mnemopi";
4
- import { BankManager } from "@oh-my-pi/pi-mnemopi/core";
3
+ import type * as MnemopiNs from "@oh-my-pi/pi-mnemopi";
4
+ import type { Mnemopi, RecallResult } from "@oh-my-pi/pi-mnemopi";
5
+ import type * as MnemopiCoreNs from "@oh-my-pi/pi-mnemopi/core";
5
6
  import { logger } from "@oh-my-pi/pi-utils";
6
7
  import {
7
8
  composeRecallQuery,
@@ -13,6 +14,39 @@ import { extractMessages } from "../hindsight/transcript";
13
14
  import type { AgentSession, AgentSessionEvent } from "../session/agent-session";
14
15
  import type { MnemopiBackendConfig, MnemopiScoping } from "./config";
15
16
 
17
+ // The mnemopi package pulls the embeddings stack; keep it off the CLI startup
18
+ // module graph by loading it lazily at the async boundaries that need it.
19
+ let mnemopiMod: typeof MnemopiNs | undefined;
20
+ let mnemopiCoreMod: typeof MnemopiCoreNs | undefined;
21
+
22
+ /** Lazily load `@oh-my-pi/pi-mnemopi` (memoized). */
23
+ export async function loadMnemopi(): Promise<typeof MnemopiNs> {
24
+ if (!mnemopiMod) {
25
+ mnemopiMod = await import("@oh-my-pi/pi-mnemopi");
26
+ }
27
+ return mnemopiMod;
28
+ }
29
+
30
+ /** Lazily load `@oh-my-pi/pi-mnemopi/core` (memoized). */
31
+ export async function loadMnemopiCore(): Promise<typeof MnemopiCoreNs> {
32
+ if (!mnemopiCoreMod) {
33
+ mnemopiCoreMod = await import("@oh-my-pi/pi-mnemopi/core");
34
+ }
35
+ return mnemopiCoreMod;
36
+ }
37
+
38
+ /** Sync access for code below an async boundary that already awaited {@link loadMnemopi}. */
39
+ export function requireMnemopi(): typeof MnemopiNs {
40
+ if (!mnemopiMod) throw new Error("Mnemopi module not loaded; await loadMnemopi() first.");
41
+ return mnemopiMod;
42
+ }
43
+
44
+ /** Sync access for code below an async boundary that already awaited {@link loadMnemopiCore}. */
45
+ export function requireMnemopiCore(): typeof MnemopiCoreNs {
46
+ if (!mnemopiCoreMod) throw new Error("Mnemopi core module not loaded; await loadMnemopiCore() first.");
47
+ return mnemopiCoreMod;
48
+ }
49
+
16
50
  const kMnemopiSessionState = Symbol("mnemopi.sessionState");
17
51
 
18
52
  interface AgentSessionWithMnemopiState extends AgentSession {
@@ -460,6 +494,7 @@ function escapeRegExp(text: string): string {
460
494
  }
461
495
  function createMemory(config: MnemopiBackendConfig, bank: string): Mnemopi {
462
496
  const providerOptions = config.providerOptions as Record<string, unknown>;
497
+ const { Mnemopi } = requireMnemopi();
463
498
  return new Mnemopi({
464
499
  dbPath: resolveBankDbPath(config, bank),
465
500
  bank,
@@ -474,6 +509,7 @@ function createMemory(config: MnemopiBackendConfig, bank: string): Mnemopi {
474
509
  function resolveBankDbPath(config: MnemopiBackendConfig, bank: string): string {
475
510
  const sharedBank = config.globalBank ?? config.baseBank ?? "default";
476
511
  if (bank === sharedBank) return config.dbPath;
512
+ const { BankManager } = requireMnemopiCore();
477
513
  return new BankManager(dirname(config.dbPath)).getBankDbPath(bank);
478
514
  }
479
515
 
@@ -71,7 +71,12 @@ import {
71
71
  type SessionInfo as StoredSessionInfo,
72
72
  type UsageStatistics,
73
73
  } from "../../session/session-manager";
74
- import { ACP_BUILTIN_SLASH_COMMANDS, executeAcpBuiltinSlashCommand } from "../../slash-commands/acp-builtins";
74
+ import {
75
+ ACP_BUILTIN_RESERVED_NAMES,
76
+ ACP_BUILTIN_SLASH_COMMANDS,
77
+ executeAcpBuiltinSlashCommand,
78
+ isAcpBuiltinShadowedName,
79
+ } from "../../slash-commands/acp-builtins";
75
80
  import { AUTO_THINKING, parseConfiguredThinkingLevel } from "../../thinking";
76
81
  import { normalizeLocalScheme } from "../../tools/path-utils";
77
82
  import { runResolveInvocation } from "../../tools/resolve";
@@ -117,6 +122,7 @@ type PromptQueueState = {
117
122
  promise: Promise<void>;
118
123
  release: (() => void) | undefined;
119
124
  };
125
+ type PromptLifecycleError = Error & { readonly code: "ACP_SESSION_CLOSED" };
120
126
 
121
127
  type PromptTurnState = {
122
128
  userMessageId: string;
@@ -158,6 +164,9 @@ type ManagedSessionRecord = {
158
164
  // Installed inside `#scheduleBootstrapUpdates` (post-race-guard); released
159
165
  // in `#disposeSessionRecord`. Lives independent of any prompt turn.
160
166
  lifetimeUnsubscribe: (() => void) | undefined;
167
+ closedError: PromptLifecycleError | undefined;
168
+ promptEventHandlers: Set<Promise<void>>;
169
+ extensionUserMessageTasks: Set<Promise<void>>;
161
170
  };
162
171
 
163
172
  type ReplayableMessage = {
@@ -594,7 +603,23 @@ export class AcpAgent implements Agent {
594
603
  const record = this.#getSessionRecord(params.sessionId);
595
604
  const activeTurn = record.promptTurn;
596
605
  if (activeTurn && !activeTurn.settled && record.session.isStreaming) {
597
- throw new Error("ACP prompt already in progress for this session");
606
+ // New prompt arrived while the previous turn is still in-flight (e.g. the
607
+ // client sent a message immediately after pressing stop, before or without
608
+ // a preceding session/cancel notification). Implicitly cancel the running
609
+ // turn so the new prompt can queue behind the abort cleanup — identical to
610
+ // what cancel() does when called explicitly. #beginCancelCleanup is
611
+ // idempotent, so a concurrent session/cancel notification is harmless.
612
+ // Mirror cancel()'s timeout handling: if abort() hangs past the cleanup
613
+ // timeout, close the managed session instead of leaving it registered
614
+ // with a still-streaming AgentSession. The queued prompt below observes
615
+ // the same cleanup rejection and fails accordingly.
616
+ this.#beginCancelCleanup(record, activeTurn).catch(async (error: unknown) => {
617
+ logger.warn("ACP cancel cleanup timed out; closing session", {
618
+ sessionId: record.session.sessionId,
619
+ error,
620
+ });
621
+ await this.#closeManagedSession(params.sessionId, record);
622
+ });
598
623
  }
599
624
  return await this.#queuePrompt(record, async () => {
600
625
  const previousTurn = record.promptTurn;
@@ -607,6 +632,7 @@ export class AcpAgent implements Agent {
607
632
  await previousTurn.promise.catch(() => undefined);
608
633
  await previousTurn.cleanup;
609
634
  }
635
+ this.#throwIfRecordClosed(record);
610
636
 
611
637
  const converted = this.#convertPromptBlocks(params.prompt);
612
638
  const pendingPrompt = Promise.withResolvers<PromptResponse>();
@@ -623,7 +649,7 @@ export class AcpAgent implements Agent {
623
649
  };
624
650
 
625
651
  record.promptTurn.unsubscribe = record.session.subscribe(event => {
626
- void this.#handlePromptEvent(record, event);
652
+ this.#trackPromptEvent(record, event);
627
653
  });
628
654
 
629
655
  this.#runPromptOrCommand(record, converted.text, converted.images).catch((error: unknown) => {
@@ -643,6 +669,7 @@ export class AcpAgent implements Agent {
643
669
  release: releaseQueue,
644
670
  };
645
671
  await previousQueue.promise;
672
+ this.#throwIfRecordClosed(record);
646
673
  try {
647
674
  return await run();
648
675
  } finally {
@@ -653,6 +680,55 @@ export class AcpAgent implements Agent {
653
680
  }
654
681
  }
655
682
 
683
+ #throwIfRecordClosed(record: ManagedSessionRecord): void {
684
+ if (record.closedError) {
685
+ throw record.closedError;
686
+ }
687
+ }
688
+
689
+ #createPromptLifecycleError(message: string): PromptLifecycleError {
690
+ return Object.assign(new Error(message), { code: "ACP_SESSION_CLOSED" as const });
691
+ }
692
+
693
+ #trackPromptEvent(record: ManagedSessionRecord, event: AgentSessionEvent): void {
694
+ const handling = this.#handlePromptEvent(record, event).catch((error: unknown) => {
695
+ logger.warn("ACP prompt event handler failed", { error });
696
+ });
697
+ record.promptEventHandlers.add(handling);
698
+ void handling.finally(() => {
699
+ record.promptEventHandlers.delete(handling);
700
+ });
701
+ }
702
+
703
+ async #waitForPromptEventHandlers(record: ManagedSessionRecord): Promise<void> {
704
+ while (record.promptEventHandlers.size > 0) {
705
+ await Promise.allSettled(Array.from(record.promptEventHandlers));
706
+ }
707
+ }
708
+
709
+ #trackExtensionUserMessage(record: ManagedSessionRecord, task: Promise<void>): void {
710
+ const tracked = task.catch((error: unknown) => {
711
+ logger.warn("ACP extension sendUserMessage failed", { error });
712
+ });
713
+ record.extensionUserMessageTasks.add(tracked);
714
+ void tracked.finally(() => {
715
+ record.extensionUserMessageTasks.delete(tracked);
716
+ });
717
+ }
718
+
719
+ async #waitForExtensionUserMessages(
720
+ record: ManagedSessionRecord,
721
+ baseline: ReadonlySet<Promise<void>>,
722
+ ): Promise<void> {
723
+ while (true) {
724
+ const pending = Array.from(record.extensionUserMessageTasks).filter(task => !baseline.has(task));
725
+ if (pending.length === 0) {
726
+ return;
727
+ }
728
+ await Promise.allSettled(pending);
729
+ }
730
+ }
731
+
656
732
  async #runPromptOrCommand(record: ManagedSessionRecord, text: string, images: AgentImageContent[]): Promise<void> {
657
733
  const skillResult = await this.#tryRunSkillCommand(record, text);
658
734
  if (skillResult) {
@@ -699,7 +775,18 @@ export class AcpAgent implements Agent {
699
775
  return;
700
776
  }
701
777
 
702
- await record.session.prompt(text, { images });
778
+ const extensionPromptBaseline = new Set(record.extensionUserMessageTasks);
779
+ const agentInvoked = await record.session.prompt(text, { images });
780
+ // Extension and custom-TS commands are handled locally inside session.prompt().
781
+ // An ACP extension command can still call pi.sendUserMessage(), which starts
782
+ // an async nested prompt through the extension runtime. Keep the ACP turn
783
+ // subscribed until those scheduled prompts and their event handlers drain;
784
+ // only then is `false` proof that the slash command was purely local.
785
+ if (!agentInvoked) {
786
+ await this.#waitForExtensionUserMessages(record, extensionPromptBaseline);
787
+ await this.#waitForPromptEventHandlers(record);
788
+ this.#finishPrompt(record, { stopReason: "end_turn" });
789
+ }
703
790
  }
704
791
 
705
792
  async #tryRunSkillCommand(record: ManagedSessionRecord, text: string): Promise<boolean> {
@@ -991,6 +1078,9 @@ export class AcpAgent implements Agent {
991
1078
  liveMessageProgress: undefined,
992
1079
  toolArgsById: new Map(),
993
1080
  extensionsConfigured: false,
1081
+ closedError: undefined,
1082
+ promptEventHandlers: new Set(),
1083
+ extensionUserMessageTasks: new Set(),
994
1084
  lifetimeUnsubscribe: undefined,
995
1085
  };
996
1086
  }
@@ -1582,10 +1672,12 @@ export class AcpAgent implements Agent {
1582
1672
  commands.push(command);
1583
1673
  };
1584
1674
 
1585
- // Advertise in the order dispatch resolves them: ACP builtins first
1586
- // (so core commands like `/model`, `/mcp`, `/todo` cannot be shadowed),
1587
- // then skills, then custom/user commands, then file-based slash
1588
- // commands. `appendCommand` dedupes by name so earlier entries win.
1675
+ // Advertise in the order dispatch resolves them (mirrors AgentSession
1676
+ // dispatch: builtins skills extensions custom TS → file-based).
1677
+ // `appendCommand` dedupes by name so earlier entries win; extension
1678
+ // commands therefore correctly shadow custom TS commands of the same
1679
+ // name, matching the runtime behaviour of #tryExecuteExtensionCommand
1680
+ // running before #tryExecuteCustomCommand.
1589
1681
  for (const command of ACP_BUILTIN_SLASH_COMMANDS) {
1590
1682
  appendCommand(command);
1591
1683
  }
@@ -1600,6 +1692,20 @@ export class AcpAgent implements Agent {
1600
1692
  }
1601
1693
  }
1602
1694
 
1695
+ for (const command of session.extensionRunner?.getRegisteredCommands(ACP_BUILTIN_RESERVED_NAMES) ?? []) {
1696
+ // Reserved-set filtering in getRegisteredCommands only covers exact
1697
+ // names; colon-namespaced names whose prefix is a builtin (e.g.
1698
+ // `model:foo`) would still dispatch to the builtin in ACP.
1699
+ if (isAcpBuiltinShadowedName(command.name)) {
1700
+ continue;
1701
+ }
1702
+ appendCommand({
1703
+ name: command.name,
1704
+ description: command.description ?? "(extension command)",
1705
+ input: { hint: "arguments" },
1706
+ });
1707
+ }
1708
+
1603
1709
  for (const command of session.customCommands) {
1604
1710
  appendCommand({
1605
1711
  name: command.command.name,
@@ -2069,9 +2175,7 @@ export class AcpAgent implements Agent {
2069
2175
  });
2070
2176
  },
2071
2177
  sendUserMessage: (content, options) => {
2072
- record.session.sendUserMessage(content, options).catch((error: unknown) => {
2073
- logger.warn("ACP extension sendUserMessage failed", { error });
2074
- });
2178
+ this.#trackExtensionUserMessage(record, record.session.sendUserMessage(content, options));
2075
2179
  },
2076
2180
  appendEntry: (customType, data) => {
2077
2181
  record.session.sessionManager.appendCustomEntry(customType, data);
@@ -2224,6 +2328,7 @@ export class AcpAgent implements Agent {
2224
2328
  }
2225
2329
 
2226
2330
  async #closeManagedSession(sessionId: string, record: ManagedSessionRecord): Promise<void> {
2331
+ record.closedError ??= this.#createPromptLifecycleError("ACP session closed before queued prompt could run");
2227
2332
  this.#sessions.delete(sessionId);
2228
2333
  await this.#cancelPromptForClose(record);
2229
2334
  await this.#disposeSessionRecord(record);
@@ -2279,6 +2384,9 @@ export class AcpAgent implements Agent {
2279
2384
  await Promise.all(
2280
2385
  records.map(async ([sessionId, record]) => {
2281
2386
  try {
2387
+ record.closedError ??= this.#createPromptLifecycleError(
2388
+ "ACP agent disposed before queued prompt could run",
2389
+ );
2282
2390
  await this.#cancelPromptForClose(record);
2283
2391
  await this.#disposeSessionRecord(record);
2284
2392
  } catch (error) {