@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
@@ -11,6 +11,16 @@ export const DEFAULT_MAX_LINES = 3000;
11
11
  export const DEFAULT_MAX_BYTES = 50 * 1024; // 50KB
12
12
  export const DEFAULT_MAX_COLUMN = 512; // Max chars per grep match line
13
13
 
14
+ /**
15
+ * Default artifact-on-disk cap for {@link OutputSink}.
16
+ *
17
+ * `0` means unbounded: by default, `artifact://<id>` references preserve the
18
+ * complete raw stream instead of a capped head/tail sample.
19
+ */
20
+ export const ARTIFACT_DEFAULT_MAX_BYTES = 0;
21
+ /** Default head budget; the remainder becomes the rolling tail window. */
22
+ export const ARTIFACT_DEFAULT_HEAD_BYTES = 3 * 1024 * 1024; // 3 MiB
23
+
14
24
  const NL = "\n";
15
25
  const ELLIPSIS = "…";
16
26
 
@@ -58,6 +68,20 @@ export interface OutputSinkOptions {
58
68
  onChunk?: (chunk: string) => void;
59
69
  /** Minimum ms between onChunk calls. 0 = every chunk (default). */
60
70
  chunkThrottleMs?: number;
71
+ /**
72
+ * Optional cap on bytes written to the artifact-on-disk file. When the cap
73
+ * is hit, the head window is preserved verbatim and subsequent output feeds
74
+ * a rolling tail window; on close, the sink writes a single
75
+ * `[ARTIFACT TRUNCATED: …]` notice between them. Default
76
+ * {@link ARTIFACT_DEFAULT_MAX_BYTES} (unbounded).
77
+ */
78
+ artifactMaxBytes?: number;
79
+ /**
80
+ * Bytes reserved for the head window of the capped artifact file. The
81
+ * tail window receives `artifactMaxBytes - artifactHeadBytes`. Default
82
+ * {@link ARTIFACT_DEFAULT_HEAD_BYTES}; clamped to `[0, artifactMaxBytes]`.
83
+ */
84
+ artifactHeadBytes?: number;
61
85
  }
62
86
 
