@oh-my-pi/pi-coding-agent 17.0.8 → 17.1.0

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 (340) hide show
  1. package/CHANGELOG.md +87 -1
  2. package/README.md +1 -1
  3. package/dist/cli.js +5495 -5021
  4. package/dist/types/async/job-manager.d.ts +37 -1
  5. package/dist/types/cli/args.d.ts +2 -0
  6. package/dist/types/cli/bench-cli.d.ts +41 -0
  7. package/dist/types/commands/bench.d.ts +15 -3
  8. package/dist/types/commands/launch.d.ts +4 -0
  9. package/dist/types/config/__tests__/model-registry.test.d.ts +1 -0
  10. package/dist/types/config/model-registry.d.ts +30 -1
  11. package/dist/types/config/models-config-schema.d.ts +18 -6
  12. package/dist/types/config/models-config.d.ts +14 -5
  13. package/dist/types/config/provider-globals.d.ts +2 -2
  14. package/dist/types/config/service-tier.d.ts +5 -1
  15. package/dist/types/config/settings-schema.d.ts +350 -50
  16. package/dist/types/config/settings.d.ts +7 -0
  17. package/dist/types/dap/client.d.ts +20 -0
  18. package/dist/types/exec/bash-executor.d.ts +38 -0
  19. package/dist/types/exec/direnv.d.ts +33 -0
  20. package/dist/types/export/html/args.d.ts +15 -0
  21. package/dist/types/export/html/index.d.ts +11 -26
  22. package/dist/types/export/html/web-palette.d.ts +55 -116
  23. package/dist/types/extensibility/extensions/loader.d.ts +3 -0
  24. package/dist/types/extensibility/extensions/types.d.ts +17 -2
  25. package/dist/types/extensibility/legacy-pi-coding-agent-shim.d.ts +1 -0
  26. package/dist/types/extensibility/legacy-pi-tui-shim.d.ts +10 -0
  27. package/dist/types/extensibility/plugins/legacy-pi-compat.d.ts +14 -1
  28. package/dist/types/internal-urls/registry-helpers.d.ts +11 -0
  29. package/dist/types/live/controller.d.ts +40 -0
  30. package/dist/types/live/protocol.d.ts +85 -0
  31. package/dist/types/live/protocol.test.d.ts +1 -0
  32. package/dist/types/live/transport.d.ts +48 -0
  33. package/dist/types/live/visualizer.d.ts +33 -0
  34. package/dist/types/lsp/client.d.ts +18 -1
  35. package/dist/types/mcp/config-writer.test.d.ts +1 -0
  36. package/dist/types/mcp/manager.d.ts +6 -2
  37. package/dist/types/mcp/oauth-discovery.d.ts +2 -0
  38. package/dist/types/mcp/render.d.ts +2 -2
  39. package/dist/types/mcp/smithery-auth.d.ts +1 -1
  40. package/dist/types/mcp/smithery-auth.test.d.ts +1 -0
  41. package/dist/types/mcp/tool-bridge.d.ts +7 -3
  42. package/dist/types/mcp/types.d.ts +6 -0
  43. package/dist/types/memory-backend/tool-names.d.ts +2 -0
  44. package/dist/types/modes/components/login-dialog.d.ts +2 -2
  45. package/dist/types/modes/components/oauth-selector.d.ts +8 -0
  46. package/dist/types/modes/components/plan-review-overlay.d.ts +26 -0
  47. package/dist/types/modes/components/settings-defs.d.ts +8 -1
  48. package/dist/types/modes/components/status-line/component.jj-cache.test.d.ts +1 -0
  49. package/dist/types/modes/components/tool-execution.d.ts +9 -1
  50. package/dist/types/modes/controllers/command-controller.d.ts +1 -1
  51. package/dist/types/modes/controllers/event-controller.d.ts +6 -1
  52. package/dist/types/modes/controllers/live-command-controller.d.ts +14 -0
  53. package/dist/types/modes/controllers/mcp-command-controller.d.ts +3 -0
  54. package/dist/types/modes/interactive-mode.d.ts +5 -0
  55. package/dist/types/modes/rpc/rpc-client.d.ts +10 -1
  56. package/dist/types/modes/rpc/rpc-frame.d.ts +16 -0
  57. package/dist/types/modes/rpc/rpc-messages.d.ts +26 -0
  58. package/dist/types/modes/rpc/rpc-types.d.ts +40 -0
  59. package/dist/types/modes/setup-wizard/scenes/sign-in.d.ts +1 -1
  60. package/dist/types/modes/setup-wizard/scenes/types.d.ts +9 -1
  61. package/dist/types/modes/setup-wizard/scenes/web-search.d.ts +1 -1
  62. package/dist/types/modes/theme/theme.d.ts +1 -1
  63. package/dist/types/modes/types.d.ts +3 -1
  64. package/dist/types/modes/utils/context-usage.d.ts +12 -2
  65. package/dist/types/plan-mode/plan-files.d.ts +10 -0
  66. package/dist/types/registry/agent-lifecycle.d.ts +21 -9
  67. package/dist/types/registry/agent-registry.d.ts +11 -4
  68. package/dist/types/sdk.d.ts +15 -1
  69. package/dist/types/secrets/index.d.ts +15 -2
  70. package/dist/types/secrets/obfuscator.d.ts +113 -15
  71. package/dist/types/session/acp-permission-gate.d.ts +19 -0
  72. package/dist/types/session/agent-session-events.d.ts +84 -0
  73. package/dist/types/session/agent-session-types.d.ts +314 -0
  74. package/dist/types/session/agent-session.d.ts +120 -750
  75. package/dist/types/session/async-job-delivery.d.ts +28 -0
  76. package/dist/types/session/bash-runner.d.ts +75 -0
  77. package/dist/types/session/blob-store.d.ts +11 -1
  78. package/dist/types/session/blob-store.test.d.ts +1 -0
  79. package/dist/types/session/checkpoint-entries.d.ts +28 -0
  80. package/dist/types/session/eval-runner.d.ts +49 -0
  81. package/dist/types/session/irc-bridge.d.ts +46 -0
  82. package/dist/types/session/messages.d.ts +22 -0
  83. package/dist/types/session/model-controls.d.ts +180 -0
  84. package/dist/types/session/prewalk.d.ts +46 -0
  85. package/dist/types/session/queued-messages.d.ts +22 -0
  86. package/dist/types/session/redis-session-storage.d.ts +1 -0
  87. package/dist/types/session/retry-fallback-chains.d.ts +74 -0
  88. package/dist/types/session/role-models.d.ts +15 -0
  89. package/dist/types/session/session-advisors.d.ts +228 -0
  90. package/dist/types/session/session-entries.d.ts +8 -0
  91. package/dist/types/session/session-handoff.d.ts +70 -0
  92. package/dist/types/session/session-history-format.d.ts +6 -1
  93. package/dist/types/session/session-loader.d.ts +6 -4
  94. package/dist/types/session/session-maintenance.d.ts +246 -0
  95. package/dist/types/session/session-manager.d.ts +41 -0
  96. package/dist/types/session/session-memory.d.ts +56 -0
  97. package/dist/types/session/session-provider-boundary.d.ts +62 -0
  98. package/dist/types/session/session-stats.d.ts +48 -0
  99. package/dist/types/session/session-tools.d.ts +143 -0
  100. package/dist/types/session/session-workspace.d.ts +32 -0
  101. package/dist/types/session/stream-guards.d.ts +44 -0
  102. package/dist/types/session/todo-tracker.d.ts +56 -0
  103. package/dist/types/session/ttsr-coordinator.d.ts +51 -0
  104. package/dist/types/session/turn-recovery.d.ts +225 -0
  105. package/dist/types/system-prompt.d.ts +2 -0
  106. package/dist/types/task/executor.d.ts +4 -1
  107. package/dist/types/task/output-manager.d.ts +3 -1
  108. package/dist/types/task/types.d.ts +14 -0
  109. package/dist/types/tools/approval.d.ts +1 -0
  110. package/dist/types/tools/browser/launch.d.ts +5 -0
  111. package/dist/types/tools/hub/jobs.d.ts +1 -1
  112. package/dist/types/tools/image-gen.d.ts +4 -4
  113. package/dist/types/tools/image-providers.d.ts +38 -0
  114. package/dist/types/tools/index.d.ts +8 -0
  115. package/dist/types/tools/path-utils.d.ts +8 -0
  116. package/dist/types/tools/report-tool-issue.d.ts +24 -9
  117. package/dist/types/tools/todo.d.ts +10 -4
  118. package/dist/types/tools/xdev.d.ts +5 -1
  119. package/dist/types/tts/streaming-player.d.ts +14 -0
  120. package/dist/types/tts/vocalizer.d.ts +5 -0
  121. package/dist/types/utils/jj.d.ts +29 -0
  122. package/dist/types/utils/lang-from-path.d.ts +1 -0
  123. package/dist/types/utils/title-generator.d.ts +30 -0
  124. package/dist/types/vibe/runtime.d.ts +36 -8
  125. package/dist/types/web/search/index.d.ts +1 -1
  126. package/dist/types/web/search/provider.d.ts +15 -5
  127. package/dist/types/web/search/providers/firecrawl.d.ts +9 -0
  128. package/dist/types/web/search/types.d.ts +95 -1
  129. package/package.json +13 -12
  130. package/scripts/generate-share-viewer.ts +4 -6
  131. package/scripts/legacy-pi-virtual-module.ts +1 -1
  132. package/src/advisor/__tests__/advisor.test.ts +643 -0
  133. package/src/advisor/runtime.ts +144 -43
  134. package/src/async/job-manager.ts +90 -2
  135. package/src/cli/args.ts +2 -0
  136. package/src/cli/auth-gateway-cli.ts +18 -3
  137. package/src/cli/bench-cli.ts +395 -29
  138. package/src/cli/flag-tables.ts +3 -0
  139. package/src/cli/gc-cli.ts +2 -2
  140. package/src/cli/grievances-cli.ts +2 -2
  141. package/src/cli/usage-cli.ts +11 -2
  142. package/src/collab/host.ts +3 -2
  143. package/src/commands/bench.ts +18 -4
  144. package/src/commands/launch.ts +4 -0
  145. package/src/config/__tests__/model-registry.test.ts +147 -0
  146. package/src/config/model-registry.ts +50 -2
  147. package/src/config/model-resolver.ts +0 -1
  148. package/src/config/models-config-schema.ts +15 -4
  149. package/src/config/provider-globals.ts +9 -9
  150. package/src/config/service-tier.ts +26 -1
  151. package/src/config/settings-schema.ts +250 -54
  152. package/src/config/settings.ts +81 -2
  153. package/src/dap/client.ts +37 -4
  154. package/src/edit/index.ts +49 -12
  155. package/src/eval/py/executor.ts +103 -33
  156. package/src/exec/bash-executor.ts +89 -6
  157. package/src/exec/direnv.ts +145 -0
  158. package/src/export/html/args.ts +20 -0
  159. package/src/export/html/index.ts +45 -47
  160. package/src/export/html/template.css +19 -1
  161. package/src/export/html/template.html +6 -0
  162. package/src/export/html/template.js +21 -0
  163. package/src/export/html/tool-views.generated.js +31 -31
  164. package/src/export/html/web-palette.ts +116 -132
  165. package/src/export/share.ts +249 -49
  166. package/src/extensibility/extensions/loader.ts +29 -1
  167. package/src/extensibility/extensions/runner.ts +6 -0
  168. package/src/extensibility/extensions/types.ts +33 -2
  169. package/src/extensibility/legacy-pi-coding-agent-shim.ts +1 -0
  170. package/src/extensibility/legacy-pi-tui-shim.ts +10 -0
  171. package/src/extensibility/plugins/legacy-pi-compat.ts +870 -311
  172. package/src/hindsight/state.ts +64 -6
  173. package/src/internal-urls/local-protocol.ts +2 -1
  174. package/src/internal-urls/memory-protocol.ts +2 -2
  175. package/src/internal-urls/registry-helpers.ts +40 -0
  176. package/src/internal-urls/skill-protocol.ts +2 -2
  177. package/src/internal-urls/ssh-protocol.ts +2 -1
  178. package/src/internal-urls/vault-protocol.ts +2 -1
  179. package/src/live/audio-worklet.txt +59 -0
  180. package/src/live/browser-runtime.txt +221 -0
  181. package/src/live/controller.ts +464 -0
  182. package/src/live/prompts/agent-final-message.md +3 -0
  183. package/src/live/prompts/live-instructions.md +23 -0
  184. package/src/live/protocol.test.ts +140 -0
  185. package/src/live/protocol.ts +233 -0
  186. package/src/live/transport.ts +490 -0
  187. package/src/live/visualizer.ts +234 -0
  188. package/src/lsp/client.ts +52 -9
  189. package/src/lsp/clients/biome-client.ts +3 -4
  190. package/src/lsp/index.ts +44 -11
  191. package/src/main.ts +9 -1
  192. package/src/mcp/config-writer.test.ts +43 -0
  193. package/src/mcp/config-writer.ts +109 -82
  194. package/src/mcp/manager.ts +39 -8
  195. package/src/mcp/oauth-discovery.ts +10 -2
  196. package/src/mcp/render.ts +94 -35
  197. package/src/mcp/smithery-auth.test.ts +29 -0
  198. package/src/mcp/smithery-auth.ts +3 -2
  199. package/src/mcp/tool-bridge.ts +107 -11
  200. package/src/mcp/types.ts +7 -0
  201. package/src/memories/index.ts +40 -20
  202. package/src/memory-backend/tool-names.ts +2 -0
  203. package/src/mnemopi/backend.ts +24 -7
  204. package/src/modes/acp/acp-agent.ts +56 -10
  205. package/src/modes/acp/acp-event-mapper.ts +8 -1
  206. package/src/modes/components/agent-hub.ts +27 -10
  207. package/src/modes/components/login-dialog.ts +14 -4
  208. package/src/modes/components/oauth-selector.ts +24 -7
  209. package/src/modes/components/plan-review-overlay.ts +350 -35
  210. package/src/modes/components/settings-defs.ts +28 -3
  211. package/src/modes/components/settings-selector.ts +178 -6
  212. package/src/modes/components/status-line/component.jj-cache.test.ts +229 -0
  213. package/src/modes/components/status-line/component.ts +123 -2
  214. package/src/modes/components/tool-execution.test.ts +63 -2
  215. package/src/modes/components/tool-execution.ts +11 -2
  216. package/src/modes/controllers/command-controller.ts +70 -22
  217. package/src/modes/controllers/event-controller.ts +111 -16
  218. package/src/modes/controllers/extension-ui-controller.test.ts +1 -0
  219. package/src/modes/controllers/extension-ui-controller.ts +20 -4
  220. package/src/modes/controllers/live-command-controller.ts +178 -0
  221. package/src/modes/controllers/mcp-command-controller.ts +80 -41
  222. package/src/modes/controllers/omfg-controller.ts +44 -40
  223. package/src/modes/controllers/selector-controller.ts +18 -10
  224. package/src/modes/controllers/session-focus-controller.ts +6 -1
  225. package/src/modes/interactive-mode.ts +160 -38
  226. package/src/modes/rpc/rpc-client.ts +94 -3
  227. package/src/modes/rpc/rpc-frame.ts +164 -4
  228. package/src/modes/rpc/rpc-messages.ts +127 -0
  229. package/src/modes/rpc/rpc-mode.ts +79 -7
  230. package/src/modes/rpc/rpc-types.ts +34 -2
  231. package/src/modes/runtime-init.ts +2 -0
  232. package/src/modes/setup-wizard/scenes/model.ts +5 -7
  233. package/src/modes/setup-wizard/scenes/providers.ts +3 -2
  234. package/src/modes/setup-wizard/scenes/sign-in.ts +8 -2
  235. package/src/modes/setup-wizard/scenes/theme.ts +13 -3
  236. package/src/modes/setup-wizard/scenes/types.ts +9 -1
  237. package/src/modes/setup-wizard/scenes/web-search.ts +23 -10
  238. package/src/modes/setup-wizard/wizard-overlay.ts +1 -1
  239. package/src/modes/theme/theme.ts +1 -1
  240. package/src/modes/types.ts +3 -1
  241. package/src/modes/utils/context-usage.ts +23 -7
  242. package/src/modes/utils/ui-helpers.ts +10 -5
  243. package/src/plan-mode/plan-files.ts +40 -0
  244. package/src/prompts/bench/cache-prefix-chunk.md +1 -0
  245. package/src/prompts/bench/cache-prefix.md +3 -0
  246. package/src/prompts/bench/cache-suffix.md +1 -0
  247. package/src/prompts/goals/guided-goal-system.md +24 -3
  248. package/src/prompts/system/custom-system-prompt.md +1 -1
  249. package/src/prompts/system/project-prompt.md +8 -1
  250. package/src/prompts/system/subagent-async-pending.md +6 -0
  251. package/src/prompts/system/system-prompt.md +1 -1
  252. package/src/prompts/system/workflow-notice.md +48 -40
  253. package/src/prompts/system/xdev-mount-notice.md +4 -0
  254. package/src/prompts/tools/task.md +12 -2
  255. package/src/prompts/tools/todo.md +3 -1
  256. package/src/registry/agent-lifecycle.ts +116 -46
  257. package/src/registry/agent-registry.ts +35 -8
  258. package/src/registry/persisted-agents.ts +26 -2
  259. package/src/sdk.ts +255 -139
  260. package/src/secrets/index.ts +127 -4
  261. package/src/secrets/obfuscator.ts +2328 -105
  262. package/src/session/acp-permission-gate.ts +165 -0
  263. package/src/session/agent-session-error-log.test.ts +1 -1
  264. package/src/session/agent-session-events.ts +66 -0
  265. package/src/session/agent-session-types.ts +334 -0
  266. package/src/session/agent-session.ts +1910 -12213
  267. package/src/session/artifacts.ts +6 -5
  268. package/src/session/async-job-delivery.ts +74 -0
  269. package/src/session/bash-runner.ts +326 -0
  270. package/src/session/blob-store.test.ts +56 -0
  271. package/src/session/blob-store.ts +18 -2
  272. package/src/session/checkpoint-entries.ts +81 -0
  273. package/src/session/eval-runner.ts +212 -0
  274. package/src/session/indexed-session-storage.ts +9 -2
  275. package/src/session/irc-bridge.ts +203 -0
  276. package/src/session/messages.ts +263 -1
  277. package/src/session/model-controls.ts +714 -0
  278. package/src/session/prewalk.ts +252 -0
  279. package/src/session/queued-messages.ts +93 -0
  280. package/src/session/redis-session-storage.ts +51 -11
  281. package/src/session/retry-fallback-chains.ts +455 -0
  282. package/src/session/role-models.ts +85 -0
  283. package/src/session/session-advisors.ts +1679 -0
  284. package/src/session/session-context.test.ts +224 -1
  285. package/src/session/session-context.ts +47 -5
  286. package/src/session/session-entries.ts +8 -0
  287. package/src/session/session-handoff.ts +308 -0
  288. package/src/session/session-history-format.ts +20 -9
  289. package/src/session/session-loader.ts +10 -51
  290. package/src/session/session-maintenance.ts +2983 -0
  291. package/src/session/session-manager.ts +390 -30
  292. package/src/session/session-memory.ts +222 -0
  293. package/src/session/session-provider-boundary.ts +307 -0
  294. package/src/session/session-stats.ts +293 -0
  295. package/src/session/session-tools.ts +943 -0
  296. package/src/session/session-workspace.ts +53 -0
  297. package/src/session/stream-guards.ts +417 -0
  298. package/src/session/todo-tracker.ts +380 -0
  299. package/src/session/ttsr-coordinator.ts +496 -0
  300. package/src/session/turn-recovery.ts +1629 -0
  301. package/src/slash-commands/builtin-registry.ts +92 -11
  302. package/src/slash-commands/helpers/usage-report.ts +25 -4
  303. package/src/system-prompt.ts +41 -17
  304. package/src/task/executor.ts +288 -70
  305. package/src/task/index.ts +148 -111
  306. package/src/task/output-manager.ts +25 -3
  307. package/src/task/persisted-revive.ts +4 -3
  308. package/src/task/structured-subagent.ts +4 -1
  309. package/src/task/types.ts +16 -0
  310. package/src/tools/approval.ts +55 -10
  311. package/src/tools/bash-interactive.ts +26 -0
  312. package/src/tools/bash-skill-urls.ts +28 -2
  313. package/src/tools/bash.ts +339 -123
  314. package/src/tools/browser/launch.ts +9 -1
  315. package/src/tools/browser/tab-supervisor.ts +8 -5
  316. package/src/tools/browser.ts +94 -47
  317. package/src/tools/hub/index.ts +1 -1
  318. package/src/tools/hub/jobs.ts +67 -8
  319. package/src/tools/image-gen.ts +20 -28
  320. package/src/tools/image-providers.ts +50 -0
  321. package/src/tools/index.ts +12 -0
  322. package/src/tools/path-utils.ts +49 -2
  323. package/src/tools/read.ts +156 -88
  324. package/src/tools/renderers.ts +7 -1
  325. package/src/tools/report-tool-issue.ts +79 -28
  326. package/src/tools/todo.ts +101 -11
  327. package/src/tools/vibe.ts +1 -2
  328. package/src/tools/write.ts +51 -1
  329. package/src/tools/xdev.ts +67 -3
  330. package/src/tts/streaming-player.ts +62 -4
  331. package/src/tts/vocalizer.ts +18 -1
  332. package/src/utils/jj.ts +125 -4
  333. package/src/utils/lang-from-path.ts +7 -0
  334. package/src/utils/title-generator.ts +137 -1
  335. package/src/vibe/runtime.ts +852 -72
  336. package/src/web/search/index.ts +3 -6
  337. package/src/web/search/provider.ts +31 -16
  338. package/src/web/search/providers/firecrawl.ts +46 -13
  339. package/src/web/search/providers/xai.ts +7 -1
  340. package/src/web/search/types.ts +8 -1
