@oh-my-pi/pi-coding-agent 15.10.9 → 15.10.11

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 (352) hide show
  1. package/CHANGELOG.md +117 -0
  2. package/dist/cli.js +23087 -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 +1 -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/commands/launch.d.ts +1 -1
  11. package/dist/types/commands/read.d.ts +1 -1
  12. package/dist/types/commands/usage.d.ts +25 -0
  13. package/dist/types/config/append-only-context-mode.d.ts +2 -1
  14. package/dist/types/config/model-discovery.d.ts +55 -0
  15. package/dist/types/config/model-registry.d.ts +20 -219
  16. package/dist/types/config/model-resolver.d.ts +16 -10
  17. package/dist/types/config/model-roles.d.ts +28 -0
  18. package/dist/types/config/models-config-schema.d.ts +523 -42
  19. package/dist/types/config/models-config.d.ts +385 -0
  20. package/dist/types/config/settings-schema.d.ts +12 -16
  21. package/dist/types/config/settings.d.ts +1 -1
  22. package/dist/types/debug/log-viewer.d.ts +1 -1
  23. package/dist/types/debug/raw-sse.d.ts +1 -1
  24. package/dist/types/debug/terminal-info.d.ts +0 -1
  25. package/dist/types/eval/backend.d.ts +0 -2
  26. package/dist/types/eval/idle-timeout.d.ts +0 -4
  27. package/dist/types/eval/js/shared/rewrite-imports.d.ts +6 -6
  28. package/dist/types/export/html/template.generated.d.ts +1 -1
  29. package/dist/types/extensibility/extensions/types.d.ts +3 -3
  30. package/dist/types/hindsight/mental-models.d.ts +17 -8
  31. package/dist/types/internal-urls/artifact-protocol.d.ts +2 -2
  32. package/dist/types/internal-urls/types.d.ts +1 -1
  33. package/dist/types/lsp/edits.d.ts +9 -0
  34. package/dist/types/lsp/index.d.ts +2 -2
  35. package/dist/types/lsp/types.d.ts +2 -0
  36. package/dist/types/lsp/utils.d.ts +3 -0
  37. package/dist/types/mcp/json-rpc.d.ts +5 -0
  38. package/dist/types/mnemopi/state.d.ts +11 -1
  39. package/dist/types/modes/components/agent-dashboard.d.ts +1 -1
  40. package/dist/types/modes/components/assistant-message.d.ts +3 -1
  41. package/dist/types/modes/components/bash-execution.d.ts +1 -1
  42. package/dist/types/modes/components/copy-selector.d.ts +1 -1
  43. package/dist/types/modes/components/dynamic-border.d.ts +1 -1
  44. package/dist/types/modes/components/extensions/extension-dashboard.d.ts +1 -1
  45. package/dist/types/modes/components/extensions/extension-list.d.ts +1 -1
  46. package/dist/types/modes/components/extensions/inspector-panel.d.ts +1 -1
  47. package/dist/types/modes/components/footer.d.ts +1 -1
  48. package/dist/types/modes/components/hook-editor.d.ts +5 -0
  49. package/dist/types/modes/components/hook-input.d.ts +4 -0
  50. package/dist/types/modes/components/hook-selector.d.ts +1 -1
  51. package/dist/types/modes/components/model-selector.d.ts +1 -1
  52. package/dist/types/modes/components/plan-review-overlay.d.ts +1 -1
  53. package/dist/types/modes/components/session-observer-overlay.d.ts +1 -1
  54. package/dist/types/modes/components/session-selector.d.ts +1 -1
  55. package/dist/types/modes/components/status-line/component.d.ts +1 -1
  56. package/dist/types/modes/components/tiny-title-download-progress.d.ts +1 -1
  57. package/dist/types/modes/components/transcript-container.d.ts +31 -26
  58. package/dist/types/modes/components/tree-selector.d.ts +1 -1
  59. package/dist/types/modes/components/user-message-selector.d.ts +1 -1
  60. package/dist/types/modes/components/user-message.d.ts +2 -1
  61. package/dist/types/modes/components/visual-truncate.d.ts +1 -1
  62. package/dist/types/modes/components/welcome.d.ts +19 -3
  63. package/dist/types/modes/controllers/mcp-command-controller.d.ts +1 -1
  64. package/dist/types/modes/controllers/streaming-reveal.d.ts +1 -1
  65. package/dist/types/modes/interactive-mode.d.ts +1 -1
  66. package/dist/types/modes/setup-wizard/scenes/sign-in.d.ts +1 -1
  67. package/dist/types/modes/setup-wizard/scenes/types.d.ts +1 -1
  68. package/dist/types/modes/setup-wizard/scenes/web-search.d.ts +1 -1
  69. package/dist/types/modes/setup-wizard/wizard-overlay.d.ts +1 -1
  70. package/dist/types/modes/types.d.ts +2 -1
  71. package/dist/types/session/agent-session.d.ts +1 -1
  72. package/dist/types/session/auth-broker-config.d.ts +4 -0
  73. package/dist/types/session/session-manager.d.ts +1 -1
  74. package/dist/types/slash-commands/helpers/stats-dashboard.d.ts +13 -0
  75. package/dist/types/ssh/connection-manager.d.ts +8 -0
  76. package/dist/types/task/discovery.d.ts +1 -2
  77. package/dist/types/task/parallel.d.ts +2 -2
  78. package/dist/types/task/worktree.d.ts +2 -0
  79. package/dist/types/tiny/title-client.d.ts +1 -1
  80. package/dist/types/tools/ask.d.ts +4 -0
  81. package/dist/types/tools/conflict-detect.d.ts +16 -0
  82. package/dist/types/tools/github-cache.d.ts +7 -0
  83. package/dist/types/tools/sqlite-reader.d.ts +3 -0
  84. package/dist/types/tools/todo.d.ts +2 -0
  85. package/dist/types/tui/output-block.d.ts +3 -3
  86. package/dist/types/utils/changelog.d.ts +8 -0
  87. package/dist/types/web/scrapers/readthedocs.d.ts +3 -0
  88. package/dist/types/web/scrapers/types.d.ts +12 -0
  89. package/dist/types/web/search/providers/codex.d.ts +1 -1
  90. package/dist/types/web/search/providers/gemini.d.ts +1 -1
  91. package/examples/extensions/tools.ts +5 -4
  92. package/package.json +14 -11
  93. package/scripts/build-binary.ts +18 -23
  94. package/scripts/bundle-dist.ts +81 -0
  95. package/scripts/{dev-launch → omp} +1 -1
  96. package/scripts/{dev-launch-preload.ts → omp.ts} +1 -1
  97. package/src/async/job-manager.ts +57 -3
  98. package/src/autoresearch/dashboard.ts +1 -1
  99. package/src/autoresearch/prompt-setup.md +6 -6
  100. package/src/autoresearch/prompt.md +6 -6
  101. package/src/capability/fs.ts +10 -0
  102. package/src/cli/args.ts +1 -1
  103. package/src/cli/auth-gateway-cli.ts +1 -3
  104. package/src/cli/dry-balance-cli.ts +1 -1
  105. package/src/cli/gallery-cli.ts +1 -1
  106. package/src/cli/gallery-fixtures/fs.ts +1 -1
  107. package/src/cli/gallery-fixtures/types.ts +5 -1
  108. package/src/cli/list-models.ts +7 -12
  109. package/src/cli/usage-cli.ts +603 -0
  110. package/src/cli-commands.ts +1 -0
  111. package/src/cli.ts +69 -5
  112. package/src/commands/complete.ts +1 -1
  113. package/src/commands/launch.ts +1 -1
  114. package/src/commands/read.ts +6 -3
  115. package/src/commands/usage.ts +35 -0
  116. package/src/commit/agentic/agent.ts +1 -1
  117. package/src/commit/model-selection.ts +1 -1
  118. package/src/config/append-only-context-mode.ts +6 -12
  119. package/src/config/model-discovery.ts +554 -0
  120. package/src/config/model-registry.ts +308 -1025
  121. package/src/config/model-resolver.ts +113 -156
  122. package/src/config/model-roles.ts +74 -0
  123. package/src/config/models-config-schema.ts +57 -8
  124. package/src/config/models-config.ts +129 -0
  125. package/src/config/settings-schema.ts +18 -14
  126. package/src/config/settings.ts +37 -1
  127. package/src/dap/client.ts +124 -37
  128. package/src/dap/session.ts +259 -158
  129. package/src/debug/log-viewer.ts +1 -1
  130. package/src/debug/raw-sse.ts +1 -1
  131. package/src/debug/terminal-info.ts +0 -3
  132. package/src/edit/diff.ts +95 -18
  133. package/src/edit/hashline/block-resolver.ts +20 -1
  134. package/src/edit/hashline/diff.ts +36 -1
  135. package/src/edit/hashline/execute.ts +8 -2
  136. package/src/edit/index.ts +16 -1
  137. package/src/edit/modes/patch.ts +52 -0
  138. package/src/edit/modes/replace.ts +56 -22
  139. package/src/edit/notebook.ts +22 -2
  140. package/src/edit/renderer.ts +36 -10
  141. package/src/eval/__tests__/completion-bridge.test.ts +1 -1
  142. package/src/eval/backend.ts +0 -2
  143. package/src/eval/completion-bridge.ts +2 -1
  144. package/src/eval/idle-timeout.ts +2 -9
  145. package/src/eval/js/context-manager.ts +6 -8
  146. package/src/eval/js/executor.ts +6 -2
  147. package/src/eval/js/index.ts +0 -2
  148. package/src/eval/js/shared/helpers.ts +5 -6
  149. package/src/eval/js/shared/local-module-loader.ts +1 -1
  150. package/src/eval/js/shared/prelude.txt +62 -1
  151. package/src/eval/js/shared/rewrite-imports.ts +49 -23
  152. package/src/eval/js/shared/runtime.ts +1 -1
  153. package/src/eval/py/index.ts +0 -2
  154. package/src/eval/py/kernel.ts +19 -0
  155. package/src/eval/py/runner.py +107 -3
  156. package/src/exec/bash-executor.ts +3 -1
  157. package/src/export/html/template.generated.ts +1 -1
  158. package/src/export/html/template.js +3 -1
  159. package/src/extensibility/extensions/types.ts +3 -2
  160. package/src/extensibility/plugins/legacy-pi-compat.ts +20 -3
  161. package/src/hindsight/mental-models.ts +59 -12
  162. package/src/hindsight/state.ts +6 -1
  163. package/src/internal-urls/artifact-protocol.ts +11 -2
  164. package/src/internal-urls/docs-index.generated.ts +10 -10
  165. package/src/internal-urls/issue-pr-protocol.ts +12 -5
  166. package/src/internal-urls/router.ts +1 -1
  167. package/src/internal-urls/types.ts +1 -1
  168. package/src/lib/xai-http.ts +1 -1
  169. package/src/lsp/client.ts +118 -38
  170. package/src/lsp/clients/biome-client.ts +101 -39
  171. package/src/lsp/edits.ts +143 -95
  172. package/src/lsp/index.ts +31 -22
  173. package/src/lsp/render.ts +1 -1
  174. package/src/lsp/types.ts +2 -0
  175. package/src/lsp/utils.ts +28 -10
  176. package/src/main.ts +165 -17
  177. package/src/mcp/json-rpc.ts +35 -5
  178. package/src/mcp/transports/stdio.ts +7 -1
  179. package/src/memories/index.ts +2 -1
  180. package/src/mnemopi/backend.ts +25 -3
  181. package/src/mnemopi/state.ts +38 -2
  182. package/src/modes/components/agent-dashboard.ts +10 -7
  183. package/src/modes/components/assistant-message.ts +19 -13
  184. package/src/modes/components/bash-execution.ts +1 -1
  185. package/src/modes/components/copy-selector.ts +1 -1
  186. package/src/modes/components/diff.ts +13 -2
  187. package/src/modes/components/dynamic-border.ts +12 -3
  188. package/src/modes/components/extensions/extension-dashboard.ts +8 -5
  189. package/src/modes/components/extensions/extension-list.ts +1 -1
  190. package/src/modes/components/extensions/inspector-panel.ts +1 -1
  191. package/src/modes/components/footer.ts +1 -1
  192. package/src/modes/components/history-search.ts +1 -1
  193. package/src/modes/components/hook-editor.ts +8 -0
  194. package/src/modes/components/hook-input.ts +8 -0
  195. package/src/modes/components/hook-selector.ts +2 -2
  196. package/src/modes/components/model-selector.ts +66 -54
  197. package/src/modes/components/plan-review-overlay.ts +1 -1
  198. package/src/modes/components/session-observer-overlay.ts +2 -2
  199. package/src/modes/components/session-selector.ts +1 -1
  200. package/src/modes/components/settings-selector.ts +5 -1
  201. package/src/modes/components/status-line/component.ts +1 -1
  202. package/src/modes/components/tiny-title-download-progress.ts +1 -1
  203. package/src/modes/components/transcript-container.ts +373 -141
  204. package/src/modes/components/tree-selector.ts +3 -3
  205. package/src/modes/components/user-message-selector.ts +1 -1
  206. package/src/modes/components/user-message.ts +17 -5
  207. package/src/modes/components/visual-truncate.ts +1 -1
  208. package/src/modes/components/welcome.ts +108 -26
  209. package/src/modes/controllers/command-controller.ts +10 -3
  210. package/src/modes/controllers/event-controller.ts +73 -49
  211. package/src/modes/controllers/input-controller.ts +5 -5
  212. package/src/modes/controllers/mcp-command-controller.ts +1 -1
  213. package/src/modes/controllers/selector-controller.ts +1 -5
  214. package/src/modes/controllers/streaming-reveal.ts +85 -18
  215. package/src/modes/interactive-mode.ts +5 -19
  216. package/src/modes/setup-wizard/scenes/glyph.ts +1 -1
  217. package/src/modes/setup-wizard/scenes/providers.ts +1 -1
  218. package/src/modes/setup-wizard/scenes/sign-in.ts +1 -1
  219. package/src/modes/setup-wizard/scenes/theme.ts +1 -1
  220. package/src/modes/setup-wizard/scenes/types.ts +1 -1
  221. package/src/modes/setup-wizard/scenes/web-search.ts +1 -1
  222. package/src/modes/setup-wizard/wizard-overlay.ts +1 -1
  223. package/src/modes/types.ts +2 -1
  224. package/src/prompts/agents/explore.md +2 -2
  225. package/src/prompts/agents/librarian.md +1 -2
  226. package/src/prompts/agents/oracle.md +1 -1
  227. package/src/prompts/agents/plan.md +5 -5
  228. package/src/prompts/agents/task.md +5 -5
  229. package/src/prompts/ci-green-request.md +5 -7
  230. package/src/prompts/goals/goal-budget-limit.md +2 -2
  231. package/src/prompts/goals/goal-continuation.md +4 -4
  232. package/src/prompts/goals/goal-mode-active.md +1 -1
  233. package/src/prompts/memories/read-path.md +1 -1
  234. package/src/prompts/memories/stage_one_system.md +2 -2
  235. package/src/prompts/review-custom-request.md +1 -1
  236. package/src/prompts/system/agent-creation-architect.md +2 -2
  237. package/src/prompts/system/auto-continue.md +1 -1
  238. package/src/prompts/system/background-tan-dispatch.md +1 -1
  239. package/src/prompts/system/btw-user.md +2 -2
  240. package/src/prompts/system/commit-message-system.md +13 -1
  241. package/src/prompts/system/custom-system-prompt.md +1 -1
  242. package/src/prompts/system/eager-todo.md +2 -2
  243. package/src/prompts/system/irc-incoming.md +1 -1
  244. package/src/prompts/system/manual-continue.md +1 -1
  245. package/src/prompts/system/omfg-user.md +3 -4
  246. package/src/prompts/system/orchestrate-notice.md +9 -9
  247. package/src/prompts/system/plan-mode-active.md +4 -4
  248. package/src/prompts/system/plan-mode-subagent.md +4 -5
  249. package/src/prompts/system/plan-mode-tool-decision-reminder.md +1 -1
  250. package/src/prompts/system/project-prompt.md +2 -2
  251. package/src/prompts/system/subagent-system-prompt.md +4 -4
  252. package/src/prompts/system/system-prompt.md +15 -26
  253. package/src/prompts/system/title-system.md +2 -2
  254. package/src/prompts/system/ttsr-tool-reminder.md +1 -1
  255. package/src/prompts/system/workflow-notice.md +1 -1
  256. package/src/prompts/tools/ast-edit.md +1 -1
  257. package/src/prompts/tools/ast-grep.md +2 -2
  258. package/src/prompts/tools/bash.md +8 -10
  259. package/src/prompts/tools/browser.md +7 -7
  260. package/src/prompts/tools/debug.md +1 -1
  261. package/src/prompts/tools/eval.md +3 -3
  262. package/src/prompts/tools/find.md +0 -1
  263. package/src/prompts/tools/github.md +8 -7
  264. package/src/prompts/tools/goal.md +1 -1
  265. package/src/prompts/tools/image-gen.md +1 -1
  266. package/src/prompts/tools/inspect-image-system.md +1 -1
  267. package/src/prompts/tools/irc.md +15 -15
  268. package/src/prompts/tools/lsp.md +2 -2
  269. package/src/prompts/tools/patch.md +2 -2
  270. package/src/prompts/tools/read.md +3 -4
  271. package/src/prompts/tools/recall.md +1 -1
  272. package/src/prompts/tools/reflect.md +1 -1
  273. package/src/prompts/tools/render-mermaid.md +2 -2
  274. package/src/prompts/tools/replace.md +4 -10
  275. package/src/prompts/tools/rewind.md +2 -2
  276. package/src/prompts/tools/search-tool-bm25.md +1 -9
  277. package/src/prompts/tools/search.md +0 -1
  278. package/src/prompts/tools/ssh.md +0 -4
  279. package/src/prompts/tools/task.md +2 -3
  280. package/src/prompts/tools/todo.md +6 -2
  281. package/src/sdk.ts +23 -10
  282. package/src/session/agent-session.ts +44 -10
  283. package/src/session/auth-broker-config.ts +30 -1
  284. package/src/session/session-manager.ts +2 -2
  285. package/src/session/streaming-output.ts +23 -2
  286. package/src/slash-commands/builtin-registry.ts +20 -0
  287. package/src/slash-commands/helpers/stats-dashboard.ts +85 -0
  288. package/src/ssh/connection-manager.ts +27 -0
  289. package/src/task/commands.ts +2 -1
  290. package/src/task/discovery.ts +17 -24
  291. package/src/task/executor.ts +61 -53
  292. package/src/task/index.ts +137 -60
  293. package/src/task/parallel.ts +3 -3
  294. package/src/task/render.ts +2 -2
  295. package/src/task/worktree.ts +64 -56
  296. package/src/thinking.ts +2 -1
  297. package/src/tiny/title-client.ts +32 -14
  298. package/src/tools/archive-reader.ts +30 -2
  299. package/src/tools/ask.ts +104 -21
  300. package/src/tools/ast-edit.ts +25 -5
  301. package/src/tools/auto-generated-guard.ts +20 -3
  302. package/src/tools/bash-interactive.ts +27 -7
  303. package/src/tools/bash.ts +54 -13
  304. package/src/tools/browser/launch.ts +11 -2
  305. package/src/tools/browser/readable.ts +19 -2
  306. package/src/tools/browser/registry.ts +4 -1
  307. package/src/tools/browser/render.ts +2 -2
  308. package/src/tools/browser/tab-supervisor.ts +55 -16
  309. package/src/tools/conflict-detect.ts +50 -4
  310. package/src/tools/debug.ts +1 -1
  311. package/src/tools/eval-render.ts +5 -5
  312. package/src/tools/eval.ts +0 -2
  313. package/src/tools/fetch.ts +33 -10
  314. package/src/tools/gh-cache-invalidation.ts +63 -8
  315. package/src/tools/gh-renderer.ts +1 -1
  316. package/src/tools/gh.ts +172 -29
  317. package/src/tools/github-cache.ts +70 -6
  318. package/src/tools/image-gen.ts +3 -9
  319. package/src/tools/irc.ts +5 -1
  320. package/src/tools/job.ts +1 -1
  321. package/src/tools/read.ts +202 -61
  322. package/src/tools/render-utils.ts +3 -3
  323. package/src/tools/resolve.ts +1 -1
  324. package/src/tools/search.ts +92 -29
  325. package/src/tools/sqlite-reader.ts +17 -5
  326. package/src/tools/ssh.ts +8 -8
  327. package/src/tools/todo.ts +51 -12
  328. package/src/tools/write.ts +118 -18
  329. package/src/tui/output-block.ts +4 -4
  330. package/src/utils/changelog.ts +27 -1
  331. package/src/utils/file-mentions.ts +2 -1
  332. package/src/web/scrapers/arxiv.ts +1 -1
  333. package/src/web/scrapers/go-pkg.ts +1 -1
  334. package/src/web/scrapers/iacr.ts +1 -1
  335. package/src/web/scrapers/readthedocs.ts +1 -1
  336. package/src/web/scrapers/twitter.ts +2 -1
  337. package/src/web/scrapers/types.ts +87 -8
  338. package/src/web/scrapers/wikipedia.ts +1 -1
  339. package/src/web/scrapers/youtube.ts +6 -1
  340. package/src/web/search/index.ts +1 -1
  341. package/src/web/search/providers/anthropic.ts +8 -2
  342. package/src/web/search/providers/codex.ts +2 -1
  343. package/src/web/search/providers/gemini.ts +2 -3
  344. package/src/web/search/render.ts +8 -6
  345. package/dist/types/config/model-equivalence.d.ts +0 -24
  346. package/dist/types/config/model-id-affixes.d.ts +0 -12
  347. package/dist/types/config/model-provider-priority.d.ts +0 -1
  348. package/dist/types/exec/idle-timeout-watchdog.d.ts +0 -18
  349. package/src/config/model-equivalence.ts +0 -875
  350. package/src/config/model-id-affixes.ts +0 -81
  351. package/src/config/model-provider-priority.ts +0 -56
  352. 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";