63
87
  export interface TruncationResult {
@@ -650,6 +674,7 @@ export class OutputSink {
650
674
  #sawData = false;
651
675
  #truncated = false;
652
676
  #lastChunkTime = 0;
677
+ #pendingChunk = "";
653
678
 
654
679
  // Per-line column cap streaming state (persists across `push` calls so a
655
680
  // long line split across chunks still trips the same trigger).
@@ -675,6 +700,21 @@ export class OutputSink {
675
700
  readonly #chunkThrottleMs: number;
676
701
  readonly #maxColumns: number;
677
702
 
703
+ // Optional artifact-on-disk cap. When `#artifactMaxBytes > 0` the file sink
704
+ // owns a head budget + a rolling tail buffer; once the head is closed,
705
+ // subsequent chunks are diverted into `#artifactTailRing` (bounded by
706
+ // `#artifactTailBudget`). On `dump()` the tail is flushed back to the sink
707
+ // behind a `[ARTIFACT TRUNCATED: …]` notice. The default cap is disabled so
708
+ // advertised `artifact://<id>` captures are lossless.
709
+ readonly #artifactMaxBytes: number;
710
+ readonly #artifactHeadBudget: number;
711
+ readonly #artifactTailBudget: number;
712
+ #artifactHeadBytesWritten = 0;
713
+ #artifactHeadClosed = false;
714
+ #artifactTailRing = "";
715
+ #artifactTailRingBytes = 0;
716
+ #artifactTailIncomingBytes = 0;
717
+
678
718
  constructor(options?: OutputSinkOptions) {
679
719
  const {
680
720
  artifactPath,
@@ -684,6 +724,8 @@ export class OutputSink {
684
724
  maxColumns = 0,
685
725
  onChunk,
686
726
  chunkThrottleMs = 0,
727
+ artifactMaxBytes = ARTIFACT_DEFAULT_MAX_BYTES,
728
+ artifactHeadBytes = ARTIFACT_DEFAULT_HEAD_BYTES,
687
729
  } = options ?? {};
688
730
  this.#artifactPath = artifactPath;
689
731
  this.#artifactId = artifactId;
@@ -692,6 +734,9 @@ export class OutputSink {
692
734
  this.#maxColumns = Math.max(0, maxColumns);
693
735
  this.#onChunk = onChunk;
694
736
  this.#chunkThrottleMs = chunkThrottleMs;
737
+ this.#artifactMaxBytes = Math.max(0, artifactMaxBytes);
738
+ this.#artifactHeadBudget = Math.max(0, Math.min(artifactHeadBytes, this.#artifactMaxBytes));
739
+ this.#artifactTailBudget = Math.max(0, this.#artifactMaxBytes - this.#artifactHeadBudget);
695
740
  }
696
741
 
697
742
  /**
@@ -701,14 +746,20 @@ export class OutputSink {
701
746
  push(chunk: string): void {
702
747
  chunk = sanitizeWithOptionalSixelPassthrough(chunk, sanitizeText);
703
748
 
704
- // Throttled onChunk: only call the callback when enough time has passed.
749
+ // Throttled onChunk: coalesce chunks arriving inside the throttle window
750
+ // and flush the buffered concatenation on the next eligible tick (plus a
751
+ // final flush in dump()) so the preview never has silent gaps.
705
752
  // Live preview gets the raw (pre-cap) chunk so the TUI never lags behind
706
753
  // what reached the sink — the column cap is for the persisted LLM view.
707
754
  if (this.#onChunk) {
708
755
  const now = Date.now();
709
756
  if (now - this.#lastChunkTime >= this.#chunkThrottleMs) {
710
757
  this.#lastChunkTime = now;
711
- this.#onChunk(chunk);
758
+ const merged = this.#pendingChunk + chunk;
759
+ this.#pendingChunk = "";
760
+ this.#onChunk(merged);
761
+ } else {
762
+ this.#pendingChunk += chunk;
712
763
  }
713
764
  }
714
765
 
@@ -858,14 +909,18 @@ export class OutputSink {
858
909
  /**
859
910
  * Write a chunk to the artifact file. Handles the async file sink creation
860
911
  * by queuing writes until the sink is ready, then draining synchronously.
912
+ * Once the sink is up, every byte flows through {@link #emitToSink} which
913
+ * owns the head + tail cap so artifacts cannot grow beyond
914
+ * `#artifactMaxBytes` on disk.
861
915
  */
862
916
  #writeToFile(chunk: string): void {
863
917
  if (this.#fileReady && this.#file) {
864
- // Fast path: file sink exists, write synchronously
865
- this.#file.sink.write(chunk);
918
+ this.#emitToSink(chunk);
866
919
  return;
867
920
  }
868
- // File sink not yet created — queue this chunk and kick off creation
921
+ // File sink not yet created — queue this chunk and kick off creation.
922
+ // The queue is bounded only by how many chunks arrive before the open
923
+ // resolves (typically <2). The cap is enforced on drain.
869
924
  if (!this.#pendingFileWrites) {
870
925
  this.#pendingFileWrites = [chunk];
871
926
  void this.#createFileSink();
@@ -874,26 +929,99 @@ export class OutputSink {
874
929
  }
875
930
  }
876
931
 
932
+ /**
933
+ * Cap-aware sink writer. Bytes flow into the head window verbatim until the
934
+ * budget is exhausted; subsequent bytes are diverted into a rolling tail
935
+ * ring, evicted from the front so total RAM stays bounded by
936
+ * `#artifactTailBudget`. `dump()` replays the ring behind a single notice
937
+ * line before closing the sink.
938
+ *
939
+ * When the cap is disabled (`#artifactMaxBytes === 0`) this collapses to a
940
+ * straight pass-through, preserving the historical "stream everything"
941
+ * contract.
942
+ */
943
+ #emitToSink(chunk: string): void {
944
+ if (!this.#file || chunk.length === 0) return;
945
+ if (this.#artifactMaxBytes === 0) {
946
+ this.#file.sink.write(chunk);
947
+ return;
948
+ }
949
+ const chunkBytes = Buffer.byteLength(chunk, "utf-8");
950
+ const room = this.#artifactHeadClosed ? 0 : this.#artifactHeadBudget - this.#artifactHeadBytesWritten;
951
+ if (room >= chunkBytes) {
952
+ this.#file.sink.write(chunk);
953
+ this.#artifactHeadBytesWritten += chunkBytes;
954
+ return;
955
+ }
956
+ let overflow = chunk;
957
+ if (room > 0) {
958
+ const headSlice = truncateHeadBytes(chunk, room);
959
+ if (headSlice.bytes > 0) {
960
+ this.#file.sink.write(headSlice.text);
961
+ this.#artifactHeadBytesWritten += headSlice.bytes;
962
+ }
963
+ // Even when UTF-8 boundary safety leaves a few bytes of nominal room,
964
+ // this chunk has already overflowed the head window. Close it now so a
965
+ // later small ASCII chunk cannot be written before this overflow tail.
966
+ this.#artifactHeadClosed = true;
967
+ overflow = chunk.substring(headSlice.text.length);
968
+ }
969
+ if (overflow.length === 0 || this.#artifactTailBudget === 0) {
970
+ // No tail budget: count the dropped bytes so the notice reflects them.
971
+ if (overflow.length > 0) {
972
+ this.#artifactTailIncomingBytes += Buffer.byteLength(overflow, "utf-8");
973
+ }
974
+ return;
975
+ }
976
+ this.#pushArtifactTail(overflow);
977
+ }
978
+
979
+ #pushArtifactTail(chunk: string): void {
980
+ const chunkBytes = Buffer.byteLength(chunk, "utf-8");
981
+ this.#artifactTailIncomingBytes += chunkBytes;
982
+ const budget = this.#artifactTailBudget;
983
+ if (chunkBytes >= budget) {
984
+ // Chunk alone dominates — keep only its tail slice.
985
+ const { text, bytes } = truncateTailBytes(chunk, budget);
986
+ this.#artifactTailRing = text;
987
+ this.#artifactTailRingBytes = bytes;
988
+ return;
989
+ }
990
+ this.#artifactTailRing += chunk;
991
+ this.#artifactTailRingBytes += chunkBytes;
992
+ if (this.#artifactTailRingBytes > budget) {
993
+ const { text, bytes } = truncateTailBytes(this.#artifactTailRing, budget);
994
+ this.#artifactTailRing = text;
995
+ this.#artifactTailRingBytes = bytes;
996
+ }
997
+ }
998
+
877
999
  async #createFileSink(): Promise<void> {
878
1000
  if (!this.#artifactPath || this.#fileReady) return;
879
1001
  try {
880
1002
  const sink = Bun.file(this.#artifactPath).writer();
881
1003
  this.#file = { path: this.#artifactPath, artifactId: this.#artifactId, sink };
1004
+ this.#fileReady = true;
1005
+
1006
+ // Head-retained bytes precede the rolling tail buffer in the capture.
1007
+ // Route through #emitToSink so they count against the artifact head
1008
+ // budget — a direct sink.write would let them escape the cap.
1009
+ if (this.#head.length > 0) {
1010
+ this.#emitToSink(this.#head);
1011
+ }
882
1012
 
883
1013
  // Flush existing buffer to file BEFORE it gets trimmed further.
884
1014
  if (this.#buffer.length > 0) {
885
- sink.write(this.#buffer);
1015
+ this.#emitToSink(this.#buffer);
886
1016
  }
887
1017
 
888
- // Drain any chunks that arrived while the sink was being created
1018
+ // Drain any chunks that arrived while the sink was being created.
889
1019
  if (this.#pendingFileWrites) {
890
1020
  for (const pending of this.#pendingFileWrites) {
891
- sink.write(pending);
1021
+ this.#emitToSink(pending);
892
1022
  }
893
1023
  this.#pendingFileWrites = undefined;
894
1024
  }
895
-
896
- this.#fileReady = true;
897
1025
  } catch {
898
1026
  try {
899
1027
  await this.#file?.sink?.end();
@@ -902,6 +1030,7 @@ export class OutputSink {
902
1030
  }
903
1031
  this.#file = undefined;
904
1032
  this.#pendingFileWrites = undefined;
1033
+ this.#fileReady = false;
905
1034
  }
906
1035
  }
907
1036
 
@@ -946,13 +1075,61 @@ export class OutputSink {
946
1075
  this.#columnEllipsisAdded = false;
947
1076
  this.#columnDroppedBytes = 0;
948
1077
  this.#columnTruncatedLines = 0;
1078
+ this.#pendingChunk = "";
1079
+ }
1080
+
1081
+ /**
1082
+ * Replay the rolling tail ring back into the artifact sink. When bytes
1083
+ * were actually dropped from the middle (the head budget was exhausted
1084
+ * *and* the tail ring evicted), a single `[ARTIFACT TRUNCATED: …]`
1085
+ * notice is injected between head and tail so a reader of
1086
+ * `artifact://<id>` understands the gap. When the total stream simply
1087
+ * spilled past the head budget but still fits below `artifactMaxBytes`,
1088
+ * `droppedBytes` is zero — head + tail together are the verbatim stream
1089
+ * and the notice is suppressed so we don't corrupt the artifact with a
1090
+ * misleading "0 B elided" marker (PR #2083 review by codex).
1091
+ *
1092
+ * No-op when the cap was never hit at all (head budget never exhausted,
1093
+ * tail ring empty).
1094
+ */
1095
+ #flushArtifactTailIfCapped(): void {
1096
+ if (!this.#file) return;
1097
+ if (this.#artifactMaxBytes === 0) return;
1098
+ const tailBytes = this.#artifactTailRingBytes;
1099
+ const droppedBytes = Math.max(0, this.#artifactTailIncomingBytes - tailBytes);
1100
+ if (tailBytes === 0 && droppedBytes === 0) return;
1101
+
1102
+ if (droppedBytes > 0) {
1103
+ const headWritten = this.#artifactHeadBytesWritten;
1104
+ const totalCapped = headWritten + this.#artifactTailIncomingBytes;
1105
+ const headSep = headWritten > 0 ? "\n" : "";
1106
+ const tailSep = tailBytes > 0 && !this.#artifactTailRing.startsWith("\n") ? "\n" : "";
1107
+ const notice =
1108
+ `${headSep}[ARTIFACT TRUNCATED: kept first ${formatBytes(headWritten)} + last ${formatBytes(tailBytes)} ` +
1109
+ `of ${formatBytes(totalCapped)}; ${formatBytes(droppedBytes)} elided from the middle]${tailSep}`;
1110
+ this.#file.sink.write(notice);
1111
+ }
1112
+ if (tailBytes > 0) {
1113
+ this.#file.sink.write(this.#artifactTailRing);
1114
+ }
949
1115
  }
950
1116
 
951
1117
  async dump(notice?: string): Promise<OutputSummary> {
952
1118
  const noticeLine = notice ? `[${notice}]\n` : "";
1119
+
1120
+ // Flush any chunk still held back by the throttle so the live preview
1121
+ // ends with the complete stream.
1122
+ if (this.#onChunk && this.#pendingChunk.length > 0) {
1123
+ const pending = this.#pendingChunk;
1124
+ this.#pendingChunk = "";
1125
+ this.#onChunk(pending);
1126
+ }
953
1127
  const totalLines = this.#sawData ? this.#totalLines + 1 : 0;
954
1128
 
955
- if (this.#file) await this.#file.sink.end();
1129
+ if (this.#file) {
1130
+ this.#flushArtifactTailIfCapped();
1131
+ await this.#file.sink.end();
1132
+ }
956
1133
 
957
1134
  // Compose the visible output. With head retention, splice head + marker
958
1135
  // + tail when content was elided. Otherwise return the rolling buffer.
@@ -5,6 +5,30 @@ import type { AcpBuiltinSlashCommandResult, SlashCommandRuntime } from "./types"
5
5
 
6
6
  export type { AcpBuiltinSlashCommandResult } from "./types";
7
7
 
8
+ /**
9
+ * All names (primary + aliases) that are reserved by ACP builtins. Used to
10
+ * filter out extension commands that would shadow a builtin or its alias at
11
+ * dispatch time (e.g. `models` is an alias for `/model`, so an extension
12
+ * registering `models` would appear in the palette but execute the builtin).
13
+ */
14
+ export const ACP_BUILTIN_RESERVED_NAMES: ReadonlySet<string> = new Set(
15
+ BUILTIN_SLASH_COMMANDS_INTERNAL.filter(c => c.handle !== undefined).flatMap(c => [c.name, ...(c.aliases ?? [])]),
16
+ );
17
+
18
+ /**
19
+ * Whether an extension command named `name` would be captured by ACP builtin
20
+ * dispatch before reaching the extension handler. Beyond exact name/alias
21
+ * collisions, `parseSlashCommand` treats `:` as a name/args separator, so a
22
+ * colon-namespaced name whose prefix is a handled builtin (e.g. `model:foo`)
23
+ * executes the `/model` builtin with `foo` as args. Such names must not be
24
+ * advertised to ACP clients.
25
+ */
26
+ export function isAcpBuiltinShadowedName(name: string): boolean {
27
+ if (ACP_BUILTIN_RESERVED_NAMES.has(name)) return true;
28
+ const colon = name.indexOf(":");
29
+ return colon !== -1 && ACP_BUILTIN_RESERVED_NAMES.has(name.slice(0, colon));
30
+ }
31
+
8
32
  /**
9
33
  * Commands advertised to ACP clients. Entries without a text-mode `handle`
10
34
  * (e.g. `/quit`, `/login`, dashboards) are filtered out so the client doesn't
@@ -30,6 +30,7 @@ import { createMarketplaceManager } from "./helpers/marketplace-manager";
30
30
  import { handleMcpAcp } from "./helpers/mcp";
31
31
  import { commandConsumed, errorMessage, parseSlashCommand, parseSubcommand, usage } from "./helpers/parse";
32
32
  import { handleSshAcp } from "./helpers/ssh";
33
+ import { launchStatsDashboard, parseStatsDashboardArgs } from "./helpers/stats-dashboard";
33
34
  import { handleTodoAcp } from "./helpers/todo";
34
35
  import { buildUsageReportText } from "./helpers/usage-report";
35
36
  import { parseMarketplaceInstallArgs, parsePluginScopeArgs } from "./marketplace-install-parser";
@@ -81,6 +82,23 @@ const BUILTIN_SLASH_COMMAND_REGISTRY: ReadonlyArray<SlashCommandSpec> = [
81
82
  runtime.ctx.editor.setText("");
82
83
  },
83
84
  },
85
+ {
86
+ name: "setup",
87
+ aliases: ["providers"],
88
+ description: "Open provider setup",
89
+ allowArgs: true,
90
+ subcommands: [{ name: "providers", description: "Configure sign-in and web search providers" }],
91
+ handleTui: async (command, runtime) => {
92
+ const args = command.args.trim().toLowerCase();
93
+ const opensProviders = args === "" || args === "providers";
94
+ if (opensProviders) {
95
+ await runtime.ctx.showProviderSetup();
96
+ } else {
97
+ runtime.ctx.showWarning(`Usage: /${command.name} [providers]`);
98
+ }
99
+ runtime.ctx.editor.setText("");
100
+ },
101
+ },
84
102
  {
85
103
  name: "plan",
86
104
  description: "Toggle plan mode (agent plans before executing)",
@@ -542,6 +560,25 @@ const BUILTIN_SLASH_COMMAND_REGISTRY: ReadonlyArray<SlashCommandSpec> = [
542
560
  runtime.ctx.editor.setText("");
543
561
  },
544
562
  },
563
+ {
564
+ name: "stats",
565
+ description: "Launch the local stats dashboard",
566
+ inlineHint: "[--port <port>]",
567
+ allowArgs: true,
568
+ handle: async (command, runtime) => {
569
+ const parsed = parseStatsDashboardArgs(command.args);
570
+ if ("error" in parsed) return usage(parsed.error, runtime);
571
+
572
+ await runtime.output("Syncing session files...");
573
+ try {
574
+ const result = await launchStatsDashboard(parsed);
575
+ await runtime.output(result.message);
576
+ } catch (error) {
577
+ await runtime.output(`Stats dashboard failed: ${errorMessage(error)}`);
578
+ }
579
+ return commandConsumed();
580
+ },
581
+ },
545
582
  {
546
583
  name: "changelog",
547
584
  description: "Show changelog entries",
@@ -1677,10 +1714,13 @@ for (const command of BUILTIN_SLASH_COMMAND_REGISTRY) {
1677
1714
  }
1678
1715
  }
1679
1716
 
1717
+ export const BUILTIN_SLASH_COMMAND_RESERVED_NAMES: ReadonlySet<string> = new Set(BUILTIN_SLASH_COMMAND_LOOKUP.keys());
1718
+
1680
1719
  /** Builtin command metadata used for slash-command autocomplete and help text. */
1681
1720
  export const BUILTIN_SLASH_COMMAND_DEFS: ReadonlyArray<BuiltinSlashCommand> = BUILTIN_SLASH_COMMAND_REGISTRY.map(
1682
1721
  command => ({
1683
1722
  name: command.name,
1723
+ aliases: command.aliases,
1684
1724
  description: command.description,
1685
1725
  subcommands: command.subcommands,
1686
1726
  inlineHint: command.inlineHint,
@@ -0,0 +1,85 @@
1
+ import * as stats from "@oh-my-pi/omp-stats";
2
+ import * as openUtils from "../../utils/open";
3
+
4
+ export const DEFAULT_STATS_DASHBOARD_PORT = 3847;
5
+
6
+ interface StatsDashboardServer {
7
+ port: number;
8
+ stop: () => void;
9
+ }
10
+
11
+ export interface StatsDashboardArgs {
12
+ port: number;
13
+ }
14
+
15
+ export interface StatsDashboardLaunchResult {
16
+ url: string;
17
+ message: string;
18
+ }
19
+
20
+ let activeStatsServer: StatsDashboardServer | undefined;
21
+
22
+ const STATS_DASHBOARD_USAGE = "Usage: /stats [--port <port>]";
23
+
24
+ function parsePort(value: string | undefined): number | string {
25
+ if (!value) return `Missing port. ${STATS_DASHBOARD_USAGE}`;
26
+ if (!/^\d+$/.test(value)) return `Invalid port: ${value}`;
27
+ const port = Number(value);
28
+ if (!Number.isInteger(port) || port < 0 || port > 65_535) return `Invalid port: ${value}`;
29
+ return port;
30
+ }
31
+
32
+ export function parseStatsDashboardArgs(args: string): StatsDashboardArgs | { error: string } {
33
+ const tokens = args.split(/\s+/).filter(Boolean);
34
+ let port = DEFAULT_STATS_DASHBOARD_PORT;
35
+
36
+ for (let i = 0; i < tokens.length; i++) {
37
+ const token = tokens[i];
38
+ if (token === "--port" || token === "-p") {
39
+ const parsed = parsePort(tokens[++i]);
40
+ if (typeof parsed === "string") return { error: parsed };
41
+ port = parsed;
42
+ continue;
43
+ }
44
+ if (token.startsWith("--port=")) {
45
+ const parsed = parsePort(token.slice("--port=".length));
46
+ if (typeof parsed === "string") return { error: parsed };
47
+ port = parsed;
48
+ continue;
49
+ }
50
+ return { error: `Unknown option: ${token}. ${STATS_DASHBOARD_USAGE}` };
51
+ }
52
+
53
+ return { port };
54
+ }
55
+
56
+ export async function launchStatsDashboard(args: StatsDashboardArgs): Promise<StatsDashboardLaunchResult> {
57
+ const { processed, files } = await stats.syncAllSessions();
58
+ const total = await stats.getTotalMessageCount();
59
+ let requestedPortIgnored = false;
60
+
61
+ if (!activeStatsServer) {
62
+ activeStatsServer = await stats.startServer(args.port);
63
+ } else if (args.port !== activeStatsServer.port) {
64
+ requestedPortIgnored = true;
65
+ }
66
+
67
+ const url = `http://localhost:${activeStatsServer.port}`;
68
+ openUtils.openPath(url);
69
+
70
+ const serverLine = requestedPortIgnored
71
+ ? `Dashboard already running at: ${url} (requested port ${args.port} ignored)`
72
+ : `Dashboard available at: ${url}`;
73
+
74
+ return {
75
+ url,
76
+ message: `Synced ${processed} new entries from ${files} files (${total} total)\n${serverLine}`,
77
+ };
78
+ }
79
+
80
+ export function stopStatsDashboard(): void {
81
+ if (!activeStatsServer) return;
82
+ activeStatsServer.stop();
83
+ activeStatsServer = undefined;
84
+ stats.closeDb();
85
+ }
@@ -14,6 +14,7 @@ export interface SubcommandDef {
14
14
  /** Declarative builtin slash command metadata used by autocomplete and help UI. */
15
15
  export interface BuiltinSlashCommand {
16
16
  name: string;
17
+ aliases?: string[];
17
18
  description: string;
18
19
  /** Subcommands for dropdown completion (e.g. /mcp add, /mcp list). */
19
20
  subcommands?: SubcommandDef[];
@@ -82,7 +83,6 @@ export interface TuiSlashCommandRuntime {
82
83
 
83
84
  /** Unified slash-command spec consumed by both TUI and ACP dispatchers. */
84
85
  export interface SlashCommandSpec extends BuiltinSlashCommand {
85
- aliases?: string[];
86
86
  /** When false, the dispatcher refuses to handle invocations that include arguments. */
87
87
  allowArgs?: boolean;
88
88
  /**
@@ -355,6 +355,33 @@ export async function getHostInfoForHost(host: SSHConnectionTarget): Promise<SSH
355
355
  return await loadHostInfoFromDisk(host);
356
356
  }
357
357
 
358
+ /**
359
+ * Synchronous, probe-free host info lookup for startup paths.
360
+ *
361
+ * Checks the in-memory cache, then falls back to a synchronous read of the
362
+ * persisted host-info cache file. Never opens a connection or probes the
363
+ * remote host — callers get `undefined` when nothing is cached yet.
364
+ */
365
+ export function getCachedHostInfoSync(host: SSHConnectionTarget): SSHHostInfo | undefined {
366
+ const cached = hostInfoCache.get(host.name);
367
+ if (cached) {
368
+ const resolved = applyCompatOverride(host, cached);
369
+ if (resolved !== cached) hostInfoCache.set(host.name, resolved);
370
+ return resolved;
371
+ }
372
+ try {
373
+ const parsed = parseHostInfo(JSON.parse(fs.readFileSync(getHostInfoPath(host.name), "utf-8")));
374
+ if (!parsed) return undefined;
375
+ const resolved = applyCompatOverride(host, parsed);
376
+ hostInfoCache.set(host.name, resolved);
377
+ return resolved;
378
+ } catch (err) {
379
+ if (isEnoent(err)) return undefined;
380
+ logger.warn("Failed to load SSH host info", { host: host.name, error: String(err) });
381
+ return undefined;
382
+ }
383
+ }
384
+
358
385
  export async function ensureHostInfo(host: SSHConnectionTarget): Promise<SSHHostInfo> {
359
386
  const cached = hostInfoCache.get(host.name);
360
387
  if (cached) {
@@ -8,6 +8,7 @@ import { $env, getGpuCachePath, getProjectDir, hasFsCode, isEnoent, logger, prom
8
8
  import { $ } from "bun";
9
9
  import { contextFileCapability } from "./capability/context-file";
10
10
  import { systemPromptCapability } from "./capability/system-prompt";
11
+ import { findConfigFile } from "./config";
11
12
  import type { SkillsSettings } from "./config/settings";
12
13
  import { type ContextFile, loadCapability, type SystemPrompt as SystemPromptFile } from "./discovery";
13
14
  import { expandAtImports } from "./discovery/at-imports";
@@ -208,6 +209,19 @@ async function getEnvironmentInfo(): Promise<Array<{ label: string; value: strin
208
209
  return entries.filter((e): e is { label: string; value: string } => !!e.value);
209
210
  }
210
211
 
212
+ /** Discover TITLE_SYSTEM.md file for automatic session-title prompt overrides */
213
+ export function discoverTitleSystemPromptFile(cwd?: string): string | undefined {
214
+ const projectPath = findConfigFile("TITLE_SYSTEM.md", { user: false, cwd });
215
+ if (projectPath) {
216
+ return projectPath;
217
+ }
218
+ const globalPath = findConfigFile("TITLE_SYSTEM.md", { user: true, cwd });
219
+ if (globalPath) {
220
+ return globalPath;
221
+ }
222
+ return undefined;
223
+ }
224
+
211
225
  /** Resolve input as file path or literal string */
212
226
  export async function resolvePromptInput(input: string | undefined, description: string): Promise<string | undefined> {
213
227
  if (!input) {
@@ -120,7 +120,8 @@ export function getCommand(commands: WorkflowCommand[], name: string): WorkflowC
120
120
  * Replaces $@ with the provided input.
121
121
  */
122
122
  export function expandCommand(command: WorkflowCommand, input: string): string {
123
- return command.instructions.replace(/\$@/g, input);
123
+ // Function replacement so `$`-patterns in user input ($$, $&, ...) stay literal.
124
+ return command.instructions.replace(/\$@/g, () => input);
124
125
  }
125
126
 
126
127
  /**