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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (345) hide show
  1. package/CHANGELOG.md +95 -4
  2. package/dist/cli.js +23087 -0
  3. package/dist/tokenizers.linux-x64-gnu-xcjh3jwk.node +0 -0
  4. package/dist/types/async/job-manager.d.ts +18 -0
  5. package/dist/types/cli/args.d.ts +1 -1
  6. package/dist/types/cli/dry-balance-cli.d.ts +1 -1
  7. package/dist/types/cli/gallery-cli.d.ts +1 -1
  8. package/dist/types/cli/gallery-fixtures/types.d.ts +1 -1
  9. package/dist/types/cli/usage-cli.d.ts +72 -0
  10. package/dist/types/commands/launch.d.ts +1 -1
  11. package/dist/types/commands/read.d.ts +1 -1
  12. package/dist/types/commands/usage.d.ts +25 -0
  13. package/dist/types/config/append-only-context-mode.d.ts +2 -1
  14. package/dist/types/config/model-discovery.d.ts +55 -0
  15. package/dist/types/config/model-registry.d.ts +7 -219
  16. package/dist/types/config/model-resolver.d.ts +16 -10
  17. package/dist/types/config/model-roles.d.ts +28 -0
  18. package/dist/types/config/models-config-schema.d.ts +523 -42
  19. package/dist/types/config/models-config.d.ts +385 -0
  20. package/dist/types/config/settings-schema.d.ts +12 -7
  21. package/dist/types/config/settings.d.ts +1 -1
  22. package/dist/types/debug/log-viewer.d.ts +1 -1
  23. package/dist/types/debug/raw-sse.d.ts +1 -1
  24. package/dist/types/eval/backend.d.ts +0 -2
  25. package/dist/types/eval/idle-timeout.d.ts +0 -4
  26. package/dist/types/eval/js/shared/rewrite-imports.d.ts +6 -6
  27. package/dist/types/export/html/template.generated.d.ts +1 -1
  28. package/dist/types/extensibility/extensions/types.d.ts +3 -3
  29. package/dist/types/hindsight/mental-models.d.ts +17 -8
  30. package/dist/types/internal-urls/artifact-protocol.d.ts +2 -2
  31. package/dist/types/internal-urls/types.d.ts +1 -1
  32. package/dist/types/lsp/edits.d.ts +9 -0
  33. package/dist/types/lsp/index.d.ts +2 -2
  34. package/dist/types/lsp/types.d.ts +2 -0
  35. package/dist/types/lsp/utils.d.ts +3 -0
  36. package/dist/types/mcp/json-rpc.d.ts +5 -0
  37. package/dist/types/mnemopi/state.d.ts +11 -1
  38. package/dist/types/modes/components/agent-dashboard.d.ts +1 -1
  39. package/dist/types/modes/components/assistant-message.d.ts +3 -1
  40. package/dist/types/modes/components/bash-execution.d.ts +1 -1
  41. package/dist/types/modes/components/copy-selector.d.ts +1 -1
  42. package/dist/types/modes/components/dynamic-border.d.ts +1 -1
  43. package/dist/types/modes/components/extensions/extension-dashboard.d.ts +1 -1
  44. package/dist/types/modes/components/extensions/extension-list.d.ts +1 -1
  45. package/dist/types/modes/components/extensions/inspector-panel.d.ts +1 -1
  46. package/dist/types/modes/components/footer.d.ts +1 -1
  47. package/dist/types/modes/components/hook-editor.d.ts +5 -0
  48. package/dist/types/modes/components/hook-input.d.ts +4 -0
  49. package/dist/types/modes/components/hook-selector.d.ts +1 -1
  50. package/dist/types/modes/components/model-selector.d.ts +1 -1
  51. package/dist/types/modes/components/plan-review-overlay.d.ts +1 -1
  52. package/dist/types/modes/components/session-observer-overlay.d.ts +1 -1
  53. package/dist/types/modes/components/session-selector.d.ts +1 -1
  54. package/dist/types/modes/components/status-line/component.d.ts +1 -1
  55. package/dist/types/modes/components/tiny-title-download-progress.d.ts +1 -1
  56. package/dist/types/modes/components/transcript-container.d.ts +25 -6
  57. package/dist/types/modes/components/tree-selector.d.ts +1 -1
  58. package/dist/types/modes/components/user-message-selector.d.ts +1 -1
  59. package/dist/types/modes/components/user-message.d.ts +2 -1
  60. package/dist/types/modes/components/visual-truncate.d.ts +1 -1
  61. package/dist/types/modes/components/welcome.d.ts +19 -3
  62. package/dist/types/modes/controllers/mcp-command-controller.d.ts +1 -1
  63. package/dist/types/modes/controllers/streaming-reveal.d.ts +1 -1
  64. package/dist/types/modes/interactive-mode.d.ts +1 -1
  65. package/dist/types/modes/setup-wizard/scenes/sign-in.d.ts +1 -1
  66. package/dist/types/modes/setup-wizard/scenes/types.d.ts +1 -1
  67. package/dist/types/modes/setup-wizard/scenes/web-search.d.ts +1 -1
  68. package/dist/types/modes/setup-wizard/wizard-overlay.d.ts +1 -1
  69. package/dist/types/modes/types.d.ts +2 -1
  70. package/dist/types/session/agent-session.d.ts +1 -1
  71. package/dist/types/session/auth-broker-config.d.ts +4 -0
  72. package/dist/types/session/session-manager.d.ts +1 -1
  73. package/dist/types/slash-commands/helpers/stats-dashboard.d.ts +13 -0
  74. package/dist/types/ssh/connection-manager.d.ts +8 -0
  75. package/dist/types/task/parallel.d.ts +2 -2
  76. package/dist/types/task/worktree.d.ts +2 -0
  77. package/dist/types/tools/ask.d.ts +4 -0
  78. package/dist/types/tools/conflict-detect.d.ts +16 -0
  79. package/dist/types/tools/github-cache.d.ts +7 -0
  80. package/dist/types/tools/sqlite-reader.d.ts +3 -0
  81. package/dist/types/tui/output-block.d.ts +3 -3
  82. package/dist/types/utils/changelog.d.ts +8 -0
  83. package/dist/types/web/scrapers/readthedocs.d.ts +3 -0
  84. package/dist/types/web/scrapers/types.d.ts +12 -0
  85. package/dist/types/web/search/providers/codex.d.ts +1 -1
  86. package/dist/types/web/search/providers/gemini.d.ts +1 -1
  87. package/examples/extensions/tools.ts +5 -4
  88. package/package.json +14 -11
  89. package/scripts/build-binary.ts +18 -23
  90. package/scripts/bundle-dist.ts +81 -0
  91. package/scripts/{dev-launch → omp} +1 -1
  92. package/scripts/{dev-launch-preload.ts → omp.ts} +1 -1
  93. package/src/async/job-manager.ts +57 -3
  94. package/src/autoresearch/dashboard.ts +1 -1
  95. package/src/autoresearch/prompt-setup.md +6 -6
  96. package/src/autoresearch/prompt.md +6 -6
  97. package/src/capability/fs.ts +10 -0
  98. package/src/cli/args.ts +1 -1
  99. package/src/cli/auth-gateway-cli.ts +1 -3
  100. package/src/cli/dry-balance-cli.ts +1 -1
  101. package/src/cli/gallery-cli.ts +1 -1
  102. package/src/cli/gallery-fixtures/fs.ts +1 -1
  103. package/src/cli/gallery-fixtures/types.ts +5 -1
  104. package/src/cli/list-models.ts +2 -1
  105. package/src/cli/usage-cli.ts +603 -0
  106. package/src/cli-commands.ts +1 -0
  107. package/src/cli.ts +69 -5
  108. package/src/commands/complete.ts +1 -1
  109. package/src/commands/launch.ts +1 -1
  110. package/src/commands/read.ts +6 -3
  111. package/src/commands/usage.ts +35 -0
  112. package/src/commit/agentic/agent.ts +1 -1
  113. package/src/commit/model-selection.ts +1 -1
  114. package/src/config/append-only-context-mode.ts +6 -12
  115. package/src/config/model-discovery.ts +554 -0
  116. package/src/config/model-registry.ts +231 -1019
  117. package/src/config/model-resolver.ts +113 -156
  118. package/src/config/model-roles.ts +74 -0
  119. package/src/config/models-config-schema.ts +57 -8
  120. package/src/config/models-config.ts +129 -0
  121. package/src/config/settings-schema.ts +18 -4
  122. package/src/config/settings.ts +37 -1
  123. package/src/dap/client.ts +124 -37
  124. package/src/dap/session.ts +259 -158
  125. package/src/debug/log-viewer.ts +1 -1
  126. package/src/debug/raw-sse.ts +1 -1
  127. package/src/edit/diff.ts +47 -3
  128. package/src/edit/hashline/block-resolver.ts +20 -1
  129. package/src/edit/hashline/diff.ts +36 -1
  130. package/src/edit/hashline/execute.ts +8 -2
  131. package/src/edit/index.ts +16 -1
  132. package/src/edit/modes/patch.ts +52 -0
  133. package/src/edit/modes/replace.ts +56 -22
  134. package/src/edit/notebook.ts +22 -2
  135. package/src/edit/renderer.ts +36 -10
  136. package/src/eval/__tests__/completion-bridge.test.ts +1 -1
  137. package/src/eval/backend.ts +0 -2
  138. package/src/eval/completion-bridge.ts +2 -1
  139. package/src/eval/idle-timeout.ts +2 -9
  140. package/src/eval/js/context-manager.ts +6 -8
  141. package/src/eval/js/executor.ts +6 -2
  142. package/src/eval/js/index.ts +0 -2
  143. package/src/eval/js/shared/helpers.ts +5 -6
  144. package/src/eval/js/shared/local-module-loader.ts +1 -1
  145. package/src/eval/js/shared/prelude.txt +62 -1
  146. package/src/eval/js/shared/rewrite-imports.ts +40 -22
  147. package/src/eval/js/shared/runtime.ts +1 -1
  148. package/src/eval/py/index.ts +0 -2
  149. package/src/eval/py/kernel.ts +19 -0
  150. package/src/eval/py/runner.py +107 -3
  151. package/src/exec/bash-executor.ts +3 -1
  152. package/src/export/html/template.generated.ts +1 -1
  153. package/src/export/html/template.js +3 -1
  154. package/src/extensibility/extensions/types.ts +3 -2
  155. package/src/extensibility/plugins/legacy-pi-compat.ts +20 -3
  156. package/src/hindsight/mental-models.ts +59 -12
  157. package/src/hindsight/state.ts +6 -1
  158. package/src/internal-urls/artifact-protocol.ts +11 -2
  159. package/src/internal-urls/docs-index.generated.ts +8 -8
  160. package/src/internal-urls/issue-pr-protocol.ts +12 -5
  161. package/src/internal-urls/router.ts +1 -1
  162. package/src/internal-urls/types.ts +1 -1
  163. package/src/lib/xai-http.ts +1 -1
  164. package/src/lsp/client.ts +118 -38
  165. package/src/lsp/clients/biome-client.ts +101 -39
  166. package/src/lsp/edits.ts +143 -95
  167. package/src/lsp/index.ts +31 -22
  168. package/src/lsp/render.ts +1 -1
  169. package/src/lsp/types.ts +2 -0
  170. package/src/lsp/utils.ts +28 -10
  171. package/src/main.ts +165 -17
  172. package/src/mcp/json-rpc.ts +35 -5
  173. package/src/mcp/transports/stdio.ts +7 -1
  174. package/src/memories/index.ts +2 -1
  175. package/src/mnemopi/backend.ts +25 -3
  176. package/src/mnemopi/state.ts +38 -2
  177. package/src/modes/components/agent-dashboard.ts +10 -7
  178. package/src/modes/components/assistant-message.ts +19 -13
  179. package/src/modes/components/bash-execution.ts +1 -1
  180. package/src/modes/components/copy-selector.ts +1 -1
  181. package/src/modes/components/diff.ts +13 -2
  182. package/src/modes/components/dynamic-border.ts +12 -3
  183. package/src/modes/components/extensions/extension-dashboard.ts +8 -5
  184. package/src/modes/components/extensions/extension-list.ts +1 -1
  185. package/src/modes/components/extensions/inspector-panel.ts +1 -1
  186. package/src/modes/components/footer.ts +1 -1
  187. package/src/modes/components/history-search.ts +1 -1
  188. package/src/modes/components/hook-editor.ts +8 -0
  189. package/src/modes/components/hook-input.ts +8 -0
  190. package/src/modes/components/hook-selector.ts +2 -2
  191. package/src/modes/components/model-selector.ts +4 -2
  192. package/src/modes/components/plan-review-overlay.ts +1 -1
  193. package/src/modes/components/session-observer-overlay.ts +2 -2
  194. package/src/modes/components/session-selector.ts +1 -1
  195. package/src/modes/components/settings-selector.ts +5 -1
  196. package/src/modes/components/status-line/component.ts +1 -1
  197. package/src/modes/components/tiny-title-download-progress.ts +1 -1
  198. package/src/modes/components/transcript-container.ts +258 -53
  199. package/src/modes/components/tree-selector.ts +3 -3
  200. package/src/modes/components/user-message-selector.ts +1 -1
  201. package/src/modes/components/user-message.ts +17 -5
  202. package/src/modes/components/visual-truncate.ts +1 -1
  203. package/src/modes/components/welcome.ts +108 -26
  204. package/src/modes/controllers/command-controller.ts +10 -3
  205. package/src/modes/controllers/event-controller.ts +73 -4
  206. package/src/modes/controllers/input-controller.ts +1 -1
  207. package/src/modes/controllers/mcp-command-controller.ts +1 -1
  208. package/src/modes/controllers/selector-controller.ts +1 -1
  209. package/src/modes/controllers/streaming-reveal.ts +85 -18
  210. package/src/modes/interactive-mode.ts +3 -9
  211. package/src/modes/setup-wizard/scenes/glyph.ts +1 -1
  212. package/src/modes/setup-wizard/scenes/providers.ts +1 -1
  213. package/src/modes/setup-wizard/scenes/sign-in.ts +1 -1
  214. package/src/modes/setup-wizard/scenes/theme.ts +1 -1
  215. package/src/modes/setup-wizard/scenes/types.ts +1 -1
  216. package/src/modes/setup-wizard/scenes/web-search.ts +1 -1
  217. package/src/modes/setup-wizard/wizard-overlay.ts +1 -1
  218. package/src/modes/types.ts +2 -1
  219. package/src/prompts/agents/explore.md +2 -2
  220. package/src/prompts/agents/librarian.md +1 -2
  221. package/src/prompts/agents/oracle.md +1 -1
  222. package/src/prompts/agents/plan.md +5 -5
  223. package/src/prompts/agents/task.md +5 -5
  224. package/src/prompts/ci-green-request.md +5 -7
  225. package/src/prompts/goals/goal-budget-limit.md +2 -2
  226. package/src/prompts/goals/goal-continuation.md +4 -4
  227. package/src/prompts/goals/goal-mode-active.md +1 -1
  228. package/src/prompts/memories/read-path.md +1 -1
  229. package/src/prompts/memories/stage_one_system.md +2 -2
  230. package/src/prompts/review-custom-request.md +1 -1
  231. package/src/prompts/system/agent-creation-architect.md +2 -2
  232. package/src/prompts/system/auto-continue.md +1 -1
  233. package/src/prompts/system/background-tan-dispatch.md +1 -1
  234. package/src/prompts/system/btw-user.md +2 -2
  235. package/src/prompts/system/commit-message-system.md +13 -1
  236. package/src/prompts/system/custom-system-prompt.md +1 -1
  237. package/src/prompts/system/eager-todo.md +2 -2
  238. package/src/prompts/system/irc-incoming.md +1 -1
  239. package/src/prompts/system/manual-continue.md +1 -1
  240. package/src/prompts/system/omfg-user.md +3 -4
  241. package/src/prompts/system/orchestrate-notice.md +9 -9
  242. package/src/prompts/system/plan-mode-active.md +4 -4
  243. package/src/prompts/system/plan-mode-subagent.md +4 -5
  244. package/src/prompts/system/plan-mode-tool-decision-reminder.md +1 -1
  245. package/src/prompts/system/project-prompt.md +2 -2
  246. package/src/prompts/system/subagent-system-prompt.md +4 -4
  247. package/src/prompts/system/system-prompt.md +13 -24
  248. package/src/prompts/system/title-system.md +2 -2
  249. package/src/prompts/system/ttsr-tool-reminder.md +1 -1
  250. package/src/prompts/system/workflow-notice.md +1 -1
  251. package/src/prompts/tools/ast-edit.md +1 -1
  252. package/src/prompts/tools/ast-grep.md +2 -2
  253. package/src/prompts/tools/bash.md +5 -7
  254. package/src/prompts/tools/browser.md +7 -7
  255. package/src/prompts/tools/debug.md +1 -1
  256. package/src/prompts/tools/eval.md +3 -3
  257. package/src/prompts/tools/find.md +0 -1
  258. package/src/prompts/tools/github.md +8 -7
  259. package/src/prompts/tools/goal.md +1 -1
  260. package/src/prompts/tools/image-gen.md +1 -1
  261. package/src/prompts/tools/inspect-image-system.md +1 -1
  262. package/src/prompts/tools/irc.md +15 -15
  263. package/src/prompts/tools/lsp.md +2 -2
  264. package/src/prompts/tools/patch.md +2 -2
  265. package/src/prompts/tools/read.md +3 -4
  266. package/src/prompts/tools/recall.md +1 -1
  267. package/src/prompts/tools/reflect.md +1 -1
  268. package/src/prompts/tools/render-mermaid.md +2 -2
  269. package/src/prompts/tools/replace.md +4 -10
  270. package/src/prompts/tools/rewind.md +2 -2
  271. package/src/prompts/tools/search-tool-bm25.md +1 -9
  272. package/src/prompts/tools/search.md +0 -1
  273. package/src/prompts/tools/ssh.md +0 -4
  274. package/src/prompts/tools/task.md +2 -3
  275. package/src/prompts/tools/todo.md +1 -1
  276. package/src/sdk.ts +23 -10
  277. package/src/session/agent-session.ts +44 -10
  278. package/src/session/auth-broker-config.ts +30 -1
  279. package/src/session/session-manager.ts +2 -2
  280. package/src/session/streaming-output.ts +23 -2
  281. package/src/slash-commands/builtin-registry.ts +20 -0
  282. package/src/slash-commands/helpers/stats-dashboard.ts +85 -0
  283. package/src/ssh/connection-manager.ts +27 -0
  284. package/src/task/commands.ts +2 -1
  285. package/src/task/executor.ts +61 -53
  286. package/src/task/index.ts +137 -60
  287. package/src/task/parallel.ts +3 -3
  288. package/src/task/render.ts +2 -2
  289. package/src/task/worktree.ts +64 -56
  290. package/src/thinking.ts +2 -1
  291. package/src/tiny/title-client.ts +26 -11
  292. package/src/tools/archive-reader.ts +30 -2
  293. package/src/tools/ask.ts +104 -21
  294. package/src/tools/ast-edit.ts +25 -5
  295. package/src/tools/auto-generated-guard.ts +20 -3
  296. package/src/tools/bash-interactive.ts +27 -7
  297. package/src/tools/bash.ts +54 -13
  298. package/src/tools/browser/launch.ts +11 -2
  299. package/src/tools/browser/readable.ts +19 -2
  300. package/src/tools/browser/registry.ts +4 -1
  301. package/src/tools/browser/render.ts +2 -2
  302. package/src/tools/browser/tab-supervisor.ts +55 -16
  303. package/src/tools/conflict-detect.ts +50 -4
  304. package/src/tools/debug.ts +1 -1
  305. package/src/tools/eval-render.ts +5 -5
  306. package/src/tools/eval.ts +0 -2
  307. package/src/tools/fetch.ts +33 -10
  308. package/src/tools/gh-cache-invalidation.ts +63 -8
  309. package/src/tools/gh-renderer.ts +1 -1
  310. package/src/tools/gh.ts +172 -29
  311. package/src/tools/github-cache.ts +70 -6
  312. package/src/tools/image-gen.ts +3 -9
  313. package/src/tools/irc.ts +5 -1
  314. package/src/tools/job.ts +1 -1
  315. package/src/tools/read.ts +202 -61
  316. package/src/tools/render-utils.ts +3 -3
  317. package/src/tools/resolve.ts +1 -1
  318. package/src/tools/search.ts +92 -29
  319. package/src/tools/sqlite-reader.ts +17 -5
  320. package/src/tools/ssh.ts +8 -8
  321. package/src/tools/todo.ts +38 -8
  322. package/src/tools/write.ts +118 -18
  323. package/src/tui/output-block.ts +4 -4
  324. package/src/utils/changelog.ts +27 -1
  325. package/src/utils/file-mentions.ts +2 -1
  326. package/src/web/scrapers/arxiv.ts +1 -1
  327. package/src/web/scrapers/go-pkg.ts +1 -1
  328. package/src/web/scrapers/iacr.ts +1 -1
  329. package/src/web/scrapers/readthedocs.ts +1 -1
  330. package/src/web/scrapers/twitter.ts +2 -1
  331. package/src/web/scrapers/types.ts +87 -8
  332. package/src/web/scrapers/wikipedia.ts +1 -1
  333. package/src/web/scrapers/youtube.ts +6 -1
  334. package/src/web/search/index.ts +1 -1
  335. package/src/web/search/providers/codex.ts +2 -1
  336. package/src/web/search/providers/gemini.ts +2 -3
  337. package/src/web/search/render.ts +8 -6
  338. package/dist/types/config/model-equivalence.d.ts +0 -24
  339. package/dist/types/config/model-id-affixes.d.ts +0 -12
  340. package/dist/types/config/model-provider-priority.d.ts +0 -1
  341. package/dist/types/exec/idle-timeout-watchdog.d.ts +0 -18
  342. package/src/config/model-equivalence.ts +0 -875
  343. package/src/config/model-id-affixes.ts +0 -81
  344. package/src/config/model-provider-priority.ts +0 -56
  345. package/src/exec/idle-timeout-watchdog.ts +0 -126