package/src/task/index.ts CHANGED
@@ -41,6 +41,7 @@ import {
41
41
  // Import review tools for side effects (registers subagent tool handlers)
42
42
  import "../tools/review";
43
43
  import type { AsyncJobManager } from "../async";
44
+ import { hasResolvableTranscript } from "../internal-urls/registry-helpers";
44
45
  import { AgentRegistry } from "../registry/agent-registry";
45
46
  import { type DiscoveryResult, discoverAgents } from "./discovery";
46
47
  import { generateTaskName } from "./name-generator";
@@ -160,6 +161,7 @@ export function formatResultOutputFallback(result: Pick<SingleResult, "output" |
160
161
  function renderDescription(
161
162
  agents: AgentDefinition[],
162
163
  isolationEnabled: boolean,
164
+ applyIsolatedChanges: boolean,
163
165
  disabledAgents: string[],
164
166
  batchEnabled: boolean,
165
167
  asyncEnabled: boolean,
@@ -185,8 +187,8 @@ function renderDescription(
185
187
  agents: renderedAgents,
186
188
  spawningDisabled,
187
189
  defaultAgent: spawnPolicy.defaultAgent,
188
- allowedAgentsText: spawnPolicy.allowedPromptText,
189
190
  isolationEnabled,
191
+ applyIsolatedChanges,
190
192
  batchEnabled,
191
193
  asyncEnabled,
192
194
  hasBlockingAgents: renderedAgents.some(agent => agent.blocking),
@@ -229,6 +231,19 @@ function validateShapeParams(batchEnabled: boolean, params: TaskParams): string
229
231
  * policy later, in `spawnParamsFor`. Returns a problem description, or
230
232
  * undefined when valid.
231
233
  */
234
+ function hasInvalidModelSelector(model: unknown): boolean {
235
+ if (model === undefined) return false;
236
+ const selectors = typeof model === "string" ? [model] : Array.isArray(model) ? model : undefined;
237
+ const materializedSelectors = selectors ? Array.from(selectors) : [];
238
+ return (
239
+ !selectors ||
240
+ materializedSelectors.length === 0 ||
241
+ materializedSelectors.some(
242
+ selector => typeof selector !== "string" || !selector.split(",").some(pattern => pattern.trim()),
243
+ )
244
+ );
245
+ }
246
+
232
247
  function validateSpawnParams(params: TaskParams, batchEnabled: boolean): string | undefined {
233
248
  const hasTask = typeof params.task === "string" && params.task.trim() !== "";
234
249
  const tasks = params.tasks;
@@ -244,6 +259,9 @@ function validateSpawnParams(params: TaskParams, batchEnabled: boolean): string
244
259
  if (!item || typeof item.task !== "string" || item.task.trim() === "") {
245
260
  return `Task ${i + 1}${item?.name ? ` (\`${item.name}\`)` : ""} is missing \`task\`. Every task needs complete, self-contained instructions.`;
246
261
  }
262
+ if (hasInvalidModelSelector(item.model)) {
263
+ return `Task ${i + 1}${item.name ? ` (\`${item.name}\`)` : ""} has an invalid \`model\`. Provide a non-empty selector or a non-empty array of non-empty selectors.`;
264
+ }
247
265
  }
248
266
  const seen = new Map<string, string>();
249
267
  for (const item of tasks) {
@@ -266,6 +284,9 @@ function validateSpawnParams(params: TaskParams, batchEnabled: boolean): string
266
284
  ? "Missing `tasks`. Provide a `tasks` array (one subagent per item) with a shared `context`."
267
285
  : "Missing `task`. Provide complete, self-contained instructions for the agent.";
268
286
  }
287
+ if (hasInvalidModelSelector(params.model)) {
288
+ return "Invalid `model`. Provide a non-empty selector or a non-empty array of non-empty selectors.";
289
+ }
269
290
  return undefined;
270
291
  }
271
292
 
@@ -279,7 +300,7 @@ function resolveSpawnItems(params: TaskParams): TaskItem[] {
279
300
  if (Array.isArray(params.tasks) && params.tasks.length > 0) {
280
301
  return params.tasks;
281
302
  }
282
- const item: TaskItem = { name: params.name, agent: params.agent, task: params.task };
303
+ const item: TaskItem = { name: params.name, agent: params.agent, task: params.task, model: params.model };
283
304
  if ("outputSchema" in params) item.outputSchema = params.outputSchema;
284
305
  if ("schemaMode" in params) item.schemaMode = params.schemaMode;
285
306
  if ("isolated" in params) item.isolated = params.isolated;
@@ -299,6 +320,7 @@ function spawnParamsFor(params: TaskParams, item: TaskItem, defaultAgent: string
299
320
  const spawn: TaskParams = { agent: item.agent?.trim() || defaultAgent };
300
321
  if (item.name !== undefined) spawn.name = item.name;
301
322
  if (item.task !== undefined) spawn.task = item.task;
323
+ if (item.model !== undefined) spawn.model = item.model;
302
324
  if (params.context !== undefined) spawn.context = params.context;
303
325
  if ("outputSchema" in item) spawn.outputSchema = item.outputSchema;
304
326
  if ("schemaMode" in item) spawn.schemaMode = item.schemaMode;
@@ -469,6 +491,14 @@ function discoverAgentsForCreate(cwd: string): Promise<DiscoveryResult> {
469
491
  return pending;
470
492
  }
471
493
 
494
+ function formatModelForApproval(model: unknown): string | undefined {
495
+ const selectors = typeof model === "string" ? [model] : Array.isArray(model) ? model : [];
496
+ const normalized = selectors.filter(
497
+ (selector): selector is string => typeof selector === "string" && !!selector.trim(),
498
+ );
499
+ return normalized.length > 0 ? truncateForPrompt(normalized.join(" → ")) : undefined;
500
+ }
501
+
472
502
  // ═══════════════════════════════════════════════════════════════════════════
473
503
  // Tool Class
474
504
  // ═══════════════════════════════════════════════════════════════════════════
@@ -492,23 +522,43 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
492
522
  if (typeof params.name === "string" && params.name.trim()) {
493
523
  lines.push(`Name: ${truncateForPrompt(params.name)}`);
494
524
  }
525
+ const model = formatModelForApproval(params.model);
526
+ if (model) lines.push(`Model: ${model}`);
495
527
  if (typeof params.task === "string") {
496
528
  lines.push(`Task:\n${truncateForPrompt(params.task)}`);
497
529
  }
498
530
  if (typeof params.context === "string" && params.context.trim()) {
499
531
  lines.push(`Context:\n${truncateForPrompt(params.context)}`);
500
532
  }
501
- const tasks = Array.isArray(params.tasks) ? params.tasks : [];
502
- const firstTask = tasks[0];
503
- if (firstTask) {
504
- if (typeof firstTask.name === "string" && firstTask.name.trim()) {
505
- lines.push(`Name: ${truncateForPrompt(firstTask.name)}`);
506
- }
507
- if (typeof firstTask.agent === "string" && firstTask.agent.trim()) {
508
- lines.push(`Agent: ${truncateForPrompt(firstTask.agent)}`);
533
+ const tasks: unknown[] = Array.isArray(params.tasks) ? params.tasks : [];
534
+ if (tasks.length > 0) {
535
+ const defaultAgent = resolveSpawnPolicy(this.session.getSessionSpawns()).defaultAgent;
536
+ const effectiveAgent = (item: unknown): string => {
537
+ if (item && typeof item === "object" && "agent" in item) {
538
+ const agent = item.agent;
539
+ if (typeof agent === "string" && agent.trim()) return agent.trim();
540
+ }
541
+ return defaultAgent;
542
+ };
543
+ const agentCounts = new Map<string, number>();
544
+ for (const item of tasks) {
545
+ const agent = effectiveAgent(item);
546
+ agentCounts.set(agent, (agentCounts.get(agent) ?? 0) + 1);
509
547
  }
510
- if (typeof firstTask.task === "string") {
511
- lines.push(`Task:\n${truncateForPrompt(firstTask.task)}`);
548
+ const agentSummary = [...agentCounts].map(([agent, count]) => `${agent} ×${count}`).join(", ");
549
+ lines.push(`Batch agents: ${truncateForPrompt(agentSummary)}`);
550
+
551
+ const firstTask = tasks[0];
552
+ if (firstTask && typeof firstTask === "object") {
553
+ if ("name" in firstTask && typeof firstTask.name === "string" && firstTask.name.trim()) {
554
+ lines.push(`Name: ${truncateForPrompt(firstTask.name)}`);
555
+ }
556
+ lines.push(`Agent: ${truncateForPrompt(effectiveAgent(firstTask))}`);
557
+ const itemModel = formatModelForApproval("model" in firstTask ? firstTask.model : undefined);
558
+ if (itemModel) lines.push(`Model: ${itemModel}`);
559
+ if ("task" in firstTask && typeof firstTask.task === "string") {
560
+ lines.push(`Task:\n${truncateForPrompt(firstTask.task)}`);
561
+ }
512
562
  }
513
563
  if (tasks.length > 1) {
514
564
  lines.push(`+${tasks.length - 1} more task${tasks.length === 2 ? "" : "s"}`);
@@ -566,6 +616,7 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
566
616
  return renderDescription(
567
617
  this.#discoveredAgents,
568
618
  !planMode && isolationMode !== "none",
619
+ this.session.settings.get("task.isolation.apply"),
569
620
  disabledAgents,
570
621
  this.#isBatchEnabled(),
571
622
  this.session.settings.get("async.enabled"),
@@ -612,6 +663,7 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
612
663
  assignment: (params.task ?? "").trim(),
613
664
  context: this.#isBatchEnabled() ? params.context?.trim() || undefined : undefined,
614
665
  agent: params.agent,
666
+ model: params.model,
615
667
  ...(Object.hasOwn(params, "outputSchema") ? { outputSchema: params.outputSchema } : {}),
616
668
  ...(Object.hasOwn(params, "schemaMode") ? { schemaMode: params.schemaMode } : {}),
617
669
  ...("isolated" in params ? { isolation: { requested: params.isolated } } : {}),
@@ -650,27 +702,53 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
650
702
  const spawnItems = resolveSpawnItems(params);
651
703
  const normalizedSpawnParams = spawnItems.map(item => spawnParamsFor(params, item, defaultAgent));
652
704
  const resolvedAgents = normalizedSpawnParams.map(spawn => spawn.agent ?? defaultAgent);
705
+ // Resolve every item before choosing an execution path. No executor or
706
+ // job manager may observe a batch unless every effective policy is valid.
707
+ const preflights = await Promise.all(
708
+ normalizedSpawnParams.map(async spawn => {
709
+ try {
710
+ return { policy: await this.#resolveSpawnPreflight(spawn) };
711
+ } catch (error) {
712
+ return { error: error instanceof StructuredSubagentError ? error.message : String(error) };
713
+ }
714
+ }),
715
+ );
716
+ const preflightFailures = preflights
717
+ .map((preflight, index) => ("error" in preflight ? { index, error: preflight.error } : undefined))
718
+ .filter((failure): failure is { index: number; error: string } => failure !== undefined);
719
+ if (preflightFailures.length > 0) {
720
+ if (!batchEnabled) {
721
+ return createTaskModeError(`Task execution failed: ${preflightFailures[0]!.error}`);
722
+ }
723
+ return createTaskModeError(
724
+ preflightFailures
725
+ .map(({ index, error }) => {
726
+ const item = spawnItems[index]!;
727
+ return `Task ${item.name?.trim() || `#${index + 1}`} failed preflight: ${error}`;
728
+ })
729
+ .join("\n"),
730
+ );
731
+ }
732
+ const policies = preflights.map(preflight => preflight.policy!);
733
+ const itemBlocking = policies.map(policy => policy.effectiveAgent.blocking === true);
734
+
653
735
  // Execution mode is per item: an item whose agent type declares
654
736
  // `blocking: true` runs inline on this turn (the parent waits on its
655
737
  // result); every other item becomes a background job when async
656
738
  // execution is available.
657
- const provisionalBlocking = resolvedAgents.map(
658
- name => this.#discoveredAgents.find(agent => agent.name === name)?.blocking === true,
659
- );
660
739
  const asyncEnabled = this.session.settings.get("async.enabled");
661
740
  const manager = asyncEnabled ? this.session.asyncJobManager : undefined;
662
- const provisionalAsyncItems = manager ? spawnItems.filter((_, index) => !provisionalBlocking[index]) : [];
741
+ const asyncItems = manager ? spawnItems.filter((_, index) => !itemBlocking[index]) : [];
663
742
  const depthCapacity = canSpawnAtDepth(
664
743
  this.session.settings.get("task.maxRecursionDepth") ?? 2,
665
744
  this.session.taskDepth ?? 0,
666
745
  );
667
746
  const ircEnabled = isIrcEnabled(this.session.settings, this.session.taskDepth ?? 0);
668
747
 
669
- if (!manager || provisionalAsyncItems.length === 0) {
748
+ if (!manager || asyncItems.length === 0) {
670
749
  // Sync fallback: async execution disabled, orphaned host that never
671
750
  // wired a job manager, or every item's agent type declares
672
- // `blocking: true`. `runStructuredSubagent` performs its own shared
673
- // preflight before reserving an id in these inline paths.
751
+ // `blocking: true`.
674
752
  if (asyncEnabled && !this.session.asyncJobManager) {
675
753
  logger.warn("task: no AsyncJobManager registered; falling back to sync execution");
676
754
  }
@@ -678,7 +756,7 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
678
756
  ? undefined
679
757
  : composeSpawnAdvisory({
680
758
  agents: resolvedAgents,
681
- items: provisionalAsyncItems,
759
+ items: asyncItems,
682
760
  depthCapacity,
683
761
  ircEnabled,
684
762
  willRunAsync: false,
@@ -704,43 +782,13 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
704
782
  return { ...result, content };
705
783
  }
706
784
 
707
- // Async jobs are otherwise registered before their body can reach
708
- // `runStructuredSubagent`. Resolve the shared policy first so policy
709
- // failures remain synchronous and cannot leave a queued invalid job.
710
- const preflights = await Promise.all(
711
- normalizedSpawnParams.map(async spawn => {
712
- try {
713
- return { policy: await this.#resolveSpawnPreflight(spawn) };
714
- } catch (error) {
715
- return { error: error instanceof StructuredSubagentError ? error.message : String(error) };
716
- }
717
- }),
718
- );
719
- const preflightFailures = preflights
720
- .map((preflight, index) => ("error" in preflight ? { index, error: preflight.error } : undefined))
721
- .filter((failure): failure is { index: number; error: string } => failure !== undefined);
722
- const renderPreflightFailures = () =>
723
- preflightFailures
724
- .map(({ index, error }) => {
725
- const item = spawnItems[index]!;
726
- return `Task ${item.name?.trim() || `#${index + 1}`} failed preflight: ${error}`;
727
- })
728
- .join("\n");
729
- if (preflightFailures.length === spawnItems.length) {
730
- return createTaskModeError(renderPreflightFailures());
731
- }
732
-
733
- const validIndices = preflights.flatMap((preflight, index) => (preflight.policy ? [index] : []));
734
- const validSpawns = validIndices.map(index => ({ item: spawnItems[index]!, index }));
735
- const itemBlocking = preflights.map(preflight => preflight.policy?.effectiveAgent.blocking === true);
736
- const asyncItems = validIndices.filter(index => !itemBlocking[index]).map(index => spawnItems[index]!);
737
785
  // Coordination only makes sense for spawns that keep running after this
738
786
  // call returns (the async subset). Blocking items have already completed
739
787
  // by then, so a "coordinate while they run" hint would misfire.
740
788
  const advisory = this.session.suppressSpawnAdvisory
741
789
  ? undefined
742
790
  : composeSpawnAdvisory({
743
- agents: validIndices.map(index => resolvedAgents[index]!),
791
+ agents: resolvedAgents,
744
792
  items: asyncItems,
745
793
  depthCapacity,
746
794
  ircEnabled,
@@ -762,24 +810,15 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
762
810
  if (!appended) content.push({ type: "text", text: advisory });
763
811
  return { ...result, content };
764
812
  };
765
- const withPreflightFailures = (result: AgentToolResult<TaskToolDetails>): AgentToolResult<TaskToolDetails> => {
766
- if (preflightFailures.length === 0) return result;
767
- const failures = renderPreflightFailures();
768
- let prepended = false;
769
- const content = result.content.map(part => {
770
- if (!prepended && part.type === "text" && typeof part.text === "string") {
771
- prepended = true;
772
- return { ...part, text: `${failures}\n\n${part.text}` };
773
- }
774
- return part;
775
- });
776
- if (!prepended) content.unshift({ type: "text", text: failures });
777
- return { ...result, content };
778
- };
779
813
  if (asyncItems.length === 0) {
780
- return withPreflightFailures(
781
- withAdvisory(
782
- await this.#executeSyncFanout(toolCallId, params, validSpawns, defaultAgent, signal, onUpdate),
814
+ return withAdvisory(
815
+ await this.#executeSyncFanout(
816
+ toolCallId,
817
+ params,
818
+ spawnItems.map((item, index) => ({ item, index })),
819
+ defaultAgent,
820
+ signal,
821
+ onUpdate,
783
822
  ),
784
823
  );
785
824
  }
@@ -799,12 +838,9 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
799
838
  blocking: boolean;
800
839
  progress: AgentProgress;
801
840
  }> = [];
802
- for (const index of validIndices) {
803
- const item = spawnItems[index]!;
841
+ for (const [index, item] of spawnItems.entries()) {
804
842
  const agentType = resolvedAgents[index]!;
805
- const preflight = preflights[index]!;
806
- const policy = preflight.policy;
807
- if (!policy) continue;
843
+ const policy = policies[index]!;
808
844
  const agentSource = policy.agent.source;
809
845
  const agentId = await outputManager.allocate(item.name?.trim() || generateTaskName());
810
846
  const assignment = (item.task ?? "").trim();
@@ -841,7 +877,7 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
841
877
  // failed. Blocking spawns run inline below and land in `results` before
842
878
  // the call returns, so post-return job updates never drop them.
843
879
  let settledCount = 0;
844
- let failedCount = preflightFailures.length;
880
+ let failedCount = 0;
845
881
  let primaryJobId = asyncSpawns[0].agentId;
846
882
  const syncResults: SingleResult[] = [];
847
883
  let syncUsage: Usage | undefined;
@@ -891,7 +927,7 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
891
927
  }
892
928
 
893
929
  if (started.length === 0 && syncSpawns.length === 0) {
894
- return withPreflightFailures({
930
+ return {
895
931
  content: [
896
932
  {
897
933
  type: "text",
@@ -899,7 +935,7 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
899
935
  },
900
936
  ],
901
937
  details: { projectAgentsDir: null, results: [], totalDurationMs: 0 },
902
- });
938
+ };
903
939
  }
904
940
 
905
941
  const scheduleFailureSummary =
@@ -924,34 +960,30 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
924
960
  content: [{ type: "text", text: `Spawned agent \`${agentId}\`...` }],
925
961
  details: buildAsyncDetails(),
926
962
  });
927
- return withPreflightFailures(
928
- withAdvisory({
929
- content: [
930
- {
931
- type: "text",
932
- text: `Spawned agent \`${agentId}\` (job \`${jobId}\`). Its result auto-delivers on yield unless a settled \`hub jobs\`/\`wait\` snapshot consumes it first. ${coordinationHint}`,
933
- },
934
- ],
935
- details: buildAsyncDetails(),
936
- }),
937
- );
938
- }
939
- const startedListing = started.map(({ agentId, jobId }) => `- \`${agentId}\` (job \`${jobId}\`)`).join("\n");
940
- onUpdate?.({
941
- content: [{ type: "text", text: `Spawned ${started.length} agents...` }],
942
- details: buildAsyncDetails(),
943
- });
944
- return withPreflightFailures(
945
- withAdvisory({
963
+ return withAdvisory({
946
964
  content: [
947
965
  {
948
966
  type: "text",
949
- text: `Spawned ${started.length} background agents using ${agentLabel}.${scheduleFailureSummary} Each result auto-delivers on yield unless a settled \`hub jobs\`/\`wait\` snapshot consumes it first.\n${startedListing}\n${coordinationHint}`,
967
+ text: `Spawned agent \`${agentId}\` (job \`${jobId}\`). Its result auto-delivers on yield unless a settled \`hub jobs\`/\`wait\` snapshot consumes it first. ${coordinationHint}`,
950
968
  },
951
969
  ],
952
970
  details: buildAsyncDetails(),
953
- }),
954
- );
971
+ });
972
+ }
973
+ const startedListing = started.map(({ agentId, jobId }) => `- \`${agentId}\` (job \`${jobId}\`)`).join("\n");
974
+ onUpdate?.({
975
+ content: [{ type: "text", text: `Spawned ${started.length} agents...` }],
976
+ details: buildAsyncDetails(),
977
+ });
978
+ return withAdvisory({
979
+ content: [
980
+ {
981
+ type: "text",
982
+ text: `Spawned ${started.length} background agents using ${agentLabel}.${scheduleFailureSummary} Each result auto-delivers on yield unless a settled \`hub jobs\`/\`wait\` snapshot consumes it first.\n${startedListing}\n${coordinationHint}`,
983
+ },
984
+ ],
985
+ details: buildAsyncDetails(),
986
+ });
955
987
  }
956
988
 
957
989
  // Mixed call: the async jobs above already run detached; the blocking
@@ -1016,12 +1048,10 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
1016
1048
  const text = [merged.contentParts.join("\n\n"), spawnedSummary]
1017
1049
  .filter(section => section.trim().length > 0)
1018
1050
  .join("\n\n");
1019
- return withPreflightFailures(
1020
- withAdvisory({
1021
- content: [{ type: "text", text: text.length > 0 ? text : "No results." }],
1022
- details: buildAsyncDetails(),
1023
- }),
1024
- );
1051
+ return withAdvisory({
1052
+ content: [{ type: "text", text: text.length > 0 ? text : "No results." }],
1053
+ details: buildAsyncDetails(),
1054
+ });
1025
1055
  }
1026
1056
 
1027
1057
  /**
@@ -1043,14 +1073,17 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
1043
1073
  }): string {
1044
1074
  const { manager, toolCallId, spawnParams, agentId, progress, ircEnabled, buildDetails, onUpdate, onSettled } =
1045
1075
  options;
1046
- const buildFollowUpHint = (aborted: boolean): string => {
1076
+ const buildFollowUpHint = async (aborted: boolean): Promise<string> => {
1047
1077
  if (aborted) {
1048
- const status = AgentRegistry.global().get(agentId)?.status;
1049
- if (status === "idle" || status === "parked") {
1078
+ const ref = AgentRegistry.global().get(agentId);
1079
+ const transcript = (await hasResolvableTranscript(agentId))
1080
+ ? `transcript at history://${agentId}`
1081
+ : "transcript unavailable";
1082
+ if (ref?.status === "idle" || ref?.status === "parked") {
1050
1083
  const followUp = ircEnabled ? "message it via `hub` to resume; " : "";
1051
- return `\n\n${agentId} was stopped but is still resumable — ${followUp}transcript at history://${agentId}`;
1084
+ return `\n\n${agentId} was stopped but is still resumable — ${followUp}${transcript}`;
1052
1085
  }
1053
- return `\n\n${agentId} was aborted — transcript at history://${agentId}`;
1086
+ return `\n\n${agentId} was aborted — ${transcript}`;
1054
1087
  }
1055
1088
  const followUp = ircEnabled ? "message it via `hub` to follow up; " : "";
1056
1089
  return `\n\n${agentId} is now idle — ${followUp}transcript at history://${agentId}`;
@@ -1105,6 +1138,7 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
1105
1138
  // and running counters without reverting the "running"
1106
1139
  // status back to the subagent's initial "pending" snapshot.
1107
1140
  progress.resolvedModel = nextProgress.resolvedModel;
1141
+ progress.resolvedModelIsFallback = nextProgress.resolvedModelIsFallback;
1108
1142
  progress.tokens = nextProgress.tokens;
1109
1143
  progress.requests = nextProgress.requests;
1110
1144
  progress.contextTokens = nextProgress.contextTokens;
@@ -1149,15 +1183,17 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
1149
1183
  progress.retryState = undefined;
1150
1184
  if (singleResult?.resolvedModel) {
1151
1185
  progress.resolvedModel = singleResult.resolvedModel;
1186
+ progress.resolvedModelIsFallback = singleResult.resolvedModelIsFallback;
1152
1187
  } else {
1153
1188
  delete progress.resolvedModel;
1189
+ delete progress.resolvedModelIsFallback;
1154
1190
  }
1155
1191
  onSettled?.(resultFailed);
1156
1192
  const statusText = resultFailed
1157
1193
  ? `Background task ${agentId} failed.`
1158
1194
  : `Background task ${agentId} complete.`;
1159
1195
  await reportProgress(statusText, buildDetails() as unknown as Record<string, unknown>);
1160
- const deliveryText = `${finalText}${buildFollowUpHint(singleResult?.aborted === true)}`;
1196
+ const deliveryText = `${finalText}${await buildFollowUpHint(singleResult?.aborted === true)}`;
1161
1197
  if (resultFailed) {
1162
1198
  // Mark the job itself failed; the failed agent stays interrogable.
1163
1199
  throw new TaskJobError(deliveryText);
@@ -1173,7 +1209,7 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
1173
1209
  const statusText = `Background task ${agentId} failed.`;
1174
1210
  await reportProgress(statusText, buildDetails() as unknown as Record<string, unknown>);
1175
1211
  const message = error instanceof Error ? error.message : String(error);
1176
- const hint = AgentRegistry.global().get(agentId) ? buildFollowUpHint(false) : "";
1212
+ const hint = AgentRegistry.global().get(agentId) ? await buildFollowUpHint(false) : "";
1177
1213
  throw new TaskJobError(`${message}${hint}`);
1178
1214
  } finally {
1179
1215
  releasePermit();
@@ -1383,6 +1419,7 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
1383
1419
  assignment,
1384
1420
  context,
1385
1421
  agent: params.agent,
1422
+ model: params.model,
1386
1423
  ...(Object.hasOwn(params, "outputSchema") ? { outputSchema: params.outputSchema } : {}),
1387
1424
  ...(Object.hasOwn(params, "schemaMode") ? { schemaMode: params.schemaMode } : {}),
1388
1425
  identity: { id: preAllocatedId, label: params.name },
@@ -18,10 +18,11 @@ import { ADVISOR_TRANSCRIPT_STEM } from "../advisor/transcript-recorder";
18
18
  *
19
19
  * The first allocation of a given name keeps the name as-is; subsequent
20
20
  * allocations of the same name get a `-2`, `-3`, … suffix. On resume, scans
21
- * existing output files so previously written outputs are never overwritten.
21
+ * existing output and child-session files so prior state is never overwritten.
22
22
  */
23
23
  export class AgentOutputManager {
24
24
  #initialized = false;
25
+ #initializing: Promise<void> | undefined;
25
26
  /** Final ids already handed out, relative to this manager's scope. */
26
27
  readonly #taken = new Set<string>();
27
28
  readonly #getArtifactsDir: () => string | null;
@@ -42,8 +43,12 @@ export class AgentOutputManager {
42
43
  */
43
44
  async #ensureInitialized(): Promise<void> {
44
45
  if (this.#initialized) return;
46
+ this.#initializing ??= this.#seedFromDisk();
47
+ await this.#initializing;
45
48
  this.#initialized = true;
49
+ }
46
50
 
51
+ async #seedFromDisk(): Promise<void> {
47
52
  const dir = this.#getArtifactsDir();
48
53
  if (!dir) return;
49
54
 
@@ -56,8 +61,9 @@ export class AgentOutputManager {
56
61
 
57
62
  const prefix = this.#parentPrefix ? `${this.#parentPrefix}.` : "";
58
63
  for (const file of files) {
59
- if (!file.endsWith(".md")) continue;
60
- let rest = file.slice(0, -3); // drop ".md"
64
+ const extension = file.endsWith(".jsonl") ? ".jsonl" : file.endsWith(".md") ? ".md" : undefined;
65
+ if (!extension) continue;
66
+ let rest = file.slice(0, -extension.length);
61
67
  if (prefix) {
62
68
  if (!rest.startsWith(prefix)) continue;
63
69
  rest = rest.slice(prefix.length);
@@ -80,6 +86,22 @@ export class AgentOutputManager {
80
86
  return this.#parentPrefix ? `${this.#parentPrefix}.${candidate}` : candidate;
81
87
  }
82
88
 
89
+ /** Reserve final IDs discovered outside the output directory scan. */
90
+ async reserve(ids: Iterable<string>): Promise<void> {
91
+ await this.#ensureInitialized();
92
+ const prefix = this.#parentPrefix ? `${this.#parentPrefix}.` : "";
93
+ for (const id of ids) {
94
+ let rest = id;
95
+ if (prefix) {
96
+ if (!rest.startsWith(prefix)) continue;
97
+ rest = rest.slice(prefix.length);
98
+ }
99
+ const dot = rest.indexOf(".");
100
+ const segment = dot === -1 ? rest : rest.slice(0, dot);
101
+ if (segment) this.#taken.add(segment);
102
+ }
103
+ }
104
+
83
105
  /**
84
106
  * Allocate a unique ID.
85
107
  *
@@ -71,7 +71,7 @@ export function createPersistedSubagentReviverFactory(
71
71
  taskDepth++;
72
72
  parentId = registry.get(parentId)?.parentId;
73
73
  }
74
- return async () => {
74
+ return async expectedRef => {
75
75
  // Re-open fresh on every revive: park closes the writer, so this takes
76
76
  // the single-writer lock cleanly and restores the full message history.
77
77
  const reopened = await SessionManager.open(sessionFile, undefined, undefined, {
@@ -97,6 +97,7 @@ export function createPersistedSubagentReviverFactory(
97
97
  agentDisplayName: ref.displayName,
98
98
  parentTaskPrefix: ref.id,
99
99
  parentAgentId: ref.parentId,
100
+ expectedAgentRef: expectedRef,
100
101
  taskDepth,
101
102
  toolNames: init.tools,
102
103
  outputSchema: init.outputSchema,
@@ -131,8 +132,8 @@ export function createPersistedSubagentReviverFactory(
131
132
  // Without it the idle-TTL timer never clears on a turn and the lifecycle
132
133
  // could park the agent mid-run.
133
134
  session.subscribe(event => {
134
- if (event.type === "agent_start") registry.setStatus(ref.id, "running");
135
- else if (event.type === "agent_end") registry.setStatus(ref.id, "idle");
135
+ if (event.type === "agent_start") registry.setStatus(ref.id, "running", session);
136
+ else if (event.type === "agent_end") registry.setStatus(ref.id, "idle", session);
136
137
  });
137
138
  return session;
138
139
  };
@@ -300,7 +300,9 @@ export async function resolveEffectiveSubagentPolicy(
300
300
  planMode,
301
301
  isIsolated,
302
302
  mergeMode: request.isolation?.merge ?? request.session.settings.get("task.isolation.merge"),
303
- applyChanges: request.isolation?.apply !== false,
303
+ applyChanges:
304
+ request.isolation?.apply ??
305
+ (request.invocationKind === "task" ? request.session.settings.get("task.isolation.apply") : true),
304
306
  enableLsp:
305
307
  !planMode &&
306
308
  (request.enableLsp ?? ((request.session.enableLsp ?? true) && request.session.settings.get("task.enableLsp"))),
@@ -371,6 +373,7 @@ function buildExecutorOptions(
371
373
  const enableMCP = !policy.planMode && (session.enableMCP ?? true);
372
374
  return {
373
375
  cwd: session.cwd,
376
+ additionalDirectories: session.additionalDirectories,
374
377
  agent: policy.effectiveAgent,
375
378
  task: renderSubagentPrompt(request.assignment),
376
379
  assignment: request.assignment.trim(),