@@ -67,8 +70,14 @@ import { resolveResumableSession, type SessionInfo, SessionManager } from "./ses
67
70
  import { 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>;
@@ -78,6 +87,44 @@ type RunRpcMode = (
78
87
  setToolUIContext?: (uiContext: ExtensionUIContext, hasUI: boolean) => void,
79
88
  ) => Promise<never>;
80
89
 
90
+ function maybeShowStartupSplash(options: {
91
+ isInteractive: boolean;
92
+ resuming: boolean;
93
+ quiet: boolean;
94
+ version: string;
95
+ setupPending: boolean;
96
+ modelName?: string;
97
+ providerName?: string;
98
+ lspServers?: LspStartupServerInfo[];
99
+ }): void {
100
+ if (!options.isInteractive) return;
101
+ if (options.resuming || options.quiet) return;
102
+ if ($env.PI_TIMING) return;
103
+ if (!process.stdin.isTTY || !process.stdout.isTTY) return;
104
+ // First-run launches go straight into the setup wizard, which paints its own
105
+ // splash — keep the minimal two-line notice there.
106
+ if (options.setupPending) {
107
+ process.stdout.write(`${chalk.dim(`omp ${options.version}`)}\n${chalk.dim("Initializing session…")}\n`);
108
+ return;
109
+ }
110
+ // Render the same welcome box the TUI paints first: recent sessions as a
111
+ // loading placeholder (the fixed slot count keeps the box height stable) and
112
+ // the logo held on the intro animation's first frame so the in-TUI intro
113
+ // continues from the frame shown here. Clearing the screen first puts the
114
+ // box at the same origin the TUI's first full paint (clearScrollback) uses,
115
+ // so the live welcome replaces this frame in place without shifting.
116
+ const welcome = new WelcomeComponent(
117
+ options.version,
118
+ options.modelName ?? "",
119
+ options.providerName ?? "",
120
+ null,
121
+ options.lspServers ?? [],
122
+ );
123
+ welcome.holdIntroFirstFrame();
124
+ const lines = welcome.render(process.stdout.columns || 80);
125
+ process.stdout.write(`\x1b[2J\x1b[H\x1b[3J\n${lines.join("\n")}\n`);
126
+ }
127
+
81
128
  async function checkForNewVersion(currentVersion: string): Promise<string | undefined> {
82
129
  if (!settings.get("startup.checkUpdate")) {
83
130
  return;
@@ -143,15 +190,79 @@ function applyAcpDefaultSettingOverrides(targetSettings: Settings = settings): v
143
190
 
144
191
  async function readPipedInput(): Promise<string | undefined> {
145
192
  if (process.stdin.isTTY !== false) return undefined;
193
+ // stdin is a pipe: a producer that never writes nor closes would block
194
+ // startup forever with zero output. Say what we're blocked on after 1s.
195
+ const notice = setTimeout(() => {
196
+ process.stderr.write(`${chalk.dim("Reading prompt from piped stdin (waiting for EOF; ctrl+c to abort)…")}\n`);
197
+ }, 1000);
198
+ notice.unref?.();
146
199
  try {
147
200
  const text = await Bun.stdin.text();
148
201
  if (text.trim().length === 0) return undefined;
149
202
  return text;
150
203
  } catch {
151
204
  return undefined;
205
+ } finally {
206
+ clearTimeout(notice);
152
207
  }
153
208
  }
154
209
 
210
+ // ---------------------------------------------------------------------------
211
+ // Startup watchdog
212
+ // ---------------------------------------------------------------------------
213
+ // Speculative-hang reporter: until startup hands off to a mode runner, print a
214
+ // stderr line every 10s naming the deepest in-flight startup phase. Turns
215
+ // zero-output indefinite hangs (stuck discovery read, network wait, stdin
216
+ // pipe) into self-diagnosing reports instead of "it just hangs" (see the
217
+ // PI_DEBUG_STARTUP markers for the synchronous-hang counterpart).
218
+
219
+ const STARTUP_WATCHDOG_INTERVAL_MS = 10_000;
220
+ let startupWatchdogTimer: NodeJS.Timeout | undefined;
221
+ let startupWatchdogActive = false;
222
+ let startupWatchdogStartedAt = 0;
223
+
224
+ function armStartupWatchdog(): void {
225
+ if (startupWatchdogTimer) return;
226
+ startupWatchdogTimer = setInterval(() => {
227
+ const elapsed = Math.round((Date.now() - startupWatchdogStartedAt) / 1000);
228
+ const phase = logger.openSpanPath().join(" > ") || "module load / pre-phase work";
229
+ process.stderr.write(
230
+ `${chalk.yellow(`Still starting after ${elapsed}s`)}${chalk.dim(` — phase: ${phase}`)}\n` +
231
+ `${chalk.dim(` logs: ${getLogPath()} · re-run with PI_DEBUG_STARTUP=1 for streaming phase markers`)}\n`,
232
+ );
233
+ }, STARTUP_WATCHDOG_INTERVAL_MS);
234
+ startupWatchdogTimer.unref?.();
235
+ }
236
+
237
+ function disarmStartupWatchdog(): void {
238
+ if (!startupWatchdogTimer) return;
239
+ clearInterval(startupWatchdogTimer);
240
+ startupWatchdogTimer = undefined;
241
+ }
242
+
243
+ /** Begin watching startup (idempotent). */
244
+ function startStartupWatchdog(): void {
245
+ startupWatchdogActive = true;
246
+ startupWatchdogStartedAt = Date.now();
247
+ armStartupWatchdog();
248
+ }
249
+
250
+ /** Permanently stop watching: a mode runner now owns the terminal. */
251
+ function stopStartupWatchdog(): void {
252
+ startupWatchdogActive = false;
253
+ disarmStartupWatchdog();
254
+ }
255
+
256
+ /** Pause while an interactive prompt legitimately waits on the user. */
257
+ function pauseStartupWatchdog(): void {
258
+ disarmStartupWatchdog();
259
+ }
260
+
261
+ /** Resume after an interactive prompt, if startup is still being watched. */
262
+ function resumeStartupWatchdog(): void {
263
+ if (startupWatchdogActive) armStartupWatchdog();
264
+ }
265
+
155
266
  export interface InteractiveModeNotify {
156
267
  kind: "warn" | "error" | "info";
157
268
  message: string;
@@ -361,12 +472,14 @@ async function promptForkSession(session: SessionInfo): Promise<SessionPromptRes
361
472
  return "unavailable";
362
473
  }
363
474
  const message = `Session found in different project: ${session.cwd}. Fork into current directory? [y/N] `;
475
+ pauseStartupWatchdog();
364
476
  const rl = createInterface({ input: process.stdin, output: process.stdout });
365
477
  try {
366
478
  const answer = (await rl.question(message)).trim().toLowerCase();
367
479
  return answer === "y" || answer === "yes" ? "accepted" : "declined";
368
480
  } finally {
369
481
  rl.close();
482
+ resumeStartupWatchdog();
370
483
  }
371
484
  }
372
485
 
@@ -375,12 +488,14 @@ async function promptMoveSession(session: SessionInfo): Promise<SessionPromptRes
375
488
  return "unavailable";
376
489
  }
377
490
  const message = `Session's directory no longer exists (${session.cwd}). Move (re-root) it into the current directory? [Y/n] `;
491
+ pauseStartupWatchdog();
378
492
  const rl = createInterface({ input: process.stdin, output: process.stdout });
379
493
  try {
380
494
  const answer = (await rl.question(message)).trim().toLowerCase();
381
495
  return answer === "" || answer === "y" || answer === "yes" ? "accepted" : "declined";
382
496
  } finally {
383
497
  rl.close();
498
+ resumeStartupWatchdog();
384
499
  }
385
500
  }
