@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
package/src/main.ts CHANGED
@@ -11,6 +11,7 @@ import { EventLoopKeepalive } from "@oh-my-pi/pi-agent-core";
11
11
  import type { ImageContent } from "@oh-my-pi/pi-ai";
12
12
  import {
13
13
  $env,
14
+ getLogPath,
14
15
  getProjectDir,
15
16
  logger,
16
17
  normalizePathForComparison,
@@ -28,7 +29,7 @@ import { runListModelsCommand } from "./cli/list-models";
28
29
  import { selectSession } from "./cli/session-picker";
29
30
  import { applyStartupCwd } from "./cli/startup-cwd";
30
31
  import { findConfigFile } from "./config";
31
- import { ModelRegistry, ModelsConfigFile } from "./config/model-registry";
32
+ import { ModelRegistry } from "./config/model-registry";
32
33
  import {
33
34
  getModelMatchPreferences,
34
35
  resolveCliModel,
@@ -36,6 +37,7 @@ import {
36
37
  resolveModelScope,
37
38
  type ScopedModel,
38
39
  } from "./config/model-resolver";
40
+ import { ModelsConfigFile } from "./config/models-config";
39
41
  import { getDefault, type SettingPath, Settings, settings } from "./config/settings";
40
42
  import { initializeWithSettings } from "./discovery";
41
43
  import {
@@ -49,6 +51,7 @@ import { ExtensionRunner } from "./extensibility/extensions/runner";
49
51
  import type { ExtensionUIContext } from "./extensibility/extensions/types";
50
52
  import { scheduleMarketplaceAutoUpdate } from "./extensibility/plugins/marketplace-auto-update";
51
53
  import type { MCPManager } from "./mcp";
54
+ import { WelcomeComponent } from "./modes/components/welcome";
52
55
  import { InteractiveMode } from "./modes/interactive-mode";
53
56
  import type { PrintModeOptions } from "./modes/print-mode";
54
57
  import { CURRENT_SETUP_VERSION } from "./modes/setup-version";
@@ -64,11 +67,17 @@ import {
64
67
  import type { AgentSession } from "./session/agent-session";
65
68
  import type { AuthStorage } from "./session/auth-storage";
66
69
  import { resolveResumableSession, type SessionInfo, SessionManager } from "./session/session-manager";
67
- import { resolvePromptInput } from "./system-prompt";
70
+ import { discoverTitleSystemPromptFile, resolvePromptInput } from "./system-prompt";
68
71
  import { initTelemetryExport, isTelemetryExportEnabled } from "./telemetry-export";
69
72
  import { AUTO_THINKING } from "./thinking";
70
- import type { LspStartupServerInfo } from "./tools";
71
- import { getChangelogPath, getNewEntries, parseChangelog } from "./utils/changelog";
73
+ import { discoverStartupLspServers, type LspStartupServerInfo } from "./tools";
74
+ import {
75
+ getChangelogPath,
76
+ getNewEntries,
77
+ parseChangelog,
78
+ readLastChangelogVersion,
79
+ writeLastChangelogVersion,
80
+ } from "./utils/changelog";
72
81
  import { EventBus } from "./utils/event-bus";
73
82
 
74
83
  type RunAcpMode = (createSession: AcpSessionFactory) => Promise<never>;
@@ -76,8 +85,47 @@ type RunPrintMode = (session: AgentSession, options: PrintModeOptions) => Promis
76
85
  type RunRpcMode = (
77
86
  session: AgentSession,
78
87
  setToolUIContext?: (uiContext: ExtensionUIContext, hasUI: boolean) => void,
88
+ eventBus?: EventBus,
79
89
  ) => Promise<never>;
80
90
 
91
+ function maybeShowStartupSplash(options: {
92
+ isInteractive: boolean;
93
+ resuming: boolean;
94
+ quiet: boolean;
95
+ version: string;
96
+ setupPending: boolean;
97
+ modelName?: string;
98
+ providerName?: string;
99
+ lspServers?: LspStartupServerInfo[];
100
+ }): void {
101
+ if (!options.isInteractive) return;
102
+ if (options.resuming || options.quiet) return;
103
+ if ($env.PI_TIMING) return;
104
+ if (!process.stdin.isTTY || !process.stdout.isTTY) return;
105
+ // First-run launches go straight into the setup wizard, which paints its own
106
+ // splash — keep the minimal two-line notice there.
107
+ if (options.setupPending) {
108
+ process.stdout.write(`${chalk.dim(`omp ${options.version}`)}\n${chalk.dim("Initializing session…")}\n`);
109
+ return;
110
+ }
111
+ // Render the same welcome box the TUI paints first: recent sessions as a
112
+ // loading placeholder (the fixed slot count keeps the box height stable) and
113
+ // the logo held on the intro animation's first frame so the in-TUI intro
114
+ // continues from the frame shown here. Clearing the screen first puts the
115
+ // box at the same origin the TUI's first full paint (clearScrollback) uses,
116
+ // so the live welcome replaces this frame in place without shifting.
117
+ const welcome = new WelcomeComponent(
118
+ options.version,
119
+ options.modelName ?? "",
120
+ options.providerName ?? "",
121
+ null,
122
+ options.lspServers ?? [],
123
+ );
124
+ welcome.holdIntroFirstFrame();
125
+ const lines = welcome.render(process.stdout.columns || 80);
126
+ process.stdout.write(`\x1b[2J\x1b[H\x1b[3J\n${lines.join("\n")}\n`);
127
+ }
128
+
81
129
  async function checkForNewVersion(currentVersion: string): Promise<string | undefined> {
82
130
  if (!settings.get("startup.checkUpdate")) {
83
131
  return;
@@ -143,15 +191,79 @@ function applyAcpDefaultSettingOverrides(targetSettings: Settings = settings): v
143
191
 
144
192
  async function readPipedInput(): Promise<string | undefined> {
145
193
  if (process.stdin.isTTY !== false) return undefined;
194
+ // stdin is a pipe: a producer that never writes nor closes would block
195
+ // startup forever with zero output. Say what we're blocked on after 1s.
196
+ const notice = setTimeout(() => {
197
+ process.stderr.write(`${chalk.dim("Reading prompt from piped stdin (waiting for EOF; ctrl+c to abort)…")}\n`);
198
+ }, 1000);
199
+ notice.unref?.();
146
200
  try {
147
201
  const text = await Bun.stdin.text();
148
202
  if (text.trim().length === 0) return undefined;
149
203
  return text;
150
204
  } catch {
151
205
  return undefined;
206
+ } finally {
207
+ clearTimeout(notice);
152
208
  }
153
209
  }
154
210
 
211
+ // ---------------------------------------------------------------------------
212
+ // Startup watchdog
213
+ // ---------------------------------------------------------------------------
214
+ // Speculative-hang reporter: until startup hands off to a mode runner, print a
215
+ // stderr line every 10s naming the deepest in-flight startup phase. Turns
216
+ // zero-output indefinite hangs (stuck discovery read, network wait, stdin
217
+ // pipe) into self-diagnosing reports instead of "it just hangs" (see the
218
+ // PI_DEBUG_STARTUP markers for the synchronous-hang counterpart).
219
+
220
+ const STARTUP_WATCHDOG_INTERVAL_MS = 10_000;
221
+ let startupWatchdogTimer: NodeJS.Timeout | undefined;
222
+ let startupWatchdogActive = false;
223
+ let startupWatchdogStartedAt = 0;
224
+
225
+ function armStartupWatchdog(): void {
226
+ if (startupWatchdogTimer) return;
227
+ startupWatchdogTimer = setInterval(() => {
228
+ const elapsed = Math.round((Date.now() - startupWatchdogStartedAt) / 1000);
229
+ const phase = logger.openSpanPath().join(" > ") || "module load / pre-phase work";
230
+ process.stderr.write(
231
+ `${chalk.yellow(`Still starting after ${elapsed}s`)}${chalk.dim(` — phase: ${phase}`)}\n` +
232
+ `${chalk.dim(` logs: ${getLogPath()} · re-run with PI_DEBUG_STARTUP=1 for streaming phase markers`)}\n`,
233
+ );
234
+ }, STARTUP_WATCHDOG_INTERVAL_MS);
235
+ startupWatchdogTimer.unref?.();
236
+ }
237
+
238
+ function disarmStartupWatchdog(): void {
239
+ if (!startupWatchdogTimer) return;
240
+ clearInterval(startupWatchdogTimer);
241
+ startupWatchdogTimer = undefined;
242
+ }
243
+
244
+ /** Begin watching startup (idempotent). */
245
+ function startStartupWatchdog(): void {
246
+ startupWatchdogActive = true;
247
+ startupWatchdogStartedAt = Date.now();
248
+ armStartupWatchdog();
249
+ }
250
+
251
+ /** Permanently stop watching: a mode runner now owns the terminal. */
252
+ function stopStartupWatchdog(): void {
253
+ startupWatchdogActive = false;
254
+ disarmStartupWatchdog();
255
+ }
256
+
257
+ /** Pause while an interactive prompt legitimately waits on the user. */
258
+ function pauseStartupWatchdog(): void {
259
+ disarmStartupWatchdog();
260
+ }
261
+
262
+ /** Resume after an interactive prompt, if startup is still being watched. */
263
+ function resumeStartupWatchdog(): void {
264
+ if (startupWatchdogActive) armStartupWatchdog();
265
+ }
266
+
155
267
  export interface InteractiveModeNotify {
156
268
  kind: "warn" | "error" | "info";
157
269
  message: string;
@@ -259,6 +371,7 @@ async function runInteractiveMode(
259
371
  eventBus?: EventBus,
260
372
  initialMessage?: string,
261
373
  initialImages?: ImageContent[],
374
+ titleSystemPrompt?: string,
262
375
  ): Promise<void> {
263
376
  const mode = new InteractiveMode(
264
377
  session,
@@ -268,6 +381,7 @@ async function runInteractiveMode(
268
381
  lspServers,
269
382
  mcpManager,
270
383
  eventBus,
384
+ titleSystemPrompt,
271
385
  );
272
386
 
273
387
  // Cold-launch gate: the full setup wizard (every scene + the overlay and
@@ -361,12 +475,14 @@ async function promptForkSession(session: SessionInfo): Promise<SessionPromptRes
361
475
  return "unavailable";
362
476
  }
363
477
  const message = `Session found in different project: ${session.cwd}. Fork into current directory? [y/N] `;
478
+ pauseStartupWatchdog();
364
479
  const rl = createInterface({ input: process.stdin, output: process.stdout });
365
480
  try {
366
481
  const answer = (await rl.question(message)).trim().toLowerCase();
367
482
  return answer === "y" || answer === "yes" ? "accepted" : "declined";
368
483
  } finally {
369
484
  rl.close();
485
+ resumeStartupWatchdog();
370
486
  }
371
487
  }
372
488
 
@@ -375,12 +491,14 @@ async function promptMoveSession(session: SessionInfo): Promise<SessionPromptRes
375
491
  return "unavailable";
376
492
  }
377
493
  const message = `Session's directory no longer exists (${session.cwd}). Move (re-root) it into the current directory? [Y/n] `;
494
+ pauseStartupWatchdog();
378
495
  const rl = createInterface({ input: process.stdin, output: process.stdout });
379
496
  try {
380
497
  const answer = (await rl.question(message)).trim().toLowerCase();
381
498
  return answer === "" || answer === "y" || answer === "yes" ? "accepted" : "declined";
382
499
  } finally {
383
500
  rl.close();
501
+ resumeStartupWatchdog();
384
502
  }
385
503
  }
386
504
 
@@ -437,7 +555,7 @@ async function getChangelogForDisplay(parsed: Args): Promise<string | undefined>
437
555
  return undefined;
438
556
  }
439
557
 
440
- const lastVersion = settings.get("lastChangelogVersion");
558
+ const lastVersion = await readLastChangelogVersion();
441
559
  if (lastVersion === VERSION) {
442
560
  // Steady state: user already saw the current version's changelog. Skip the file read + parse.
443
561
  return undefined;
@@ -448,15 +566,13 @@ async function getChangelogForDisplay(parsed: Args): Promise<string | undefined>
448
566
 
449
567
  if (!lastVersion) {
450
568
  if (entries.length > 0) {
451
- settings.set("lastChangelogVersion", VERSION);
452
- await flushChangelogVersion();
569
+ await writeLastChangelogVersion(VERSION);
453
570
  return entries.map(e => e.content).join("\n\n");
454
571
  }
455
572
  } else {
456
573
  const newEntries = getNewEntries(entries, lastVersion);
457
574
  if (newEntries.length > 0) {
458
- settings.set("lastChangelogVersion", VERSION);
459
- await flushChangelogVersion();
575
+ await writeLastChangelogVersion(VERSION);
460
576
  return newEntries.map(e => e.content).join("\n\n");
461
577
  }
462
578
  }
@@ -464,14 +580,6 @@ async function getChangelogForDisplay(parsed: Args): Promise<string | undefined>
464
580
  return undefined;
465
581
  }
466
582
 
467
- async function flushChangelogVersion(): Promise<void> {
468
- try {
469
- await settings.flush();
470
- } catch (error: unknown) {
471
- logger.warn("Failed to persist lastChangelogVersion", { error });
472
- }
473
- }
474
-
475
583
  /** Resolves CLI session flags into an existing, forked, in-memory, or cancelled session manager. */
476
584
  export async function createSessionManager(
477
585
  parsed: Args,
@@ -619,7 +727,7 @@ async function buildSessionOptions(
619
727
  sessionManager: SessionManager | undefined,
620
728
  modelRegistry: ModelRegistry,
621
729
  activeSettings: Settings,
622
- ): Promise<{ options: CreateAgentSessionOptions }> {
730
+ ): Promise<{ options: CreateAgentSessionOptions; titleSystemPrompt?: string }> {
623
731
  const options: CreateAgentSessionOptions = {
624
732
  cwd: parsed.cwd ?? getProjectDir(),
625
733
  autoApprove: parsed.autoApprove ?? false,
@@ -630,6 +738,8 @@ async function buildSessionOptions(
630
738
  const resolvedSystemPrompt = await resolvePromptInput(systemPromptSource, "system prompt");
631
739
  const appendPromptSource = parsed.appendSystemPrompt ?? discoverAppendSystemPromptFile();
632
740
  const resolvedAppendPrompt = await resolvePromptInput(appendPromptSource, "append system prompt");
741
+ const titleSystemPromptSource = discoverTitleSystemPromptFile();
742
+ const titleSystemPrompt = await resolvePromptInput(titleSystemPromptSource, "title system prompt");
633
743
 
634
744
  if (sessionManager) {
635
745
  options.sessionManager = sessionManager;
@@ -775,7 +885,7 @@ async function buildSessionOptions(
775
885
  options.additionalExtensionPaths = [];
776
886
  }
777
887
 
778
- return { options };
888
+ return { options, titleSystemPrompt };
779
889
  }
780
890
 
781
891
  interface RunRootCommandDependencies {
@@ -792,6 +902,7 @@ export async function runRootCommand(
792
902
  deps: RunRootCommandDependencies = {},
793
903
  ): Promise<void> {
794
904
  logger.startTiming();
905
+ startStartupWatchdog();
795
906
 
796
907
  // Initialize theme early with defaults (CLI commands need symbols)
797
908
  // Will be re-initialized with user preferences later
@@ -803,7 +914,7 @@ export async function runRootCommand(
803
914
  const notifs: (InteractiveModeNotify | null)[] = [];
804
915
 
805
916
  // Create AuthStorage and ModelRegistry upfront
806
- const authStorage = await logger.time("discoverModels", deps.discoverAuthStorage ?? discoverAuthStorage);
917
+ const authStorage = await logger.time("discoverAuthStorage", deps.discoverAuthStorage ?? discoverAuthStorage);
807
918
  const modelRegistry = new ModelRegistry(authStorage);
808
919
 
809
920
  if (parsedArgs.version) {
@@ -814,6 +925,7 @@ export async function runRootCommand(
814
925
  if (parsedArgs.listModels !== undefined) {
815
926
  const settingsInstance = await logger.time("settings:init:list-models", Settings.init, {
816
927
  cwd: getProjectDir(),
928
+ configFiles: parsedArgs.config,
817
929
  });
818
930
  await modelRegistry.refresh("online");
819
931
  const cliExtensionPaths = parsedArgs.noExtensions
@@ -877,11 +989,16 @@ export async function runRootCommand(
877
989
  }
878
990
 
879
991
  let cwd = getProjectDir();
880
- const settingsInstance = deps.settings ?? (await logger.time("settings:init", Settings.init, { cwd }));
992
+ const settingsInstance =
993
+ deps.settings ?? (await logger.time("settings:init", Settings.init, { cwd, configFiles: parsedArgs.config }));
881
994
  if (parsedArgs.approvalMode) {
882
995
  // Runtime override (not persisted): every settings.get("tools.approvalMode") downstream
883
996
  // sees this value. The wrapper still honours --auto-approve / --yolo on top of it.
884
997
  settingsInstance.override("tools.approvalMode", parsedArgs.approvalMode);
998
+ } else if (parsedArgs.autoApprove) {
999
+ // --auto-approve / --yolo without an explicit --approval-mode: reflect in settings so
1000
+ // setup-time checks (e.g. #wrapToolForAcpPermission) also see the yolo intent.
1001
+ settingsInstance.override("tools.approvalMode", "yolo");
885
1002
  }
886
1003
  if (parsedArgs.mode === "rpc" || parsedArgs.mode === "rpc-ui") {
887
1004
  applyRpcDefaultSettingOverrides(settingsInstance);
@@ -991,10 +1108,12 @@ export async function runRootCommand(
991
1108
  }
992
1109
  startInAllScope = true;
993
1110
  }
1111
+ pauseStartupWatchdog();
994
1112
  const selected = await logger.time("selectSession", selectSession, folderSessions, {
995
1113
  allSessions: preloadedAllSessions,
996
1114
  startInAllScope,
997
1115
  });
1116
+ resumeStartupWatchdog();
998
1117
  if (!selected) {
999
1118
  process.stdout.write(`${chalk.dim("No session selected")}\n`);
1000
1119
  return;
@@ -1025,7 +1144,7 @@ export async function runRootCommand(
1025
1144
  clearPluginRootsCache: clearPluginRootsAndCaches,
1026
1145
  });
1027
1146
 
1028
- const { options: sessionOptions } = await logger.time(
1147
+ const { options: sessionOptions, titleSystemPrompt } = await logger.time(
1029
1148
  "buildSessionOptions",
1030
1149
  buildSessionOptions,
1031
1150
  parsedArgs,
@@ -1086,6 +1205,7 @@ export async function runRootCommand(
1086
1205
  });
1087
1206
  // Branch-only protocol runner: keep ACP server code out of normal interactive startup.
1088
1207
  const runAcpMode = deps.runAcpMode ?? (await import("./modes/acp/acp-mode")).runAcpMode;
1208
+ stopStartupWatchdog();
1089
1209
  await runAcpMode(createAcpSession);
1090
1210
  } else {
1091
1211
  // Resolve extension-registered CLI flags before creating the session so a
@@ -1119,6 +1239,42 @@ export async function runRootCommand(
1119
1239
  stdinContent: pipedInput,
1120
1240
  });
1121
1241
 
1242
+ // Resolve the model the session will most likely start with so the splash
1243
+ // box matches the final welcome screen (the raw role selector, e.g.
1244
+ // "anthropic/claude-fable-5:high", is wider than the left column and would
1245
+ // collapse the box into the single-column layout).
1246
+ let splashModel = sessionOptions.model;
1247
+ if (!splashModel) {
1248
+ const remembered = settingsInstance.getModelRole("default");
1249
+ if (remembered) {
1250
+ splashModel = resolveModelRoleValue(remembered, modelRegistry.getAll(), {
1251
+ settings: settingsInstance,
1252
+ matchPreferences: modelMatchPreferences,
1253
+ modelRegistry,
1254
+ }).model;
1255
+ }
1256
+ }
1257
+ // Mirror createAgentSession's startup LSP discovery (sync and cheap: root
1258
+ // markers + binary lookup) so the splash lists the same servers the live
1259
+ // welcome screen will show.
1260
+ const splashLspServers =
1261
+ (sessionOptions.enableLsp ?? true)
1262
+ ? discoverStartupLspServers(
1263
+ sessionOptions.cwd ?? cwd,
1264
+ settingsInstance.get("lsp.lazy") ? "available" : "connecting",
1265
+ )
1266
+ : [];
1267
+ maybeShowStartupSplash({
1268
+ isInteractive,
1269
+ resuming: Boolean(parsedArgs.continue || parsedArgs.resume || parsedArgs.fork),
1270
+ quiet: settingsInstance.get("startup.quiet"),
1271
+ version: VERSION,
1272
+ setupPending: deps.forceSetupWizard === true || settingsInstance.get("setupVersion") < CURRENT_SETUP_VERSION,
1273
+ modelName: splashModel?.name,
1274
+ providerName: splashModel?.provider,
1275
+ lspServers: splashLspServers,
1276
+ });
1277
+
1122
1278
  const { session, setToolUIContext, modelFallbackMessage, lspServers, mcpManager } = await createSession({
1123
1279
  ...sessionOptions,
1124
1280
  eventBus,
@@ -1152,7 +1308,8 @@ export async function runRootCommand(
1152
1308
  if (mode === "rpc" || mode === "rpc-ui") {
1153
1309
  // Branch-only protocol runner: keep RPC host code out of normal interactive startup.
1154
1310
  const runRpcMode: RunRpcMode = (await import("./modes/rpc/rpc-mode")).runRpcMode;
1155
- await runRpcMode(session, mode === "rpc-ui" ? setToolUIContext : undefined);
1311
+ stopStartupWatchdog();
1312
+ await runRpcMode(session, mode === "rpc-ui" ? setToolUIContext : undefined, eventBus);
1156
1313
  } else if (isInteractive) {
1157
1314
  const versionCheckPromise = checkForNewVersion(VERSION).catch(() => undefined);
1158
1315
  const changelogMarkdown = await logger.time("main:getChangelogForDisplay", getChangelogForDisplay, parsedArgs);
@@ -1175,6 +1332,7 @@ export async function runRootCommand(
1175
1332
  }
1176
1333
  }
1177
1334
 
1335
+ stopStartupWatchdog();
1178
1336
  logger.endTiming();
1179
1337
  await runInteractiveMode(
1180
1338
  session,
@@ -1191,9 +1349,11 @@ export async function runRootCommand(
1191
1349
  eventBus,
1192
1350
  initialMessage,
1193
1351
  initialImages,
1352
+ titleSystemPrompt,
1194
1353
  );
1195
1354
  } else {
1196
1355
  // Branch-only single-shot runner: keep print-mode code out of normal interactive startup.
1356
+ stopStartupWatchdog();
1197
1357
  const runPrintMode: RunPrintMode = (await import("./modes/print-mode")).runPrintMode;
1198
1358
  await runPrintMode(session, {
1199
1359
  mode,
@@ -6,6 +6,28 @@
6
6
  */
7
7
  import { logger } from "@oh-my-pi/pi-utils";
8
8
 
9
+ /** Hard ceiling on a single MCP HTTP request when the caller provides no signal. */
10
+ const MCP_DEFAULT_TIMEOUT_MS = 60_000;
11
+
12
+ const SENSITIVE_QUERY_PARAM = /key|token|secret|auth/i;
13
+
14
+ /**
15
+ * Redact credential-bearing query params (e.g. `exaApiKey`) so failed
16
+ * requests never write secrets to the persistent log file.
17
+ */
18
+ export function redactUrlForLog(url: string): string {
19
+ try {
20
+ const parsed = new URL(url);
21
+ for (const name of parsed.searchParams.keys()) {
22
+ if (SENSITIVE_QUERY_PARAM.test(name)) parsed.searchParams.set(name, "[redacted]");
23
+ }
24
+ return parsed.toString();
25
+ } catch {
26
+ // Unparseable URL — drop the query string entirely rather than risk leaking it.
27
+ return url.split("?")[0];
28
+ }
29
+ }
30
+
9
31
  /** Parse SSE response format (lines starting with "data: ") */
10
32
  export function parseSSE(text: string): unknown {
11
33
  const lines = text.split("\n");
@@ -13,8 +35,12 @@ export function parseSSE(text: string): unknown {
13
35
  if (line.startsWith("data: ")) {
14
36
  const data = line.slice(6).trim();
15
37
  if (data === "[DONE]") continue;
16
- const result = JSON.parse(data) as unknown;
17
- if (result) return result;
38
+ try {
39
+ const result = JSON.parse(data) as unknown;
40
+ if (result) return result;
41
+ } catch {
42
+ // Non-JSON data line (keep-alive/comment) — skip and keep scanning.
43
+ }
18
44
  }
19
45
  }
20
46
  // Fallback: try parsing entire response as JSON
@@ -71,12 +97,12 @@ export async function callMCP<T = unknown>(
71
97
  Accept: "application/json, text/event-stream",
72
98
  },
73
99
  body: JSON.stringify(body),
74
- signal: options?.signal,
100
+ signal: options?.signal ?? AbortSignal.timeout(MCP_DEFAULT_TIMEOUT_MS),
75
101
  });
76
102
 
77
103
  if (!response.ok) {
78
104
  const errorMsg = `MCP request failed: ${response.status} ${response.statusText}`;
79
- logger.error(errorMsg, { url, method, params });
105
+ logger.error(errorMsg, { url: redactUrlForLog(url), method, params });
80
106
  throw new Error(errorMsg);
81
107
  }
82
108
 
@@ -84,7 +110,11 @@ export async function callMCP<T = unknown>(
84
110
  const result = parseSSE(text) as JsonRpcResponse<T> | null;
85
111
 
86
112
  if (!result) {
87
- logger.error("Failed to parse MCP response", { url, method, responseText: text.slice(0, 500) });
113
+ logger.error("Failed to parse MCP response", {
114
+ url: redactUrlForLog(url),
115
+ method,
116
+ responseText: text.slice(0, 500),
117
+ });
88
118
  throw new Error("Failed to parse MCP response");
89
119
  }
90
120
 
@@ -133,6 +133,12 @@ function resolveComSpec(env: Record<string, string | undefined>): string {
133
133
  return comspec && comspec.length > 0 ? comspec : "cmd.exe";
134
134
  }
135
135
 
136
+ /** `cmd /s /c` strips one outer quote pair; keep inner argv quotes intact. */
137
+ function buildCmdExeCommand(command: string, args: readonly string[]): string {
138
+ const quotedCommand = [command, ...args].map(quoteCmdArg).join(" ");
139
+ return `"${quotedCommand}"`;
140
+ }
141
+
136
142
  /** Resolve the subprocess argv used to launch an MCP stdio server. */
137
143
  export async function resolveStdioSpawnCommand(
138
144
  config: MCPStdioServerConfig,
@@ -146,7 +152,7 @@ export async function resolveStdioSpawnCommand(
146
152
  if (!isWindowsBatchCommand(resolvedCommand)) return { cmd: [resolvedCommand, ...args] };
147
153
 
148
154
  return {
149
- cmd: [resolveComSpec(options.env), "/d", "/s", "/c", [resolvedCommand, ...args].map(quoteCmdArg).join(" ")],
155
+ cmd: [resolveComSpec(options.env), "/d", "/s", "/c", buildCmdExeCommand(resolvedCommand, args)],
150
156
  };
151
157
  }
152
158
 
@@ -3,7 +3,8 @@ import type * as fsNode from "node:fs";
3
3
  import * as fs from "node:fs/promises";
4
4
  import * as path from "node:path";
5
5
  import type { AgentMessage } from "@oh-my-pi/pi-agent-core";
6
- import { type ApiKey, clampThinkingLevelForModel, completeSimple, Effort, type Model } from "@oh-my-pi/pi-ai";
6
+ import { type ApiKey, completeSimple, Effort, type Model } from "@oh-my-pi/pi-ai";
7
+ import { clampThinkingLevelForModel } from "@oh-my-pi/pi-catalog/model-thinking";
7
8
  import { getAgentDbPath, getMemoriesDir, logger, parseJsonlLenient, prompt } from "@oh-my-pi/pi-utils";
8
9
 
9
10
  import type { ModelRegistry } from "../config/model-registry";
@@ -275,6 +276,7 @@ async function runPhase1(options: {
275
276
  apiKey: modelRegistry.resolver(phase1Model.provider, {
276
277
  sessionId: session.sessionId,
277
278
  baseUrl: phase1Model.baseUrl,
279
+ modelId: phase1Model.id,
278
280
  }),
279
281
  modelMaxTokens: computeModelTokenBudget(phase1Model, config),
280
282
  config,
@@ -435,6 +437,7 @@ async function runPhase2(options: {
435
437
  apiKey: modelRegistry.resolver(phase2Model.provider, {
436
438
  sessionId: session.sessionId,
437
439
  baseUrl: phase2Model.baseUrl,
440
+ modelId: phase2Model.id,
438
441
  }),
439
442
  metadata: session.agent?.metadataForProvider(phase2Model.provider),
440
443
  });
@@ -14,4 +14,5 @@ export type {
14
14
  export * from "./local-backend";
15
15
  export * from "./off-backend";
16
16
  export * from "./resolve";
17
+ export * from "./runtime";
17
18
  export * from "./types";
@@ -27,4 +27,13 @@ export const localBackend: MemoryBackend = {
27
27
  async enqueue(agentDir, cwd) {
28
28
  enqueueMemoryConsolidation(agentDir, cwd);
29
29
  },
30
+ async status() {
31
+ return {
32
+ backend: "local" as const,
33
+ active: true,
34
+ writable: false,
35
+ searchable: false,
36
+ message: "Local rollout-summary memory is active; structured search/save is not available.",
37
+ };
38
+ },
30
39
  };
@@ -13,4 +13,13 @@ export const offBackend: MemoryBackend = {
13
13
  },
14
14
  async clear() {},
15
15
  async enqueue() {},
16
+ async status() {
17
+ return {
18
+ backend: "off" as const,
19
+ active: false,
20
+ writable: false,
21
+ searchable: false,
22
+ message: "Memory backend is off.",
23
+ };
24
+ },
16
25
  };
@@ -0,0 +1,66 @@
1
+ import type { AgentSession } from "../session/agent-session";
2
+ import { resolveMemoryBackend } from "./resolve";
3
+ import type {
4
+ MemoryBackendId,
5
+ MemoryBackendOperationContext,
6
+ MemoryBackendSaveInput,
7
+ MemoryBackendSearchOptions,
8
+ MemoryRuntimeContext,
9
+ } from "./types";
10
+ export function createMemoryRuntimeContext(context: MemoryBackendOperationContext): MemoryRuntimeContext {
11
+ const settings = context.session?.settings;
12
+ return {
13
+ async status() {
14
+ if (!settings) {
15
+ return {
16
+ backend: "off" as const,
17
+ active: false,
18
+ writable: false,
19
+ searchable: false,
20
+ message: "No active agent session.",
21
+ };
22
+ }
23
+ const backend = await resolveMemoryBackend(settings);
24
+ return backend.status
25
+ ? await backend.status(context)
26
+ : {
27
+ backend: backend.id,
28
+ active: backend.id !== "off",
29
+ writable: false,
30
+ searchable: false,
31
+ message: "This memory backend does not expose structured status.",
32
+ };
33
+ },
34
+ async search(query: string, options?: MemoryBackendSearchOptions) {
35
+ if (!settings) return unavailableSearch("off", query, "No active agent session.");
36
+ const backend = await resolveMemoryBackend(settings);
37
+ return backend.search
38
+ ? await backend.search(context, query, options)
39
+ : unavailableSearch(backend.id, query, `Memory search is not available for the ${backend.id} backend.`);
40
+ },
41
+ async save(input: string | MemoryBackendSaveInput) {
42
+ if (!settings) return unavailableSave("off", "No active agent session.");
43
+ const backend = await resolveMemoryBackend(settings);
44
+ const normalized = typeof input === "string" ? { content: input } : input;
45
+ return backend.save
46
+ ? await backend.save(context, normalized)
47
+ : unavailableSave(backend.id, `Memory save is not available for the ${backend.id} backend.`);
48
+ },
49
+ };
50
+ }
51
+
52
+ export function createSessionMemoryRuntimeContext(
53
+ session: AgentSession,
54
+ agentDir: string,
55
+ cwd: string,
56
+ ): MemoryRuntimeContext {
57
+ return createMemoryRuntimeContext({ agentDir, cwd, session });
58
+ }
59
+
60
+ function unavailableSearch(backend: MemoryBackendId, query: string, message: string) {
61
+ return { backend, query, count: 0, items: [], message };
62
+ }
63
+
64
+ function unavailableSave(backend: MemoryBackendId, message: string) {
65
+ return { backend, stored: 0, message };
66
+ }