@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
@@ -162,6 +162,7 @@ export interface ExecutorOptions {
162
162
  description?: string;
163
163
  index: number;
164
164
  id: string;
165
+ parentToolCallId?: string;
165
166
  modelOverride?: string | string[];
166
167
  /**
167
168
  * Active model selector of the parent session, used as an auth-aware fallback
@@ -840,6 +841,7 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
840
841
  agent: agent.name,
841
842
  agentSource: agent.source,
842
843
  task,
844
+ parentToolCallId: options.parentToolCallId,
843
845
  assignment,
844
846
  progress: { ...progress },
845
847
  sessionFile: subtaskSessionFile,
@@ -922,20 +924,16 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
922
924
  progress.recentOutput = [];
923
925
  };
924
926
 
927
+ const emitSubagentEvent = (event: AgentSessionEvent) => {
928
+ if (!options.eventBus) return;
929
+ options.eventBus.emit(TASK_SUBAGENT_EVENT_CHANNEL, {
930
+ id,
931
+ event,
932
+ });
933
+ };
934
+
925
935
  const processEvent = (event: AgentEvent) => {
926
936
  if (resolved) return;
927
-
928
- if (options.eventBus) {
929
- options.eventBus.emit(TASK_SUBAGENT_EVENT_CHANNEL, {
930
- index,
931
- agent: agent.name,
932
- agentSource: agent.source,
933
- task,
934
- assignment,
935
- event,
936
- });
937
- }
938
-
939
937
  const now = Date.now();
940
938
  let flushProgress = false;
941
939
 
@@ -1285,59 +1283,67 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
1285
1283
 
1286
1284
  const { normalized: normalizedOutputSchema } = normalizeSchema(outputSchema);
1287
1285
 
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
- );
1286
+ const sessionPromise = createAgentSession({
1287
+ cwd: worktree ?? cwd,
1288
+ authStorage,
1289
+ modelRegistry,
1290
+ settings: subagentSettings,
1291
+ model,
1292
+ thinkingLevel: effectiveThinkingLevel,
1293
+ toolNames,
1294
+ outputSchema,
1295
+ requireYieldTool: true,
1296
+ contextFiles: options.contextFiles,
1297
+ skills: options.skills,
1298
+ promptTemplates: options.promptTemplates,
1299
+ workspaceTree: options.workspaceTree,
1300
+ rules: options.rules,
1301
+ preloadedExtensionPaths: options.preloadedExtensionPaths,
1302
+ preloadedCustomToolPaths: options.preloadedCustomToolPaths,
1303
+ systemPrompt: defaultPrompt => {
1304
+ const subagentPrompt = prompt.render(subagentSystemPromptTemplate, {
1305
+ agent: agent.systemPrompt,
1306
+ context: options.context?.trim() ?? "",
1307
+ planReference: options.planReference?.content ?? "",
1308
+ planReferencePath: options.planReference?.path ?? "",
1309
+ worktree: worktree ?? "",
1310
+ outputSchema: normalizedOutputSchema,
1311
+ contextFile: contextFileForPrompt,
1312
+ ircPeers: ircEnabled ? renderIrcPeerRoster(id) : "",
1313
+ ircSelfId: ircEnabled ? id : "",
1314
+ });
1315
+ return defaultPrompt.length === 0
1316
+ ? [subagentPrompt]
1317
+ : [...defaultPrompt.slice(0, -1), subagentPrompt, defaultPrompt[defaultPrompt.length - 1]];
1318
+ },
1319
+ sessionManager,
1320
+ hasUI: false,
1321
+ spawns: spawnsEnv,
1322
+ taskDepth: childDepth,
1323
+ parentHindsightSessionState: options.parentHindsightSessionState,
1324
+ parentMnemopiSessionState: options.parentMnemopiSessionState,
1325
+ parentTaskPrefix: id,
1326
+ agentId: id,
1327
+ agentDisplayName: agent.name,
1328
+ enableLsp: lspEnabled,
1329
+ skipPythonPreflight,
1330
+ enableMCP,
1331
+ mcpManager: options.mcpManager,
1332
+ customTools: mcpProxyTools.length > 0 ? mcpProxyTools : undefined,
1333
+ localProtocolOptions: options.localProtocolOptions,
1334
+ telemetry: subagentTelemetry,
1335
+ parentEvalSessionId: options.parentEvalSessionId,
1336
+ });
1337
+ let session: AgentSession;
1338
+ try {
1339
+ ({ session } = await awaitAbortable(sessionPromise));
1340
+ } catch (err) {
1341
+ // Abort raced session startup. The session may still resolve later
1342
+ // holding live LSP/MCP child processes — dispose it when it does so
1343
+ // a cancelled subagent cannot leak them.
1344
+ void sessionPromise.then(created => created.session.dispose()).catch(() => {});
1345
+ throw err;
1346
+ }
1341
1347
 
1342
1348
  activeSession = session;
1343
1349
 
@@ -1346,6 +1352,7 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
1346
1352
  options.eventBus.emit(TASK_SUBAGENT_LIFECYCLE_CHANNEL, {
1347
1353
  id,
1348
1354
  agent: agent.name,
1355
+ parentToolCallId: options.parentToolCallId,
1349
1356
  agentSource: agent.source,
1350
1357
  description: options.description,
1351
1358
  status: "started",
@@ -1444,6 +1451,7 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
1444
1451
 
1445
1452
  const MAX_YIELD_RETRIES = 3;
1446
1453
  unsubscribe = session.subscribe(event => {
1454
+ emitSubagentEvent(event);
1447
1455
  if (event.type === "auto_retry_start") {
1448
1456
  progress.retryState = {
1449
1457
  attempt: event.attempt,
@@ -1696,6 +1704,7 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
1696
1704
  options.eventBus.emit(TASK_SUBAGENT_LIFECYCLE_CHANNEL, {
1697
1705
  id,
1698
1706
  agent: agent.name,
1707
+ parentToolCallId: options.parentToolCallId,
1699
1708
  agentSource: agent.source,
1700
1709
  description: options.description,
1701
1710
  status: progress.status as "completed" | "failed" | "aborted",
package/src/task/index.ts CHANGED
@@ -43,7 +43,7 @@ import type { LocalProtocolOptions } from "../internal-urls";
43
43
  import { loadOverallPlanReference } from "../plan-mode/plan-handoff";
44
44
  import { generateCommitMessage } from "../utils/commit-message-generator";
45
45
  import * as git from "../utils/git";
46
- import { discoverAgents, getAgent } from "./discovery";
46
+ import { type DiscoveryResult, discoverAgents, getAgent } from "./discovery";
47
47
  import { runSubprocess } from "./executor";
48
48
  import { AgentOutputManager } from "./output-manager";
49
49
  import { mapWithConcurrencyLimit, Semaphore } from "./parallel";
@@ -119,6 +119,7 @@ export type {
119
119
  AgentDefinition,
120
120
  AgentProgress,
121
121
  SingleResult,
122
+ SubagentEventPayload,
122
123
  SubagentLifecyclePayload,
123
124
  SubagentProgressPayload,
124
125
  TaskParams,
@@ -242,6 +243,88 @@ function validateTaskModeParams(simpleMode: TaskSimpleMode, params: TaskParams):
242
243
  return "task.simple is set to independent, so the task tool does not accept `context` or `schema`. Put all required background and output expectations inside each task assignment or the selected agent definition.";
243
244
  }
244
245
 
246
+ /** Sentinel for async jobs whose subagent finished with a failing result; batch counters are already updated. */
247
+ class TaskJobError extends Error {}
248
+
249
+ /**
250
+ * Validate task ids: every task needs a non-empty id and ids must be unique
251
+ * (case-insensitive). Returns a problem description, or undefined when valid.
252
+ */
253
+ function validateTaskIds(tasks: TaskParams["tasks"]): string | undefined {
254
+ const missingTaskIndexes: number[] = [];
255
+ const idIndexes = new Map<string, number[]>();
256
+
257
+ for (let i = 0; i < tasks.length; i++) {
258
+ const id = tasks[i]?.id;
259
+ if (typeof id !== "string" || id.trim() === "") {
260
+ missingTaskIndexes.push(i);
261
+ continue;
262
+ }
263
+ const normalizedId = id.toLowerCase();
264
+ const indexes = idIndexes.get(normalizedId);
265
+ if (indexes) {
266
+ indexes.push(i);
267
+ } else {
268
+ idIndexes.set(normalizedId, [i]);
269
+ }
270
+ }
271
+
272
+ const duplicateIds: Array<{ id: string; indexes: number[] }> = [];
273
+ for (const [normalizedId, indexes] of idIndexes.entries()) {
274
+ if (indexes.length > 1) {
275
+ duplicateIds.push({
276
+ id: tasks[indexes[0]]?.id ?? normalizedId,
277
+ indexes,
278
+ });
279
+ }
280
+ }
281
+
282
+ if (missingTaskIndexes.length === 0 && duplicateIds.length === 0) {
283
+ return undefined;
284
+ }
285
+
286
+ const problems: string[] = [];
287
+ if (missingTaskIndexes.length > 0) {
288
+ problems.push(`Missing task ids at indexes: ${missingTaskIndexes.join(", ")}`);
289
+ }
290
+ if (duplicateIds.length > 0) {
291
+ const details = duplicateIds.map(entry => `${entry.id} (indexes ${entry.indexes.join(", ")})`).join("; ");
292
+ problems.push(`Duplicate task ids detected (case-insensitive): ${details}`);
293
+ }
294
+ return `Invalid tasks: ${problems.join(". ")}`;
295
+ }
296
+
297
+ /**
298
+ * Process-level memo for create-time agent discovery, keyed by resolved cwd.
299
+ *
300
+ * `TaskTool.create` runs for every (sub)agent session in this process and the
301
+ * walk-up + plugin-registry scan in `discoverAgents` is identical for a given
302
+ * cwd, so repeat creations reuse the first scan. Execution-time discovery
303
+ * (`#executeSync`) intentionally stays fresh. The memo also tracks the live
304
+ * `discoverAgents` binding: test spies swap that binding, which invalidates
305
+ * the memo automatically.
306
+ */
307
+ const discoveryMemo = new Map<string, Promise<DiscoveryResult>>();
308
+ let discoveryMemoFn: typeof discoverAgents | undefined;
309
+
310
+ function discoverAgentsForCreate(cwd: string): Promise<DiscoveryResult> {
311
+ const fn = discoverAgents;
312
+ if (discoveryMemoFn !== fn) {
313
+ discoveryMemoFn = fn;
314
+ discoveryMemo.clear();
315
+ }
316
+ const key = path.resolve(cwd);
317
+ let pending = discoveryMemo.get(key);
318
+ if (!pending) {
319
+ pending = fn(cwd);
320
+ discoveryMemo.set(key, pending);
321
+ pending.catch(() => {
322
+ if (discoveryMemo.get(key) === pending) discoveryMemo.delete(key);
323
+ });
324
+ }
325
+ return pending;
326
+ }
327
+
245
328
  // ═══════════════════════════════════════════════════════════════════════════
246
329
  // Tool Class
247
330
  // ═══════════════════════════════════════════════════════════════════════════
@@ -325,12 +408,12 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
325
408
  * Create a TaskTool instance with async agent discovery.
326
409
  */
327
410
  static async create(session: ToolSession): Promise<TaskTool> {
328
- const { agents } = await discoverAgents(session.cwd);
411
+ const { agents } = await discoverAgentsForCreate(session.cwd);
329
412
  return new TaskTool(session, agents);
330
413
  }
331
414
 
332
415
  async execute(
333
- _toolCallId: string,
416
+ toolCallId: string,
334
417
  rawParams: unknown,
335
418
  signal?: AbortSignal,
336
419
  onUpdate?: AgentToolUpdateCallback<TaskToolDetails>,
@@ -345,7 +428,7 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
345
428
  const asyncEnabled = this.session.settings.get("async.enabled");
346
429
  const selectedAgent = this.#discoveredAgents.find(agent => agent.name === params.agent);
347
430
  if (!asyncEnabled || selectedAgent?.blocking === true) {
348
- return this.#executeSync(_toolCallId, params, signal, onUpdate);
431
+ return this.#executeSync(toolCallId, params, signal, onUpdate);
349
432
  }
350
433
 
351
434
  const manager = this.session.asyncJobManager;
@@ -355,12 +438,17 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
355
438
  // to the sync path keeps the tool usable; only background/job-poll
356
439
  // semantics are lost.
357
440
  logger.warn("task: async.enabled but no AsyncJobManager registered; falling back to sync execution");
358
- return this.#executeSync(_toolCallId, params, signal, onUpdate);
441
+ return this.#executeSync(toolCallId, params, signal, onUpdate);
359
442
  }
360
443
 
361
444
  const taskItems = params.tasks ?? [];
362
445
  if (taskItems.length === 0) {
363
- return this.#executeSync(_toolCallId, params, signal, onUpdate);
446
+ return this.#executeSync(toolCallId, params, signal, onUpdate);
447
+ }
448
+
449
+ const taskIdProblem = validateTaskIds(taskItems);
450
+ if (taskIdProblem) {
451
+ return createTaskModeError(taskIdProblem);
364
452
  }
365
453
 
366
454
  const outputManager =
@@ -396,9 +484,13 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
396
484
  let failedJobs = 0;
397
485
 
398
486
  const getProgressSnapshot = (): AgentProgress[] => {
487
+ // Shallow copies: top-level fields are reassigned (never mutated in
488
+ // place) and the large nested payloads (extractedToolData) are
489
+ // immutable once attached — structuredClone here cost O(batch × payload)
490
+ // per progress event.
399
491
  return Array.from(progressByTaskId.values())
400
492
  .sort((a, b) => a.index - b.index)
401
- .map(progress => structuredClone(progress));
493
+ .map(progress => ({ ...progress }));
402
494
  };
403
495
 
404
496
  const buildAsyncDetails = (state: "running" | "completed" | "failed", jobId: string): TaskToolDetails => ({
@@ -424,6 +516,7 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
424
516
  const taskItem = taskItems[i];
425
517
  if (signal?.aborted) {
426
518
  failedSchedules.push(`${taskItem.id}: cancelled before scheduling`);
519
+ completedJobs += 1;
427
520
  const progress = progressByTaskId.get(taskItem.id);
428
521
  if (progress) {
429
522
  progress.status = "aborted";
@@ -438,7 +531,7 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
438
531
  const jobId = manager.register(
439
532
  "task",
440
533
  label,
441
- async ({ signal: runSignal, reportProgress }) => {
534
+ async ({ signal: runSignal, reportProgress, markRunning }) => {
442
535
  const startedAt = Date.now();
443
536
  const progress = progressByTaskId.get(taskItem.id);
444
537
  await semaphore.acquire();
@@ -447,8 +540,11 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
447
540
  if (progress) {
448
541
  progress.status = "aborted";
449
542
  }
543
+ completedJobs += 1;
544
+ failedJobs += 1;
450
545
  throw new Error("Aborted before execution");
451
546
  }
547
+ markRunning();
452
548
  if (progress) {
453
549
  progress.status = "running";
454
550
  }
@@ -457,17 +553,15 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
457
553
  buildAsyncDetails("running", startedJobs[0]?.jobId ?? label) as unknown as Record<string, unknown>,
458
554
  );
459
555
  try {
460
- const result = await this.#executeSync(_toolCallId, singleParams, runSignal, undefined, [
461
- uniqueId,
462
- ]);
556
+ const result = await this.#executeSync(toolCallId, singleParams, runSignal, undefined, [uniqueId]);
463
557
  const finalText = result.content.find(part => part.type === "text")?.text ?? "(no output)";
464
558
  const singleResult = result.details?.results[0];
559
+ // A missing per-task result means #executeSync failed at the
560
+ // tool level (results: []) — treat it as a failure, not success.
561
+ const resultFailed =
562
+ !singleResult || (singleResult.aborted ?? false) || singleResult.exitCode !== 0;
465
563
  if (progress) {
466
- progress.status = singleResult?.aborted
467
- ? "aborted"
468
- : (singleResult?.exitCode ?? 0) === 0
469
- ? "completed"
470
- : "failed";
564
+ progress.status = singleResult?.aborted ? "aborted" : resultFailed ? "failed" : "completed";
471
565
  progress.durationMs = singleResult?.durationMs ?? Math.max(0, Date.now() - startedAt);
472
566
  progress.tokens = singleResult?.tokens ?? 0;
473
567
  progress.contextTokens = singleResult?.contextTokens;
@@ -478,7 +572,7 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
478
572
  progress.retryState = undefined;
479
573
  }
480
574
  completedJobs += 1;
481
- if (singleResult && ((singleResult.aborted ?? false) || singleResult.exitCode !== 0)) {
575
+ if (resultFailed) {
482
576
  failedJobs += 1;
483
577
  }
484
578
  const remaining = taskItems.length - completedJobs;
@@ -498,8 +592,15 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
498
592
  `Background task batch complete: ${completedJobs}/${taskItems.length} finished.`,
499
593
  );
500
594
  }
595
+ if (resultFailed) {
596
+ // Mark the job itself failed; counters above are already updated.
597
+ throw new TaskJobError(finalText);
598
+ }
501
599
  return finalText;
502
600
  } catch (error) {
601
+ if (error instanceof TaskJobError) {
602
+ throw error;
603
+ }
503
604
  if (progress) {
504
605
  progress.status = "failed";
505
606
  progress.durationMs = Math.max(0, Date.now() - startedAt);
@@ -530,6 +631,7 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
530
631
  },
531
632
  {
532
633
  id: label,
634
+ queued: true,
533
635
  ownerId: this.session.getAgentId?.() ?? undefined,
534
636
  onProgress: (text, details) => {
535
637
  const progressDetails =
@@ -543,6 +645,7 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
543
645
  } catch (error) {
544
646
  const message = error instanceof Error ? error.message : String(error);
545
647
  failedSchedules.push(`${taskItem.id}: ${message}`);
648
+ completedJobs += 1;
546
649
  const progress = progressByTaskId.get(taskItem.id);
547
650
  if (progress) {
548
651
  progress.status = "failed";
@@ -603,7 +706,7 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
603
706
  }
604
707
 
605
708
  async #executeSync(
606
- _toolCallId: string,
709
+ toolCallId: string,
607
710
  params: TaskParams,
608
711
  signal?: AbortSignal,
609
712
  onUpdate?: AgentToolUpdateCallback<TaskToolDetails>,
@@ -734,45 +837,10 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
734
837
  }
735
838
 
736
839
  const tasks = params.tasks;
737
- const missingTaskIndexes: number[] = [];
738
- const idIndexes = new Map<string, number[]>();
739
-
740
- for (let i = 0; i < tasks.length; i++) {
741
- const id = tasks[i]?.id;
742
- if (typeof id !== "string" || id.trim() === "") {
743
- missingTaskIndexes.push(i);
744
- continue;
745
- }
746
- const normalizedId = id.toLowerCase();
747
- const indexes = idIndexes.get(normalizedId);
748
- if (indexes) {
749
- indexes.push(i);
750
- } else {
751
- idIndexes.set(normalizedId, [i]);
752
- }
753
- }
754
-
755
- const duplicateIds: Array<{ id: string; indexes: number[] }> = [];
756
- for (const [normalizedId, indexes] of idIndexes.entries()) {
757
- if (indexes.length > 1) {
758
- duplicateIds.push({
759
- id: tasks[indexes[0]]?.id ?? normalizedId,
760
- indexes,
761
- });
762
- }
763
- }
764
-
765
- if (missingTaskIndexes.length > 0 || duplicateIds.length > 0) {
766
- const problems: string[] = [];
767
- if (missingTaskIndexes.length > 0) {
768
- problems.push(`Missing task ids at indexes: ${missingTaskIndexes.join(", ")}`);
769
- }
770
- if (duplicateIds.length > 0) {
771
- const details = duplicateIds.map(entry => `${entry.id} (indexes ${entry.indexes.join(", ")})`).join("; ");
772
- problems.push(`Duplicate task ids detected (case-insensitive): ${details}`);
773
- }
840
+ const taskIdProblem = validateTaskIds(tasks);
841
+ if (taskIdProblem) {
774
842
  return {
775
- content: [{ type: "text", text: `Invalid tasks: ${problems.join(". ")}` }],
843
+ content: [{ type: "text", text: taskIdProblem }],
776
844
  details: {
777
845
  projectAgentsDir,
778
846
  results: [],
@@ -951,7 +1019,11 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
951
1019
  }
952
1020
  emitProgress();
953
1021
 
954
- const runTask = async (task: (typeof tasksWithUniqueIds)[number], index: number) => {
1022
+ const runTask = async (
1023
+ task: (typeof tasksWithUniqueIds)[number],
1024
+ index: number,
1025
+ workerSignal?: AbortSignal,
1026
+ ) => {
955
1027
  if (!isIsolated) {
956
1028
  return runSubprocess({
957
1029
  cwd: this.session.cwd,
@@ -962,6 +1034,7 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
962
1034
  planReference,
963
1035
  description: task.description,
964
1036
  index,
1037
+ parentToolCallId: toolCallId,
965
1038
  id: task.id,
966
1039
  taskDepth,
967
1040
  modelOverride,
@@ -973,12 +1046,13 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
973
1046
  artifactsDir: effectiveArtifactsDir,
974
1047
  contextFile: contextFilePath,
975
1048
  enableLsp: subagentLspEnabled,
976
- signal,
1049
+ signal: workerSignal ?? signal,
977
1050
  eventBus: this.session.eventBus,
978
1051
  onProgress: progress => {
979
- progressMap.set(index, {
980
- ...structuredClone(progress),
981
- });
1052
+ // Shallow snapshot; recentTools is mutated in place by the
1053
+ // executor, the rest is reassigned or immutable. A deep clone
1054
+ // here cost O(extractedToolData) per progress event.
1055
+ progressMap.set(index, { ...progress, recentTools: progress.recentTools.slice() });
982
1056
  emitProgress();
983
1057
  },
984
1058
  authStorage: this.session.authStorage,
@@ -1023,6 +1097,7 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
1023
1097
  planReference,
1024
1098
  description: task.description,
1025
1099
  index,
1100
+ parentToolCallId: toolCallId,
1026
1101
  id: task.id,
1027
1102
  taskDepth,
1028
1103
  modelOverride,
@@ -1034,12 +1109,10 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
1034
1109
  artifactsDir: effectiveArtifactsDir,
1035
1110
  contextFile: contextFilePath,
1036
1111
  enableLsp: subagentLspEnabled,
1037
- signal,
1112
+ signal: workerSignal ?? signal,
1038
1113
  eventBus: this.session.eventBus,
1039
1114
  onProgress: progress => {
1040
- progressMap.set(index, {
1041
- ...structuredClone(progress),
1042
- });
1115
+ progressMap.set(index, { ...progress, recentTools: progress.recentTools.slice() });
1043
1116
  emitProgress();
1044
1117
  },
1045
1118
  authStorage: this.session.authStorage,
@@ -1226,6 +1299,9 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
1226
1299
  const conflictPart = mergeResult.conflict ? `\nConflict: ${mergeResult.conflict}` : "";
1227
1300
  mergeSummary = `\n\n<system-notification>Branch merge failed. ${mergedPart}${failedPart}${conflictPart}\nUnmerged branches remain for manual resolution.</system-notification>`;
1228
1301
  }
1302
+ if (mergeResult.stashConflict) {
1303
+ mergeSummary += `\n\n<system-notification>${mergeResult.stashConflict}</system-notification>`;
1304
+ }
1229
1305
  }
1230
1306
 
1231
1307
  // Clean up merged branches (keep failed ones for manual resolution)
@@ -1234,9 +1310,11 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
1234
1310
  await cleanupTaskBranches(repoRoot, allBranches);
1235
1311
  }
1236
1312
  } else {
1237
- // Patch mode: combine and apply patches
1238
- const patchesInOrder = results.map(result => result.patchPath).filter(Boolean) as string[];
1239
- const missingPatch = results.some(result => !result.patchPath);
1313
+ // Patch mode: apply patches from successful tasks. Failed or
1314
+ // aborted siblings must not block completed work from landing.
1315
+ const successfulResults = results.filter(r => r.exitCode === 0 && !r.error && !r.aborted);
1316
+ const patchesInOrder = successfulResults.map(result => result.patchPath).filter(Boolean) as string[];
1317
+ const missingPatch = successfulResults.some(result => !result.patchPath);
1240
1318
  if (missingPatch) {
1241
1319
  changesApplied = false;
1242
1320
  hadAnyChanges = false;
@@ -20,13 +20,13 @@ export interface ParallelResult<R> {
20
20
  *
21
21
  * @param items - Items to process
22
22
  * @param concurrency - Maximum concurrent operations
23
- * @param fn - Async function to execute for each item
23
+ * @param fn - Async function to execute for each item; receives a worker signal that fires on abort or fail-fast so in-flight siblings can cancel
24
24
  * @param signal - Optional abort signal to stop scheduling new work
25
25
  */
26
26
  export async function mapWithConcurrencyLimit<T, R>(
27
27
  items: T[],
28
28
  concurrency: number,
29
- fn: (item: T, index: number) => Promise<R>,
29
+ fn: (item: T, index: number, signal: AbortSignal) => Promise<R>,
30
30
  signal?: AbortSignal,
31
31
  ): Promise<ParallelResult<R>> {
32
32
  const normalizedConcurrency = Number.isFinite(concurrency) ? Math.floor(concurrency) : items.length;
@@ -52,7 +52,7 @@ export async function mapWithConcurrencyLimit<T, R>(
52
52
  const index = nextIndex++;
53
53
  if (index >= items.length) return;
54
54
  try {
55
- results[index] = await fn(items[index], index);
55
+ results[index] = await fn(items[index], index, workerSignal);
56
56
  } catch (error) {
57
57
  // On abort, the fn itself handles it and returns a result
58
58
  // Only propagate non-abort errors
@@ -541,7 +541,7 @@ function renderTaskItemLines(tasks: TaskItem[] | undefined, expanded: boolean, t
541
541
  * the merged result frame so the brief stays visible for the whole task
542
542
  * lifecycle — not just until the first progress snapshot replaces the call view.
543
543
  */
544
- type TaskRenderSection = { lines: string[] };
544
+ type TaskRenderSection = { lines: readonly string[] };
545
545
  type ContextSectionRenderer = (width: number) => TaskRenderSection;
546
546
 
547
547
  // Default output-block layout is: left border + one-cell content inset + right
@@ -578,7 +578,7 @@ export function renderCall(
578
578
  const header = renderStatusLine({ icon: "pending", title: "Task", description: args.agent }, theme);
579
579
  const contextSectionRenderer = createContextSectionRenderer(args, theme);
580
580
  return framedBlock(theme, width => {
581
- const sections: Array<{ label?: string; lines: string[]; separator?: boolean }> = [];
581
+ const sections: Array<{ label?: string; lines: readonly string[]; separator?: boolean }> = [];
582
582
 
583
583
  if (contextSectionRenderer) sections.push(contextSectionRenderer(width));
584
584
 
@@ -1072,6 +1072,20 @@ function renderAgentResult(
1072
1072
  return lines;
1073
1073
  }
1074
1074
 
1075
+ /**
1076
+ * Order live progress entries so finished agents render first and unfinished
1077
+ * (pending/running) ones stay pinned at the bottom as tasks complete. Stable
1078
+ * within each group, so agents keep their dispatch order.
1079
+ */
1080
+ function orderProgressForDisplay(progress: readonly AgentProgress[]): AgentProgress[] {
1081
+ const finished: AgentProgress[] = [];
1082
+ const unfinished: AgentProgress[] = [];
1083
+ for (const p of progress) {
1084
+ (p.status === "pending" || p.status === "running" ? unfinished : finished).push(p);
1085
+ }
1086
+ return finished.concat(unfinished);
1087
+ }
1088
+
1075
1089
  /**
1076
1090
  * Render the tool result.
1077
1091
  */
@@ -1140,7 +1154,7 @@ export function renderResult(
1140
1154
  const shouldRenderProgress =
1141
1155
  Boolean(details.progress && details.progress.length > 0) && (isPartial || details.results.length === 0);
1142
1156
  if (shouldRenderProgress && details.progress) {
1143
- details.progress.forEach(progress => {
1157
+ orderProgressForDisplay(details.progress).forEach(progress => {
1144
1158
  lines.push(...renderAgentProgress(progress, "", " ", expanded, theme, spinnerFrame));
1145
1159
  });
1146
1160
  } else if (details.results && details.results.length > 0) {
@@ -1269,8 +1283,9 @@ function renderNestedTaskTree(
1269
1283
  }
1270
1284
  const inflight = details.progress;
1271
1285
  if (inflight && inflight.length > 0) {
1272
- inflight.forEach((prog, index) => {
1273
- const { prefix, continuePrefix } = nestedMarkers(index === inflight.length - 1, theme);
1286
+ const ordered = orderProgressForDisplay(inflight);
1287
+ ordered.forEach((prog, index) => {
1288
+ const { prefix, continuePrefix } = nestedMarkers(index === ordered.length - 1, theme);
1274
1289
  lines.push(...renderAgentProgress(prog, prefix, continuePrefix, expanded, theme, spinnerFrame));
1275
1290
  });
1276
1291
  }