386
501
 
@@ -437,7 +552,7 @@ async function getChangelogForDisplay(parsed: Args): Promise<string | undefined>
437
552
  return undefined;
438
553
  }
439
554
 
440
- const lastVersion = settings.get("lastChangelogVersion");
555
+ const lastVersion = await readLastChangelogVersion();
441
556
  if (lastVersion === VERSION) {
442
557
  // Steady state: user already saw the current version's changelog. Skip the file read + parse.
443
558
  return undefined;
@@ -448,15 +563,13 @@ async function getChangelogForDisplay(parsed: Args): Promise<string | undefined>
448
563
 
449
564
  if (!lastVersion) {
450
565
  if (entries.length > 0) {
451
- settings.set("lastChangelogVersion", VERSION);
452
- await flushChangelogVersion();
566
+ await writeLastChangelogVersion(VERSION);
453
567
  return entries.map(e => e.content).join("\n\n");
454
568
  }
455
569
  } else {
456
570
  const newEntries = getNewEntries(entries, lastVersion);
457
571
  if (newEntries.length > 0) {
458
- settings.set("lastChangelogVersion", VERSION);
459
- await flushChangelogVersion();
572
+ await writeLastChangelogVersion(VERSION);
460
573
  return newEntries.map(e => e.content).join("\n\n");
461
574
  }
462
575
  }
@@ -464,14 +577,6 @@ async function getChangelogForDisplay(parsed: Args): Promise<string | undefined>
464
577
  return undefined;
465
578
  }
