@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
@@ -17,6 +17,24 @@ const TIPS: readonly string[] = tipsText
17
17
  .map(line => line.trim())
18
18
  .filter(line => line.length > 0);
19
19
 
20
+ /**
21
+ * Tip chosen once per process so the pre-TUI startup splash and the in-TUI
22
+ * welcome screen show the same tip instead of shuffling on the swap.
23
+ */
24
+ const PROCESS_TIP: string | undefined = TIPS.length > 0 ? TIPS[Math.floor(Math.random() * TIPS.length)] : undefined;
25
+
26
+ /**
27
+ * Fixed number of session rows in the welcome box so its height doesn't shift
28
+ * between the pre-TUI splash (loading placeholder) and the loaded state.
29
+ */
30
+ export const WELCOME_SESSION_SLOTS = 4;
31
+
32
+ /**
33
+ * Fixed number of LSP-server rows, for the same reason. Overflow is sliced so
34
+ * the box height is constant regardless of how many servers a project has.
35
+ */
36
+ export const WELCOME_LSP_SLOTS = 4;
37
+
20
38
  export function renderWelcomeTip(tip: string, boxWidth: number): string[] {
21
39
  const label = "Tip: ";
22
40
  const labelWidth = visibleWidth(label);
@@ -48,7 +66,7 @@ export interface RecentSession {
48
66
 
49
67
  export interface LspServerInfo {
50
68
  name: string;
51
- status: "ready" | "error" | "connecting";
69
+ status: "ready" | "error" | "connecting" | "available";
52
70
  fileTypes: string[];
53
71
  }
54
72
 
@@ -58,18 +76,38 @@ export interface LspServerInfo {
58
76
  export class WelcomeComponent implements Component {
59
77
  #animStart: number | null = null;
60
78
  #animTimer: ReturnType<typeof setInterval> | null = null;
61
- /** Tip chosen once per instance so re-renders (intro, LSP updates) don't shuffle it. */
62
- readonly #tip: string | undefined = TIPS.length > 0 ? TIPS[Math.floor(Math.random() * TIPS.length)] : undefined;
79
+ /** When set, a non-animating render shows the intro's first frame instead of the resting frame. */
80
+ #holdIntroFirstFrame = false;
81
+ /** Per-process tip so re-renders (intro, LSP updates, splash swap) don't shuffle it. */
82
+ readonly #tip: string | undefined = PROCESS_TIP;
83
+ // Render cache: the welcome box is the first transcript-area component, so
84
+ // returning a stable array reference keeps the whole frame prefix stable.
85
+ // Bypassed while the intro animation runs (every frame differs).
86
+ #cachedWidth = -1;
87
+ #cachedLines: string[] | undefined;
63
88
 
64
89
  constructor(
65
90
  private readonly version: string,
66
91
  private modelName: string,
67
92
  private providerName: string,
68
- private recentSessions: RecentSession[] = [],
93
+ private recentSessions: RecentSession[] | null = [],
69
94
  private lspServers: LspServerInfo[] = [],
70
95
  ) {}
71
96
 
72
- invalidate(): void {}
97
+ invalidate(): void {
98
+ this.#cachedWidth = -1;
99
+ this.#cachedLines = undefined;
100
+ }
101
+
102
+ /**
103
+ * Freeze the logo on the intro animation's first frame. The pre-TUI startup
104
+ * splash uses this so the in-TUI intro — which starts at that exact frame —
105
+ * picks up seamlessly from the splash's static box.
106
+ */
107
+ holdIntroFirstFrame(): void {
108
+ this.#holdIntroFirstFrame = true;
109
+ this.invalidate();
110
+ }
73
111
 
74
112
  /**
75
113
  * Play a one-shot intro that sweeps the gradient through every phase
@@ -78,6 +116,7 @@ export class WelcomeComponent implements Component {
78
116
  */
79
117
  playIntro(requestRender: () => void): void {
80
118
  this.#stopAnimation();
119
+ this.#holdIntroFirstFrame = false;
81
120
  this.#animStart = performance.now();
82
121
  requestRender();
83
122
  this.#animTimer = setInterval(() => {
@@ -95,22 +134,43 @@ export class WelcomeComponent implements Component {
95
134
  this.#animTimer = null;
96
135
  }
97
136
  this.#animStart = null;
137
+ // The settled (resting) frame differs from the last intro frame.
138
+ this.invalidate();
98
139
  }
99
140
 
100
141
  setModel(modelName: string, providerName: string): void {
101
142
  this.modelName = modelName;
102
143
  this.providerName = providerName;
144
+ this.invalidate();
103
145
  }
104
146
 
105
147
  setRecentSessions(sessions: RecentSession[]): void {
106
148
  this.recentSessions = sessions;
149
+ this.invalidate();
107
150
  }
108
151
 
109
152
  setLspServers(servers: LspServerInfo[]): void {
110
153
  this.lspServers = servers;
154
+ this.invalidate();
111
155
  }
112
156
 
113
- render(termWidth: number): string[] {
157
+ render(termWidth: number): readonly string[] {
158
+ const animating = this.#animStart != null;
159
+ if (!animating && this.#cachedLines && this.#cachedWidth === termWidth) {
160
+ return this.#cachedLines;
161
+ }
162
+ const lines = this.#renderLines(termWidth);
163
+ if (animating) {
164
+ this.#cachedLines = undefined;
165
+ this.#cachedWidth = -1;
166
+ } else {
167
+ this.#cachedLines = lines;
168
+ this.#cachedWidth = termWidth;
169
+ }
170
+ return lines;
171
+ }
172
+
173
+ #renderLines(termWidth: number): string[] {
114
174
  // Box dimensions - responsive with max width and small-terminal support
115
175
  const maxWidth = 100;
116
176
  const boxWidth = Math.min(maxWidth, Math.max(0, termWidth - 2));
@@ -157,7 +217,9 @@ export class WelcomeComponent implements Component {
157
217
 
158
218
  // Recent sessions content
159
219
  const sessionLines: string[] = [];
160
- if (this.recentSessions.length === 0) {
220
+ if (this.recentSessions === null) {
221
+ sessionLines.push(` ${theme.fg("dim", "Loading…")}`);
222
+ } else if (this.recentSessions.length === 0) {
161
223
  sessionLines.push(` ${theme.fg("dim", "No recent sessions")}`);
162
224
  } else {
163
225
  // Reserve width for the bullet prefix (" • ") and the trailing " (timeAgo)"
@@ -165,7 +227,7 @@ export class WelcomeComponent implements Component {
165
227
  // absorbs whatever space is left.
166
228
  const bulletPrefix = ` ${theme.md.bullet} `;
167
229
  const prefixWidth = visibleWidth(bulletPrefix);
168
- for (const session of this.recentSessions.slice(0, 3)) {
230
+ for (const session of this.recentSessions.slice(0, WELCOME_SESSION_SLOTS)) {
169
231
  const timeSuffixRaw = ` (${session.timeAgo})`;
170
232
  const timeWidth = visibleWidth(timeSuffixRaw);
171
233
  const nameBudget = Math.max(1, rightCol - prefixWidth - timeWidth);
@@ -176,23 +238,33 @@ export class WelcomeComponent implements Component {
176
238
  );
177
239
  }
178
240
  }
241
+ // Pad to the fixed slot count so the box doesn't grow when sessions load in.
242
+ while (sessionLines.length < WELCOME_SESSION_SLOTS) {
243
+ sessionLines.push("");
244
+ }
179
245
 
180
246
  // LSP servers content
181
247
  const lspLines: string[] = [];
182
248
  if (this.lspServers.length === 0) {
183
249
  lspLines.push(` ${theme.fg("dim", "No LSP servers")}`);
184
250
  } else {
185
- for (const server of this.lspServers) {
251
+ for (const server of this.lspServers.slice(0, WELCOME_LSP_SLOTS)) {
186
252
  const icon =
187
253
  server.status === "ready"
188
254
  ? theme.styledSymbol("status.enabled", "success")
189
- : server.status === "connecting"
190
- ? theme.styledSymbol("status.pending", "muted")
191
- : theme.styledSymbol("status.error", "error");
255
+ : server.status === "available"
256
+ ? theme.styledSymbol("status.enabled", "dim")
257
+ : server.status === "connecting"
258
+ ? theme.styledSymbol("status.pending", "muted")
259
+ : theme.styledSymbol("status.error", "error");
192
260
  const exts = server.fileTypes.slice(0, 3).join(" ");
193
261
  lspLines.push(` ${icon} ${theme.fg("muted", server.name)} ${theme.fg("dim", exts)}`);
194
262
  }
195
263
  }
264
+ // Pad to the fixed slot count so the box height doesn't depend on server count.
265
+ while (lspLines.length < WELCOME_LSP_SLOTS) {
266
+ lspLines.push("");
267
+ }
196
268
 
197
269
  // Right column
198
270
  const rightLines = [
@@ -305,23 +377,12 @@ export class WelcomeComponent implements Component {
305
377
  return str + padding(width - visLen);
306
378
  }
307
379
 
308
- /** Pick the logo frame for the current intro phase, or the resting frame. */
380
+ /** Pick the logo frame for the current intro phase, or the resting/held frame. */
309
381
  #currentLogoFrame(): readonly string[] {
310
- if (this.#animStart == null) return REST_FRAME;
382
+ if (this.#animStart == null) return this.#holdIntroFirstFrame ? INTRO_FIRST_FRAME : REST_FRAME;
311
383
  const elapsed = performance.now() - this.#animStart;
312
384
  if (elapsed >= INTRO_MS) return REST_FRAME;
313
- // Ease-out cubic so the spin decelerates into the resting state.
314
- const progress = elapsed / INTRO_MS;
315
- const eased = 1 - (1 - progress) ** 3;
316
- // Sweep backward through INTRO_SWEEPS full rotations so the gradient
317
- // visibly spins multiple times. `eased == 1` → phase = 0 = resting frame.
318
- const phase = ((((1 - eased) * INTRO_SWEEPS) % 1) + 1) % 1;
319
- // Shine traverses the diagonal at a steady pace, decoupled from the
320
- // gradient phase so the two layers parallax. Strength fades out with
321
- // the same ease-out curve so the highlight is gone by the resting frame.
322
- const shinePos = (((progress * INTRO_SHINE_TRAVERSALS) % 1) + 1) % 1;
323
- const shineStrength = (1 - eased) ** 1.5;
324
- return gradientLogo(PI_LOGO, phase, { strength: shineStrength, pos: shinePos });
385
+ return introLogoFrame(elapsed / INTRO_MS);
325
386
  }
326
387
  }
327
388
 
@@ -431,5 +492,26 @@ const INTRO_SWEEPS = 2.5;
431
492
  /** Number of times the shine highlight crosses the diagonal across the intro. */
432
493
  const INTRO_SHINE_TRAVERSALS = 3;
433
494
 
495
+ /**
496
+ * Logo frame for a normalized intro progress in [0, 1).
497
+ *
498
+ * Ease-out cubic so the spin decelerates into the resting state. The gradient
499
+ * sweeps backward through INTRO_SWEEPS full rotations (`eased == 1` → phase =
500
+ * 0 = resting frame) while the shine traverses the diagonal at a steady pace,
501
+ * decoupled from the gradient phase so the two layers parallax; its strength
502
+ * fades with the same ease-out curve so the highlight is gone by the resting
503
+ * frame.
504
+ */
505
+ function introLogoFrame(progress: number): string[] {
506
+ const eased = 1 - (1 - progress) ** 3;
507
+ const phase = ((((1 - eased) * INTRO_SWEEPS) % 1) + 1) % 1;
508
+ const shinePos = (((progress * INTRO_SHINE_TRAVERSALS) % 1) + 1) % 1;
509
+ const shineStrength = (1 - eased) ** 1.5;
510
+ return gradientLogo(PI_LOGO, phase, { strength: shineStrength, pos: shinePos });
511
+ }
512
+
513
+ /** First intro frame, cached for splash-held renders (resize re-renders reuse it). */
514
+ const INTRO_FIRST_FRAME = introLogoFrame(0);
515
+
434
516
  /** Resting gradient frame, cached for re-renders outside of the intro. */
435
517
  const REST_FRAME = gradientLogo(PI_LOGO, 0);
@@ -21,6 +21,7 @@ import {
21
21
  loadHindsightConfig,
22
22
  reloadMentalModelsForSession,
23
23
  resolveSeedsForScope,
24
+ seedAlreadyExists,
24
25
  summarizeMentalModel,
25
26
  } from "../../hindsight";
26
27
  import { resolveMemoryBackend } from "../../memory-backend";
@@ -314,7 +315,13 @@ export class CommandController {
314
315
  info += `\n${theme.bold("LSP Servers")}\n`;
315
316
  for (const server of this.ctx.lspServers) {
316
317
  const statusColor =
317
- server.status === "ready" ? "success" : server.status === "connecting" ? "warning" : "error";
318
+ server.status === "ready"
319
+ ? "success"
320
+ : server.status === "available"
321
+ ? "dim"
322
+ : server.status === "connecting"
323
+ ? "warning"
324
+ : "error";
318
325
  const statusText =
319
326
  server.status === "error" && server.error ? `${server.status}: ${server.error}` : server.status;
320
327
  info += `${theme.fg("dim", `${server.name}:`)} ${theme.fg(statusColor, statusText)} ${theme.fg("dim", `(${server.fileTypes.join(", ")})`)}\n`;
@@ -712,11 +719,11 @@ export class CommandController {
712
719
  return;
713
720
  }
714
721
  const list = await state.client.listMentalModels(state.bankId, { detail: "metadata" });
715
- const existing = new Set((list.items ?? []).map(m => m.id));
722
+ const existing = list.items ?? [];
716
723
  let created = 0;
717
724
  let skipped = 0;
718
725
  for (const seed of seeds) {
719
- if (existing.has(seed.id)) {
726
+ if (seedAlreadyExists(seed, existing)) {
720
727
  skipped++;
721
728
  continue;
722
729
  }
@@ -927,7 +934,7 @@ export class CommandController {
927
934
  this.ctx.bashComponent.appendOutput(chunk);
928
935
  }
929
936
  },
930
- { excludeFromContext },
937
+ { excludeFromContext, useUserShell: true },
931
938
  );
932
939
 
933
940
  if (this.ctx.bashComponent) {
@@ -25,6 +25,16 @@ import { StreamingRevealController } from "./streaming-reveal";
25
25
  type AgentSessionEventKind = AgentSessionEvent["type"];
26
26
 
27
27
  const IRC_MESSAGE_VISIBLE_TTL_MS = 10_000;
28
+ /**
29
+ * Concurrent IRC cards allowed in the transcript's live region. Cards land
30
+ * below a still-live block (a running task), where they cannot commit to
31
+ * native scrollback (commits are prefix-only) — every visible card inflates
32
+ * the live region and pushes the live block's uncommitted rows above the
33
+ * window top, where they are neither on screen nor in history. A swarm burst
34
+ * (several agents coordinating at once) must therefore stay bounded: the
35
+ * oldest live-region card retires as soon as a new one would exceed the cap.
36
+ */
37
+ const MAX_LIVE_IRC_CARDS = 4;
28
38
 
29
39
  /**
30
40
  * Loader label shown the instant a user interrupt (Esc) is requested, kept until
@@ -64,6 +74,9 @@ export class EventController {
64
74
  #pinnedErrorComponent: AssistantMessageComponent | undefined = undefined;
65
75
  #idleCompactionTimer?: NodeJS.Timeout;
66
76
  #ircExpiryTimers = new Map<string, NodeJS.Timeout>();
77
+ // Insertion-ordered IRC cards not yet retired; values are the transcript
78
+ // components each card contributed (see #retireIrcCard for the guard).
79
+ #liveIrcCards = new Map<string, Component[]>();
67
80
  #streamingReveal: StreamingRevealController;
68
81
  #handlers: AgentSessionEventHandlers;
69
82
 
@@ -111,6 +124,7 @@ export class EventController {
111
124
  clearTimeout(timer);
112
125
  }
113
126
  this.#ircExpiryTimers.clear();
127
+ this.#liveIrcCards.clear();
114
128
  }
115
129
 
116
130
  #resetReadGroup(): void {
@@ -324,6 +338,7 @@ export class EventController {
324
338
  this.#resetReadGroup();
325
339
  const components = this.ctx.addMessageToChat(event.message);
326
340
  this.#scheduleIrcExpiry(signature, components);
341
+ this.#enforceIrcCardCap(signature);
327
342
  this.ctx.ui.requestRender();
328
343
  }
329
344
 
@@ -331,13 +346,47 @@ export class EventController {
331
346
  if (components.length === 0 || this.#ircExpiryTimers.has(signature)) return;
332
347
  const timer = setTimeout(() => {
333
348
  this.#ircExpiryTimers.delete(signature);
334
- for (const component of components) {
335
- this.ctx.chatContainer.removeChild(component);
336
- }
337
- this.ctx.ui.requestRender();
349
+ this.#retireIrcCard(signature);
338
350
  }, IRC_MESSAGE_VISIBLE_TTL_MS);
339
351
  timer.unref?.();
340
352
  this.#ircExpiryTimers.set(signature, timer);
353
+ this.#liveIrcCards.set(signature, components);
354
+ }
355
+
356
+ /**
357
+ * Remove an expired/evicted IRC card — but only while it still sits below a
358
+ * live block, where its rows cannot have entered native scrollback. Once
359
+ * everything above it has finalized, its rows may already be committed;
360
+ * removing them then is an interior deletion of the committed prefix, which
361
+ * the engine can only repair by recommitting every row below the gap —
362
+ * exactly the duplicated-block artifact this guard exists to prevent. Such
363
+ * a card simply stays: it is final history, and the window scrolls past it.
364
+ */
365
+ #retireIrcCard(signature: string): void {
366
+ const components = this.#liveIrcCards.get(signature);
367
+ this.#liveIrcCards.delete(signature);
368
+ if (!components) return;
369
+ let removed = false;
370
+ for (const component of components) {
371
+ if (!this.ctx.chatContainer.isWithinLiveRegion(component)) continue;
372
+ this.ctx.chatContainer.removeChild(component);
373
+ removed = true;
374
+ }
375
+ if (removed) this.ctx.ui.requestRender();
376
+ }
377
+
378
+ /** Evict oldest live-region cards beyond {@link MAX_LIVE_IRC_CARDS}. */
379
+ #enforceIrcCardCap(latestSignature: string): void {
380
+ while (this.#liveIrcCards.size > MAX_LIVE_IRC_CARDS) {
381
+ const oldest = this.#liveIrcCards.keys().next().value;
382
+ if (oldest === undefined || oldest === latestSignature) return;
383
+ const timer = this.#ircExpiryTimers.get(oldest);
384
+ if (timer) {
385
+ clearTimeout(timer);
386
+ this.#ircExpiryTimers.delete(oldest);
387
+ }
388
+ this.#retireIrcCard(oldest);
389
+ }
341
390
  }
342
391
 
343
392
  async #handleNotice(event: Extract<AgentSessionEvent, { type: "notice" }>): Promise<void> {
@@ -365,6 +414,26 @@ export class EventController {
365
414
  this.#resetReadGroup();
366
415
  this.#lastVisibleBlockCount = visibleBlockCount;
367
416
  }
417
+
418
+ // Content blocks stream sequentially: a toolCall block can only begin
419
+ // after every preceding thinking/text block has closed, and the
420
+ // reveal's setTarget above force-completes the visible text for
421
+ // toolCall messages. Finalize the assistant block now instead of at
422
+ // message_end so the transcript's commit-safe run can extend through
423
+ // it into the streaming tool preview below — otherwise a long args
424
+ // stream (a big write/edit/eval) sits below a still-live block and
425
+ // can never reach native scrollback: the head of the preview is
426
+ // neither committed nor on screen and the transcript reads as cut.
427
+ // Skipped when the per-turn usage row is enabled: that row is only
428
+ // known at message_end and appends to this block, which would shift
429
+ // committed tool rows below it every turn (audit recommit →
430
+ // duplicated preview copies in scrollback).
431
+ if (
432
+ this.ctx.streamingMessage.content.some(content => content.type === "toolCall") &&
433
+ !settings.get("display.showTokenUsage")
434
+ ) {
435
+ this.ctx.streamingComponent.markTranscriptBlockFinalized();
436
+ }
368
437
  for (const content of this.ctx.streamingMessage.content) {
369
438
  if (content.type !== "toolCall") continue;
370
439
  if (content.name === "read") {
@@ -2,7 +2,7 @@ import * as fs from "node:fs/promises";
2
2
  import type { ImageContent } from "@oh-my-pi/pi-ai";
3
3
  import type { AutocompleteProvider, SlashCommand } from "@oh-my-pi/pi-tui";
4
4
  import { $env, logger, sanitizeText } from "@oh-my-pi/pi-utils";
5
- import { getRoleInfo } from "../../config/model-registry";
5
+ import { getRoleInfo } from "../../config/model-roles";
6
6
  import { isSettingsInitialized, settings } from "../../config/settings";
7
7
  import { renderSegmentTrack } from "../../modes/components/segment-track";
8
8
  import { TinyTitleDownloadProgressComponent } from "../../modes/components/tiny-title-download-progress";
@@ -467,6 +467,7 @@ export class InputController {
467
467
  this.ctx.session.sessionId,
468
468
  this.ctx.session.model,
469
469
  provider => this.ctx.session.agent.metadataForProvider(provider),
470
+ this.ctx.titleSystemPrompt,
470
471
  )
471
472
  .then(async title => {
472
473
  // Re-check: a concurrent attempt for an earlier message may have
@@ -46,9 +46,14 @@ import { theme } from "../theme/theme";
46
46
  import type { InteractiveModeContext } from "../types";
47
47
  import { groupBySource, parseRemoveArgs, readScopeFlag, showCommandMessage } from "./command-controller-shared";
48
48
 
49
- function withTimeout<T>(promise: Promise<T>, timeoutMs: number, message: string): Promise<T> {
49
+ const MCP_MANUAL_INPUT_PROVIDER_ID = "mcp";
50
+ const MCP_MANUAL_LOGIN_TIP = "Headless? Paste the redirect URL or code with /login <value>.";
51
+ function withTimeout<T>(promise: Promise<T>, timeoutMs: number, message: string, onTimeout?: () => void): Promise<T> {
50
52
  const { promise: timeoutPromise, reject } = Promise.withResolvers<T>();
51
- const timer = setTimeout(() => reject(new Error(message)), timeoutMs);
53
+ const timer = setTimeout(() => {
54
+ onTimeout?.();
55
+ reject(new Error(message));
56
+ }, timeoutMs);
52
57
  return Promise.race([promise, timeoutPromise]).finally(() => clearTimeout(timer));
53
58
  }
54
59
 
@@ -62,7 +67,7 @@ export class MCPAuthorizationLinkPrompt implements Component {
62
67
 
63
68
  invalidate(): void {}
64
69
 
65
- render(_width: number): string[] {
70
+ render(_width: number): readonly string[] {
66
71
  const link = urlHyperlinkAlways(this.#url, "Click here to authorize");
67
72
  return [
68
73
  ` ${theme.fg("success", "Open authorization URL:")}`,
@@ -591,6 +596,15 @@ export class MCPCommandController {
591
596
  const resolvedClientId = clientId.trim() || parsedAuthUrl.searchParams.get("client_id") || undefined;
592
597
  const resolvedClientSecret = clientSecret.trim() || undefined;
593
598
 
599
+ const manualInput = this.ctx.oauthManualInput;
600
+ if (manualInput.hasPending()) {
601
+ const pendingProvider = manualInput.pendingProviderId ?? "another provider";
602
+ throw new Error(
603
+ `OAuth login already in progress for ${pendingProvider}. Complete or cancel it before starting MCP OAuth.`,
604
+ );
605
+ }
606
+ let manualInputClaim: { promise: Promise<string>; clear: (reason?: string) => void } | undefined;
607
+ const oauthTimeout = new AbortController();
594
608
  try {
595
609
  // Create OAuth flow
596
610
  const flow = new MCPOAuthFlow(
@@ -620,6 +634,7 @@ export class MCPCommandController {
620
634
  0,
621
635
  ),
622
636
  );
637
+ block.addChild(new Text(theme.fg("muted", MCP_MANUAL_LOGIN_TIP), 1, 0));
623
638
  block.addChild(new Spacer(1));
624
639
  block.addChild(new Text(theme.fg("accent", "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"), 1, 0));
625
640
  // Try to open browser automatically
@@ -644,11 +659,29 @@ export class MCPCommandController {
644
659
  onProgress: (message: string) => {
645
660
  this.ctx.present([new Spacer(1), new Text(theme.fg("muted", message), 1, 0)]);
646
661
  },
662
+ onManualCodeInput: () => {
663
+ if (manualInputClaim) return manualInputClaim.promise;
664
+ const pendingInput = manualInput.tryClaimInput(MCP_MANUAL_INPUT_PROVIDER_ID);
665
+ if (!pendingInput) {
666
+ const pendingProvider = manualInput.pendingProviderId ?? "another provider";
667
+ throw new Error(
668
+ `OAuth login already in progress for ${pendingProvider}. Complete or cancel it before starting MCP OAuth.`,
669
+ );
670
+ }
671
+ manualInputClaim = pendingInput;
672
+ return pendingInput.promise;
673
+ },
674
+ signal: oauthTimeout.signal,
647
675
  },
648
676
  );
649
677
 
650
678
  // Execute OAuth flow with 5 minute timeout
651
- const credentials = await withTimeout(flow.login(), 5 * 60 * 1000, "OAuth flow timed out after 5 minutes");
679
+ const credentials = await withTimeout(
680
+ flow.login(),
681
+ 5 * 60 * 1000,
682
+ "OAuth flow timed out after 5 minutes",
683
+ () => oauthTimeout.abort("MCP OAuth flow timed out"),
684
+ );
652
685
 
653
686
  this.ctx.present([
654
687
  new Spacer(1),
@@ -687,6 +720,8 @@ export class MCPCommandController {
687
720
  } else {
688
721
  throw new Error(`OAuth authentication failed: ${errorMsg}`);
689
722
  }
723
+ } finally {
724
+ manualInputClaim?.clear("Manual MCP OAuth input cleared");
690
725
  }
691
726
  }
692
727
 
@@ -5,8 +5,8 @@ import type { OAuthProvider } from "@oh-my-pi/pi-ai/oauth/types";
5
5
  import type { Component, OverlayHandle } from "@oh-my-pi/pi-tui";
6
6
  import { Input, Loader, Spacer, Text } from "@oh-my-pi/pi-tui";
7
7
  import { getAgentDbPath, getProjectDir, normalizePathForComparison } from "@oh-my-pi/pi-utils";
8
- import { getRoleInfo } from "../../config/model-registry";
9
8
  import { formatModelSelectorValue } from "../../config/model-resolver";
9
+ import { getRoleInfo } from "../../config/model-roles";
10
10
  import { settings } from "../../config/settings";
11
11
  import { disableProvider, enableProvider } from "../../discovery";
12
12
  import { clearPluginRootsAndCaches, resolveActiveProjectRegistryPath } from "../../discovery/helpers";