@@ -31,10 +31,9 @@ Subagents have no conversation history. Every fact, file path, and direction the
31
31
 
32
32
  <rules>
33
33
  - **Maximize batch width.** Spawn the widest parallel set the work decomposes into. NEVER spawn a single-task batch for divisible work, or defer work that could have been concurrent.
34
- - NEVER assign tasks to run project-wide build/test/lint. Caller verifies after the batch.
35
- - **Subagents do not verify, lint, or format.** Every assignment MUST instruct the subagent to skip all gates and formatters. You run them once at the end across the union of changed files — avoids redundant runs and racing formatter passes.
34
+ - **Subagents do not verify, lint, or format.** Every assignment MUST instruct the subagent to skip all gates, formatters, and project-wide build/test/lint. You run them once at the end across the union of changed files — avoids redundant runs and racing formatter passes.
36
35
  - No globs, no "update all", no package-wide scope. Fan out.
37
- - Do not concern yourself with how agents might overlap on certain actions. Never use it as an excuse to go slower: they can resolve collisions in real-time with the harness facilities.
36
+ - NEVER slow down or serialize because tasks might overlap on some files. Agents resolve collisions among themselves in real time.
38
37
  - Pass large payloads via `local://<path>` URIs, not inline. {{#if contextEnabled}} (other than the context){{/if}}
39
38
  {{#if contextEnabled}}- Put shared constraints in `context` once; do not duplicate across assignments.{{/if}}
40
39
  - Prefer agents that investigate **and** edit in one pass; only spin a read-only discovery step when affected files are genuinely unknown.
@@ -12,7 +12,7 @@ Allowed `op` values are only `init`, `start`, `done`, `drop`, `rm`, `append`, `n
12
12
  |`start`|`task`|Mark in progress|
13
13
  |`done`|`task` or `phase`|Mark completed|
14
14
  |`drop`|`task` or `phase`|Mark abandoned|
15
- |`rm`|`task` or `phase`|Remove|
15
+ |`rm`|`task` or `phase` (optional)|Remove task or phase's tasks; omit both to clear the entire list|
16
16
  |`append`|`phase`, `items: string[]`|Append tasks to `phase`; lazily creates phase|
17
17
  |`note`|`task`, `text`|Append a note to a task. Reminders for future-you only.|
18
18
  |`view`|—|Read-only: echo the current list without modifying it|
package/src/sdk.ts CHANGED
@@ -19,6 +19,7 @@ import {
19
19
  getOpenAICodexTransportDetails,
20
20
  prewarmOpenAICodexResponses,
21
21
  } from "@oh-my-pi/pi-ai/providers/openai-codex-responses";
22
+ import { DEFAULT_MODEL_PER_PROVIDER } from "@oh-my-pi/pi-catalog/provider-models";
22
23
  import type { Component } from "@oh-my-pi/pi-tui";
23
24
  import {
24
25
  $env,
@@ -41,7 +42,6 @@ import { createApiKeyResolver } from "./config/api-key-resolver";
41
42
  import { shouldEnableAppendOnlyContext } from "./config/append-only-context-mode";
42
43
  import { ModelRegistry } from "./config/model-registry";
43
44
  import {
44
- defaultModelPerProvider,
45
45
  formatModelString,
46
46
  getModelMatchPreferences,
47
47
  parseModelPattern,
@@ -531,11 +531,18 @@ function resolveSnapshotTtlMs(): number {
531
531
  * override to re-mint access tokens when needed.
532
532
  */
533
533
  export async function discoverAuthStorage(agentDir: string = getDefaultAgentDir()): Promise<AuthStorage> {
534
- const brokerConfig = await resolveAuthBrokerConfig();
534
+ const brokerConfigPromise = resolveAuthBrokerConfig();
535
+ const cachePath = getAuthBrokerSnapshotCachePath();
536
+ // Warm the encrypted snapshot cache into the page cache while the broker
537
+ // config resolves (it may shell out for a `!command` token). Decryption
538
+ // needs the resolved token, so the real cache read cannot start earlier.
539
+ void Bun.file(cachePath)
540
+ .arrayBuffer()
541
+ .catch(() => undefined);
542
+ const brokerConfig = await brokerConfigPromise;
535
543
  if (brokerConfig) {
536
544
  const client = new AuthBrokerClient({ url: brokerConfig.url, token: brokerConfig.token });
537
545
  const ttlMs = resolveSnapshotTtlMs();
538
- const cachePath = getAuthBrokerSnapshotCachePath();
539
546
  const persist =
540
547
  ttlMs > 0
541
548
  ? (snapshot: SnapshotResponse): void => {
@@ -1730,7 +1737,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1730
1737
  // the winning provider (e.g. anthropic's claude-3-5-sonnet-20240620)
1731
1738
  // instead of the intended provider default (claude-sonnet-4-6). Mirrors
1732
1739
  // findInitialModel's precedence.
1733
- for (const [provider, defaultId] of Object.entries(defaultModelPerProvider)) {
1740
+ for (const [provider, defaultId] of Object.entries(DEFAULT_MODEL_PER_PROVIDER)) {
1734
1741
  const preferred = fallbackCandidates.find(
1735
1742
  candidate => candidate.provider === provider && candidate.id === defaultId,
1736
1743
  );
@@ -2342,7 +2349,8 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
2342
2349
  }
2343
2350
 
2344
2351
  if (model?.api === "openai-codex-responses") {
2345
- const codexModel = model;
2352
+ // `.api` equality doesn't narrow the generic; the guard makes this cast sound.
2353
+ const codexModel = model as Model<"openai-codex-responses">;
2346
2354
  const codexTransport = getOpenAICodexTransportDetails(codexModel, {
2347
2355
  sessionId: providerSessionId,
2348
2356
  baseUrl: codexModel.baseUrl,
@@ -2373,12 +2381,17 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
2373
2381
  }
2374
2382
 
2375
2383
  // Start LSP warmup in the background so startup does not block on language server initialization.
2376
- // Print/script invocations (`hasUI=false`) don't render the warmup status indicator AND typically
2377
- // finish before LSP servers would have stabilizedwarming them just spends CPU parsing big
2378
- // `initialize` responses concurrently with the LLM stream consumer, jittering perceived latency.
2379
- // Tools that need an LSP server still spin one up on demand through `getOrCreateClient`.
2384
+ // With `lsp.lazy` (the default) the warmup is skipped: recognized servers are still discovered and
2385
+ // surfaced in the UI as "available", but cold-start on first use the lsp tool or an edit/write
2386
+ // touching a matching file type through `getOrCreateClient`.
2387
+ // Print/script invocations (`hasUI=false`) skip it regardless: they don't render the warmup status
2388
+ // indicator AND typically finish before LSP servers would have stabilized — warming them just spends
2389
+ // CPU parsing big `initialize` responses concurrently with the LLM stream consumer, jittering
2390
+ // perceived latency.
2380
2391
  let lspServers: CreateAgentSessionResult["lspServers"];
2381
- if (enableLsp && options.hasUI && settings.get("lsp.diagnosticsOnWrite")) {
2392
+ if (enableLsp && options.hasUI && settings.get("lsp.lazy")) {
2393
+ lspServers = discoverStartupLspServers(cwd, "available");
2394
+ } else if (enableLsp && options.hasUI) {
2382
2395
  lspServers = discoverStartupLspServers(cwd);
2383
2396
  if (lspServers.length > 0) {
2384
2397
  void (async () => {
@@ -79,14 +79,14 @@ import {
79
79
  clearAnthropicFastModeFallback,
80
80
  deriveClaudeDeviceId,
81
81
  Effort,
82
- getSupportedEfforts,
83
82
  isContextOverflow,
84
83
  isUsageLimitError,
85
- modelsAreEqual,
86
84
  parseRateLimitReason,
87
85
  resolveServiceTier,
88
86
  streamSimple,
89
87
  } from "@oh-my-pi/pi-ai";
88
+ import { getSupportedEfforts } from "@oh-my-pi/pi-catalog/model-thinking";
89
+ import { modelsAreEqual } from "@oh-my-pi/pi-catalog/models";
90
90
  import { countTokens, MacOSPowerAssertion } from "@oh-my-pi/pi-natives";
91
91
  import {
92
92
  extractRetryHint,
@@ -105,7 +105,7 @@ import { classifyDifficulty } from "../auto-thinking/classifier";
105
105
  import { reset as resetCapabilities } from "../capability";
106
106
  import type { Rule } from "../capability/rule";
107
107
  import { shouldEnableAppendOnlyContext } from "../config/append-only-context-mode";
108
- import { MODEL_ROLE_IDS, type ModelRegistry } from "../config/model-registry";
108
+ import type { ModelRegistry } from "../config/model-registry";
109
109
  import {
110
110
  extractExplicitThinkingSelector,
111
111
  formatModelSelectorValue,
@@ -115,6 +115,7 @@ import {
115
115
  type ResolvedModelRoleValue,
116
116
  resolveModelRoleValue,
117
117
  } from "../config/model-resolver";
118
+ import { MODEL_ROLE_IDS } from "../config/model-roles";
118
119
  import { expandPromptTemplate, type PromptTemplate } from "../config/prompt-templates";
119
120
  import type { Settings, SkillsSettings } from "../config/settings";
120
121
  import { onAppendOnlyModeChanged } from "../config/settings";
@@ -287,6 +288,20 @@ export type AgentSessionEventListener = (event: AgentSessionEvent) => void;
287
288
  export type AsyncJobSnapshotItem = Pick<AsyncJob, "id" | "type" | "status" | "label" | "startTime">;
288
289
 
289
290
  const EMPTY_STOP_MAX_RETRIES = 3;
291
+ const RETRY_BACKOFF_MAX_DELAY_MS = 8_000;
292
+ const RETRY_BACKOFF_JITTER_RATIO = 0.25;
293
+
294
+ function calculateRetryBackoffDelayMs(baseDelayMs: number, attempt: number): number {
295
+ const cappedDelayMs = Math.min(Math.max(0, baseDelayMs) * 2 ** Math.max(0, attempt - 1), RETRY_BACKOFF_MAX_DELAY_MS);
296
+ const jitter = 1 - Math.random() * RETRY_BACKOFF_JITTER_RATIO;
297
+ return cappedDelayMs * jitter;
298
+ }
299
+
300
+ /**
301
+ * Slack added past a sibling credential's block expiry before retrying, so
302
+ * the next getApiKey lands after the block has actually lapsed.
303
+ */
304
+ const SIBLING_UNBLOCK_BUFFER_MS = 1_000;
290
305
  const NON_WHITESPACE_RE = /\S/;
291
306
 
292
307
  function hasNonWhitespace(value: string): boolean {
@@ -8307,13 +8322,16 @@ export class AgentSession {
8307
8322
 
8308
8323
  const errorMessage = message.errorMessage || "Unknown error";
8309
8324
  const parsedRetryAfterMs = this.#parseRetryAfterMsFromError(errorMessage);
8310
- let delayMs = retrySettings.baseDelayMs * 2 ** (this.#retryAttempt - 1);
8325
+ let delayMs = calculateRetryBackoffDelayMs(retrySettings.baseDelayMs, this.#retryAttempt);
8311
8326
  let switchedCredential = false;
8312
8327
  let switchedModel = false;
8328
+ // Set when a usage-limit error pinned the wait to credential
8329
+ // availability — suppresses the generic retry-after bump below.
8330
+ let usageLimitWaitMs: number | undefined;
8313
8331
 
8314
8332
  if (this.model && isUsageLimitError(errorMessage)) {
8315
8333
  const retryAfterMs = parsedRetryAfterMs ?? calculateRateLimitBackoffMs(parseRateLimitReason(errorMessage));
8316
- const switched = await this.#modelRegistry.authStorage.markUsageLimitReached(
8334
+ const outcome = await this.#modelRegistry.authStorage.markUsageLimitReached(
8317
8335
  this.model.provider,
8318
8336
  this.sessionId,
8319
8337
  {
@@ -8321,12 +8339,28 @@ export class AgentSession {
8321
8339
  baseUrl: this.model.baseUrl,
8322
8340
  },
8323
8341
  );
8324
- if (switched) {
8342
+ if (outcome.switched) {
8325
8343
  switchedCredential = true;
8326
8344
  delayMs = 0;
8327
- } else if (retryAfterMs > delayMs) {
8328
- // No more accounts to switch to wait out the backoff
8329
- delayMs = retryAfterMs;
8345
+ } else {
8346
+ // No sibling credential is usable right now. Wait for whichever
8347
+ // comes first: the provider's retry-after window for the current
8348
+ // account, or the earliest moment a temporarily blocked sibling
8349
+ // frees up (e.g. a 60s post-401 block or a 5-min usage-probe
8350
+ // block) — the next attempt's getApiKey re-ranks and picks it up.
8351
+ // Without this, one short-lived sibling block escalates a
8352
+ // recoverable situation into the provider's multi-hour wait and
8353
+ // trips the fail-fast cap below.
8354
+ usageLimitWaitMs = retryAfterMs;
8355
+ if (outcome.retryAtMs !== undefined) {
8356
+ const siblingWaitMs = Math.max(0, outcome.retryAtMs - Date.now()) + SIBLING_UNBLOCK_BUFFER_MS;
8357
+ if (siblingWaitMs < usageLimitWaitMs) {
8358
+ usageLimitWaitMs = siblingWaitMs;
8359
+ }
8360
+ }
8361
+ if (usageLimitWaitMs > delayMs) {
8362
+ delayMs = usageLimitWaitMs;
8363
+ }
8330
8364
  }
8331
8365
  }
8332
8366
 
@@ -8338,7 +8372,7 @@ export class AgentSession {
8338
8372
  }
8339
8373
  if (switchedModel) {
8340
8374
  delayMs = 0;
8341
- } else if (parsedRetryAfterMs && parsedRetryAfterMs > delayMs) {
8375
+ } else if (usageLimitWaitMs === undefined && parsedRetryAfterMs && parsedRetryAfterMs > delayMs) {
8342
8376
  delayMs = parsedRetryAfterMs;
8343
8377
  }
8344
8378
  }
@@ -65,13 +65,42 @@ async function readConfigYaml(): Promise<ConfigSnapshot> {
65
65
  }
66
66
  }
67
67
 
68
+ /**
69
+ * Process-lifetime memo for {@link resolveAuthBrokerConfig}. Keyed on the env
70
+ * inputs (plus agent dir, which decides which config.yml is read) so tests
71
+ * that flip `OMP_AUTH_BROKER_*` between cases still observe the change, while
72
+ * repeated resolution within one CLI invocation (startup, subagent sessions)
73
+ * skips the config.yml read and any `!command` token resolution.
74
+ */
75
+ let cachedConfigKey: string | null = null;
76
+ let cachedConfigPromise: Promise<AuthBrokerClientConfig | null> | null = null;
77
+
68
78
  /**
69
79
  * Read broker configuration. Returns null when the URL is missing
70
80
  * (broker disabled — local store is used). Throws when URL is set but no
71
81
  * token is available — the caller cannot fall back silently because the
72
82
  * user explicitly asked to use the broker.
83
+ *
84
+ * Successful resolutions (including "no broker configured") are memoized for
85
+ * the process lifetime; failures are not, so a missing token can be fixed and
86
+ * retried. Concurrent callers share one in-flight resolution.
73
87
  */
74
- export async function resolveAuthBrokerConfig(): Promise<AuthBrokerClientConfig | null> {
88
+ export function resolveAuthBrokerConfig(): Promise<AuthBrokerClientConfig | null> {
89
+ const key = `${process.env.OMP_AUTH_BROKER_URL ?? ""}\u0000${process.env.OMP_AUTH_BROKER_TOKEN ?? ""}\u0000${getAgentDir()}`;
90
+ if (cachedConfigPromise && cachedConfigKey === key) return cachedConfigPromise;
91
+ const promise = resolveAuthBrokerConfigUncached();
92
+ cachedConfigKey = key;
93
+ cachedConfigPromise = promise;
94
+ promise.catch(() => {
95
+ if (cachedConfigPromise === promise) {
96
+ cachedConfigPromise = null;
97
+ cachedConfigKey = null;
98
+ }
99
+ });
100
+ return promise;
101
+ }
102
+
103
+ async function resolveAuthBrokerConfigUncached(): Promise<AuthBrokerClientConfig | null> {
75
104
  const envUrl = process.env.OMP_AUTH_BROKER_URL;
76
105
  const envToken = process.env.OMP_AUTH_BROKER_TOKEN;
77
106
 
@@ -1516,10 +1516,10 @@ class NdjsonFileWriter {
1516
1516
  }
1517
1517
  }
1518
1518
 
1519
- /** Get recent sessions for display in welcome screen */
1519
+ /** Get recent sessions for display in welcome screen (which reserves WELCOME_SESSION_SLOTS rows) */
1520
1520
  export async function getRecentSessions(
1521
1521
  sessionDir: string,
1522
- limit = 3,
1522
+ limit = 4,
1523
1523
  storage: SessionStorage = new FileSessionStorage(),
1524
1524
  ): Promise<RecentSessionInfo[]> {
1525
1525
  const sessions = await getSortedSessions(sessionDir, storage);
@@ -650,6 +650,7 @@ export class OutputSink {
650
650
  #sawData = false;
651
651
  #truncated = false;
652
652
  #lastChunkTime = 0;
653
+ #pendingChunk = "";
653
654
 
654
655
  // Per-line column cap streaming state (persists across `push` calls so a
655
656
  // long line split across chunks still trips the same trigger).
@@ -701,14 +702,20 @@ export class OutputSink {
701
702
  push(chunk: string): void {
702
703
  chunk = sanitizeWithOptionalSixelPassthrough(chunk, sanitizeText);
703
704
 
704
- // Throttled onChunk: only call the callback when enough time has passed.
705
+ // Throttled onChunk: coalesce chunks arriving inside the throttle window
706
+ // and flush the buffered concatenation on the next eligible tick (plus a
707
+ // final flush in dump()) so the preview never has silent gaps.
705
708
  // Live preview gets the raw (pre-cap) chunk so the TUI never lags behind
706
709
  // what reached the sink — the column cap is for the persisted LLM view.
707
710
  if (this.#onChunk) {
708
711
  const now = Date.now();
709
712
  if (now - this.#lastChunkTime >= this.#chunkThrottleMs) {
710
713
  this.#lastChunkTime = now;
711
- this.#onChunk(chunk);
714
+ const merged = this.#pendingChunk + chunk;
715
+ this.#pendingChunk = "";
716
+ this.#onChunk(merged);
717
+ } else {
718
+ this.#pendingChunk += chunk;
712
719
  }
713
720
  }
714
721
 
@@ -880,6 +887,11 @@ export class OutputSink {
880
887
  const sink = Bun.file(this.#artifactPath).writer();
881
888
  this.#file = { path: this.#artifactPath, artifactId: this.#artifactId, sink };
882
889
 
890
+ // Head-retained bytes precede the rolling tail buffer in the capture.
891
+ if (this.#head.length > 0) {
892
+ sink.write(this.#head);
893
+ }
894
+
883
895
  // Flush existing buffer to file BEFORE it gets trimmed further.
884
896
  if (this.#buffer.length > 0) {
885
897
  sink.write(this.#buffer);
@@ -946,10 +958,19 @@ export class OutputSink {
946
958
  this.#columnEllipsisAdded = false;
947
959
  this.#columnDroppedBytes = 0;
948
960
  this.#columnTruncatedLines = 0;
961
+ this.#pendingChunk = "";
949
962
  }
950
963
 
951
964
  async dump(notice?: string): Promise<OutputSummary> {
952
965
  const noticeLine = notice ? `[${notice}]\n` : "";
966
+
967
+ // Flush any chunk still held back by the throttle so the live preview
968
+ // ends with the complete stream.
969
+ if (this.#onChunk && this.#pendingChunk.length > 0) {
970
+ const pending = this.#pendingChunk;
971
+ this.#pendingChunk = "";
972
+ this.#onChunk(pending);
973
+ }
953
974
  const totalLines = this.#sawData ? this.#totalLines + 1 : 0;
954
975
 
955
976
  if (this.#file) await this.#file.sink.end();
@@ -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";
@@ -542,6 +543,25 @@ const BUILTIN_SLASH_COMMAND_REGISTRY: ReadonlyArray<SlashCommandSpec> = [
542
543
  runtime.ctx.editor.setText("");
543
544
  },
544
545
  },
546
+ {
547
+ name: "stats",
548
+ description: "Launch the local stats dashboard",
549
+ inlineHint: "[--port <port>]",
550
+ allowArgs: true,
551
+ handle: async (command, runtime) => {
552
+ const parsed = parseStatsDashboardArgs(command.args);
553
+ if ("error" in parsed) return usage(parsed.error, runtime);
554
+
555
+ await runtime.output("Syncing session files...");
556
+ try {
557
+ const result = await launchStatsDashboard(parsed);
558
+ await runtime.output(result.message);
559
+ } catch (error) {
560
+ await runtime.output(`Stats dashboard failed: ${errorMessage(error)}`);
561
+ }
562
+ return commandConsumed();
563
+ },
564
+ },
545
565
  {
546
566
  name: "changelog",
547
567
  description: "Show changelog entries",
@@ -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
+ }
@@ -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) {
@@ -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
  /**
@@ -1285,59 +1285,67 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
1285
1285
 
1286
1286
  const { normalized: normalizedOutputSchema } = normalizeSchema(outputSchema);
1287
1287
 
1288
- const { session } = await awaitAbortable(
1289
- createAgentSession({
1290
- cwd: worktree ?? cwd,
1291
- authStorage,
1292
- modelRegistry,
1293
- settings: subagentSettings,
1294
- model,
1295
- thinkingLevel: effectiveThinkingLevel,
1296
- toolNames,
1297
- outputSchema,
1298
- requireYieldTool: true,
1299
- contextFiles: options.contextFiles,
1300
- skills: options.skills,
1301
- promptTemplates: options.promptTemplates,
1302
- workspaceTree: options.workspaceTree,
1303
- rules: options.rules,
1304
- preloadedExtensionPaths: options.preloadedExtensionPaths,
1305
- preloadedCustomToolPaths: options.preloadedCustomToolPaths,
1306
- systemPrompt: defaultPrompt => {
1307
- const subagentPrompt = prompt.render(subagentSystemPromptTemplate, {
1308
- agent: agent.systemPrompt,
1309
- context: options.context?.trim() ?? "",
1310
- planReference: options.planReference?.content ?? "",
1311
- planReferencePath: options.planReference?.path ?? "",
1312
- worktree: worktree ?? "",
1313
- outputSchema: normalizedOutputSchema,
1314
- contextFile: contextFileForPrompt,
1315
- ircPeers: ircEnabled ? renderIrcPeerRoster(id) : "",
1316
- ircSelfId: ircEnabled ? id : "",
1317
- });
1318
- return defaultPrompt.length === 0
1319
- ? [subagentPrompt]
1320
- : [...defaultPrompt.slice(0, -1), subagentPrompt, defaultPrompt[defaultPrompt.length - 1]];
1321
- },
1322
- sessionManager,
1323
- hasUI: false,
1324
- spawns: spawnsEnv,
1325
- taskDepth: childDepth,
1326
- parentHindsightSessionState: options.parentHindsightSessionState,
1327
- parentMnemopiSessionState: options.parentMnemopiSessionState,
1328
- parentTaskPrefix: id,
1329
- agentId: id,
1330
- agentDisplayName: agent.name,
1331
- enableLsp: lspEnabled,
1332
- skipPythonPreflight,
1333
- enableMCP,
1334
- mcpManager: options.mcpManager,
1335
- customTools: mcpProxyTools.length > 0 ? mcpProxyTools : undefined,
1336
- localProtocolOptions: options.localProtocolOptions,
1337
- telemetry: subagentTelemetry,
1338
- parentEvalSessionId: options.parentEvalSessionId,
1339
- }),
1340
- );
1288
+ const sessionPromise = createAgentSession({
1289
+ cwd: worktree ?? cwd,
1290
+ authStorage,
1291
+ modelRegistry,
1292
+ settings: subagentSettings,
1293
+ model,
1294
+ thinkingLevel: effectiveThinkingLevel,
1295
+ toolNames,
1296
+ outputSchema,
1297
+ requireYieldTool: true,
1298
+ contextFiles: options.contextFiles,
1299
+ skills: options.skills,
1300
+ promptTemplates: options.promptTemplates,
1301
+ workspaceTree: options.workspaceTree,
1302
+ rules: options.rules,
1303
+ preloadedExtensionPaths: options.preloadedExtensionPaths,
1304
+ preloadedCustomToolPaths: options.preloadedCustomToolPaths,
1305
+ systemPrompt: defaultPrompt => {
1306
+ const subagentPrompt = prompt.render(subagentSystemPromptTemplate, {
1307
+ agent: agent.systemPrompt,
1308
+ context: options.context?.trim() ?? "",
1309
+ planReference: options.planReference?.content ?? "",
1310
+ planReferencePath: options.planReference?.path ?? "",
1311
+ worktree: worktree ?? "",
1312
+ outputSchema: normalizedOutputSchema,
1313
+ contextFile: contextFileForPrompt,
1314
+ ircPeers: ircEnabled ? renderIrcPeerRoster(id) : "",
1315
+ ircSelfId: ircEnabled ? id : "",
1316
+ });
1317
+ return defaultPrompt.length === 0
1318
+ ? [subagentPrompt]
1319
+ : [...defaultPrompt.slice(0, -1), subagentPrompt, defaultPrompt[defaultPrompt.length - 1]];
1320
+ },
1321
+ sessionManager,
1322
+ hasUI: false,
1323
+ spawns: spawnsEnv,
1324
+ taskDepth: childDepth,
1325
+ parentHindsightSessionState: options.parentHindsightSessionState,
1326
+ parentMnemopiSessionState: options.parentMnemopiSessionState,
1327
+ parentTaskPrefix: id,
1328
+ agentId: id,
1329
+ agentDisplayName: agent.name,
1330
+ enableLsp: lspEnabled,
1331
+ skipPythonPreflight,
1332
+ enableMCP,
1333
+ mcpManager: options.mcpManager,
1334
+ customTools: mcpProxyTools.length > 0 ? mcpProxyTools : undefined,
1335
+ localProtocolOptions: options.localProtocolOptions,
1336
+ telemetry: subagentTelemetry,
1337
+ parentEvalSessionId: options.parentEvalSessionId,
1338
+ });
1339
+ let session: AgentSession;
1340
+ try {
1341
+ ({ session } = await awaitAbortable(sessionPromise));
1342
+ } catch (err) {
1343
+ // Abort raced session startup. The session may still resolve later
1344
+ // holding live LSP/MCP child processes — dispose it when it does so
1345
+ // a cancelled subagent cannot leak them.
1346
+ void sessionPromise.then(created => created.session.dispose()).catch(() => {});
1347
+ throw err;
1348
+ }
1341
1349
 
1342
1350
  activeSession = session;
1343
1351