466
579
 
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
580
  /** Resolves CLI session flags into an existing, forked, in-memory, or cancelled session manager. */
476
581
  export async function createSessionManager(
477
582
  parsed: Args,
@@ -792,6 +897,7 @@ export async function runRootCommand(
792
897
  deps: RunRootCommandDependencies = {},
793
898
  ): Promise<void> {
794
899
  logger.startTiming();
900
+ startStartupWatchdog();
795
901
 
796
902
  // Initialize theme early with defaults (CLI commands need symbols)
797
903
  // Will be re-initialized with user preferences later
@@ -803,7 +909,7 @@ export async function runRootCommand(
803
909
  const notifs: (InteractiveModeNotify | null)[] = [];
804
910
 
805
911
  // Create AuthStorage and ModelRegistry upfront
806
- const authStorage = await logger.time("discoverModels", deps.discoverAuthStorage ?? discoverAuthStorage);
912
+ const authStorage = await logger.time("discoverAuthStorage", deps.discoverAuthStorage ?? discoverAuthStorage);
807
913
  const modelRegistry = new ModelRegistry(authStorage);
808
914
 
809
915
  if (parsedArgs.version) {
@@ -991,10 +1097,12 @@ export async function runRootCommand(
991
1097
  }
992
1098
  startInAllScope = true;
993
1099
  }
1100
+ pauseStartupWatchdog();
994
1101
  const selected = await logger.time("selectSession", selectSession, folderSessions, {
995
1102
  allSessions: preloadedAllSessions,
996
1103
  startInAllScope,
997
1104
  });
1105
+ resumeStartupWatchdog();
998
1106
  if (!selected) {
999
1107
  process.stdout.write(`${chalk.dim("No session selected")}\n`);
1000
1108
  return;
@@ -1086,6 +1194,7 @@ export async function runRootCommand(
1086
1194
  });
1087
1195
  // Branch-only protocol runner: keep ACP server code out of normal interactive startup.
1088
1196
  const runAcpMode = deps.runAcpMode ?? (await import("./modes/acp/acp-mode")).runAcpMode;
1197
+ stopStartupWatchdog();
1089
1198
  await runAcpMode(createAcpSession);
1090
1199
  } else {
1091
1200
  // Resolve extension-registered CLI flags before creating the session so a
@@ -1119,6 +1228,42 @@ export async function runRootCommand(
1119
1228
  stdinContent: pipedInput,
1120
1229
  });
1121
1230
 
1231
+ // Resolve the model the session will most likely start with so the splash
1232
+ // box matches the final welcome screen (the raw role selector, e.g.
1233
+ // "anthropic/claude-fable-5:high", is wider than the left column and would
1234
+ // collapse the box into the single-column layout).
1235
+ let splashModel = sessionOptions.model;
1236
+ if (!splashModel) {
1237
+ const remembered = settingsInstance.getModelRole("default");
1238
+ if (remembered) {
1239
+ splashModel = resolveModelRoleValue(remembered, modelRegistry.getAll(), {
1240
+ settings: settingsInstance,
1241
+ matchPreferences: modelMatchPreferences,
1242
+ modelRegistry,
1243
+ }).model;
1244
+ }
1245
+ }
1246
+ // Mirror createAgentSession's startup LSP discovery (sync and cheap: root
1247
+ // markers + binary lookup) so the splash lists the same servers the live
1248
+ // welcome screen will show.
1249
+ const splashLspServers =
1250
+ (sessionOptions.enableLsp ?? true)
1251
+ ? discoverStartupLspServers(
1252
+ sessionOptions.cwd ?? cwd,
1253
+ settingsInstance.get("lsp.lazy") ? "available" : "connecting",
1254
+ )
1255
+ : [];
1256
+ maybeShowStartupSplash({
1257
+ isInteractive,
1258
+ resuming: Boolean(parsedArgs.continue || parsedArgs.resume || parsedArgs.fork),
1259
+ quiet: settingsInstance.get("startup.quiet"),
1260
+ version: VERSION,
1261
+ setupPending: deps.forceSetupWizard === true || settingsInstance.get("setupVersion") < CURRENT_SETUP_VERSION,
1262
+ modelName: splashModel?.name,
1263
+ providerName: splashModel?.provider,
1264
+ lspServers: splashLspServers,
1265
+ });
1266
+
1122
1267
  const { session, setToolUIContext, modelFallbackMessage, lspServers, mcpManager } = await createSession({
1123
1268
  ...sessionOptions,
1124
1269
  eventBus,
@@ -1152,6 +1297,7 @@ export async function runRootCommand(
1152
1297
  if (mode === "rpc" || mode === "rpc-ui") {
1153
1298
  // Branch-only protocol runner: keep RPC host code out of normal interactive startup.
1154
1299
  const runRpcMode: RunRpcMode = (await import("./modes/rpc/rpc-mode")).runRpcMode;
1300
+ stopStartupWatchdog();
1155
1301
  await runRpcMode(session, mode === "rpc-ui" ? setToolUIContext : undefined);
1156
1302
  } else if (isInteractive) {
1157
1303
  const versionCheckPromise = checkForNewVersion(VERSION).catch(() => undefined);
@@ -1175,6 +1321,7 @@ export async function runRootCommand(
1175
1321
  }
1176
1322
  }
1177
1323
 
1324
+ stopStartupWatchdog();
1178
1325
  logger.endTiming();
1179
1326
  await runInteractiveMode(
1180
1327
  session,
@@ -1194,6 +1341,7 @@ export async function runRootCommand(
1194
1341
  );
1195
1342
  } else {
1196
1343
  // Branch-only single-shot runner: keep print-mode code out of normal interactive startup.
1344
+ stopStartupWatchdog();
1197
1345
  const runPrintMode: RunPrintMode = (await import("./modes/print-mode")).runPrintMode;
1198
1346
  await runPrintMode(session, {
1199
1347
  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";
@@ -1,9 +1,9 @@
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
8
 
9
9
  import type { ModelRegistry } from "../config/model-registry";
@@ -25,10 +25,25 @@ import {
25
25
  getMnemopiScopedBanks,
26
26
  getMnemopiScopedDbPaths,
27
27
  getMnemopiSessionState,
28
+ loadMnemopi,
29
+ loadMnemopiCore,
28
30
  MnemopiSessionState,
31
+ requireMnemopi,
32
+ requireMnemopiCore,
29
33
  setMnemopiSessionState,
30
34
  } from "./state";
31
35
 
36
+ // `/diagnose` is the only user of this subpath; load it lazily alongside the
37
+ // loaders in ./state to keep mnemopi off the CLI startup module graph.
38
+ let mnemopiDiagnoseMod: typeof MnemopiDiagnoseNs | undefined;
39
+
40
+ async function loadMnemopiDiagnose(): Promise<typeof MnemopiDiagnoseNs> {
41
+ if (!mnemopiDiagnoseMod) {
42
+ mnemopiDiagnoseMod = await import("@oh-my-pi/pi-mnemopi/diagnose");
43
+ }
44
+ return mnemopiDiagnoseMod;
45
+ }
46
+
32
47
  const STATIC_INSTRUCTIONS = [
33
48
  "# Memory",
34
49
  "This agent has local Mnemopi long-term memory.",
@@ -68,6 +83,7 @@ export const mnemopiBackend: MemoryBackend = {
68
83
 
69
84
  try {
70
85
  const config = await loadMnemopiConfigWithProviders(settings, agentDir, modelRegistry, sessionId);
86
+ await Promise.all([loadMnemopi(), loadMnemopiCore()]);
71
87
  const state = new MnemopiSessionState({ sessionId, config, session });
72
88
  const previous = setMnemopiSessionState(session, state);
73
89
  previous?.dispose();
@@ -97,6 +113,7 @@ export const mnemopiBackend: MemoryBackend = {
97
113
  previous?.dispose();
98
114
  const config = previous?.config ?? (session ? loadMnemopiConfig(session.settings, agentDir) : undefined);
99
115
  if (!config) return;
116
+ await loadMnemopiCore();
100
117
  await removeDbFiles(getMnemopiScopedDbPaths(config));
101
118
  },
102
119
 
@@ -110,6 +127,7 @@ export const mnemopiBackend: MemoryBackend = {
110
127
  session.modelRegistry,
111
128
  session.sessionId,
112
129
  );
130
+ await Promise.all([loadMnemopi(), loadMnemopiCore()]);
113
131
  state = new MnemopiSessionState({ sessionId: session.sessionId, config, session });
114
132
  setMnemopiSessionState(session, state);
115
133
  }
@@ -124,6 +142,7 @@ export const mnemopiBackend: MemoryBackend = {
124
142
  },
125
143
 
126
144
  async stats(agentDir, _cwd, session): Promise<string | undefined> {
145
+ await Promise.all([loadMnemopi(), loadMnemopiCore()]);
127
146
  const { targets, owned } = createStatsTargets(agentDir, session);
128
147
  try {
129
148
  if (targets.length === 0) return undefined;
@@ -137,6 +156,7 @@ export const mnemopiBackend: MemoryBackend = {
137
156
  const state = getMnemopiSessionState(session);
138
157
  const config = state?.config ?? (session ? loadMnemopiConfig(session.settings, agentDir) : undefined);
139
158
  if (!config) return undefined;
159
+ const [{ inspectDatabase }] = await Promise.all([loadMnemopiDiagnose(), loadMnemopiCore()]);
140
160
  const banks = getMnemopiScopedBanks(config);
141
161
  const dbPaths = getMnemopiScopedDbPaths(config);
142
162
  const summaries = dbPaths.map((dbPath, index) => ({
@@ -179,6 +199,7 @@ function createStatsTargets(
179
199
 
180
200
  function createStatsMemory(config: MnemopiBackendConfig, bank: string): Mnemopi {
181
201
  const providerOptions = config.providerOptions as Record<string, unknown>;
202
+ const { Mnemopi } = requireMnemopi();
182
203
  return new Mnemopi({
183
204
  dbPath: resolveBankDbPath(config, bank),
184
205
  bank,
@@ -193,6 +214,7 @@ function createStatsMemory(config: MnemopiBackendConfig, bank: string): Mnemopi
193
214
  function resolveBankDbPath(config: MnemopiBackendConfig, bank: string): string {
194
215
  const sharedBank = config.globalBank ?? config.baseBank ?? "default";
195
216
  if (bank === sharedBank) return config.dbPath;
217
+ const { BankManager } = requireMnemopiCore();
196
218
  return new BankManager(path.dirname(config.dbPath)).getBankDbPath(bank);
197
219
  }
198
220
 
@@ -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
 
@@ -194,7 +194,7 @@ class AgentListPane implements Component {
194
194
  private readonly maxVisible: number,
195
195
  ) {}
196
196
 
197
- render(width: number): string[] {
197
+ render(width: number): readonly string[] {
198
198
  const lines: string[] = [];
199
199
  const searchPrefix = theme.fg("muted", "Search: ");
200
200
  const searchText = this.searchQuery || theme.fg("dim", "type to filter");
@@ -255,7 +255,7 @@ class AgentInspectorPane implements Component {
255
255
  private readonly effectiveResolution: ModelResolution | undefined,
256
256
  ) {}
257
257
 
258
- render(width: number): string[] {
258
+ render(width: number): readonly string[] {
259
259
  if (!this.agent) {
260
260
  return [theme.fg("muted", "Select an agent"), theme.fg("dim", "to inspect settings")];
261
261
  }
@@ -314,7 +314,7 @@ class TwoColumnBody implements Component {
314
314
  private readonly maxHeight: number,
315
315
  ) {}
316
316
 
317
- render(width: number): string[] {
317
+ render(width: number): readonly string[] {
318
318
  const leftWidth = Math.floor(width * 0.5);
319
319
  const rightWidth = width - leftWidth - 3;
320
320
  const leftLines = this.leftPane.render(leftWidth);
@@ -507,7 +507,7 @@ export class AgentDashboard extends Container {
507
507
  return Math.max(3, this.#computeBodyHeight() - 3);
508
508
  }
509
509
 
510
- override render(width: number): string[] {
510
+ override render(width: number): readonly string[] {
511
511
  // Rebuild when terminal geometry changes so the full-screen overlay
512
512
  // re-fits on resize.
513
513
  if (this.#terminalRows() !== this.#builtRows || this.#uiWidth() !== this.#builtCols) {
@@ -516,10 +516,13 @@ export class AgentDashboard extends Container {
516
516
  const lines = super.render(width);
517
517
  // Pad to the full viewport so every state (list, edit, create) covers the
518
518
  // screen as a true full-screen view instead of letting the transcript peek
519
- // through below it.
519
+ // through below it. Copy before padding — the container's render result is
520
+ // component-owned and must not be mutated.
520
521
  const rows = this.#terminalRows();
521
- while (lines.length < rows) lines.push("");
522
- return lines;
522
+ if (lines.length >= rows) return lines;
523
+ const padded = lines.slice();
524
+ while (padded.length < rows) padded.push("");
525
+ return padded;
523
526
  }
524
527
 
525
528
  #clampSelection(): void {