@gajae-code/coding-agent 0.14.2 → 0.15.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 (345) hide show
  1. package/CHANGELOG.md +120 -1
  2. package/dist/types/async/job-manager.d.ts +4 -1
  3. package/dist/types/autoresearch/dashboard.d.ts +29 -4
  4. package/dist/types/autoresearch/data-context.d.ts +25 -0
  5. package/dist/types/autoresearch/git.d.ts +65 -7
  6. package/dist/types/autoresearch/harness.d.ts +58 -0
  7. package/dist/types/autoresearch/prompts.d.ts +51 -0
  8. package/dist/types/autoresearch/runs.d.ts +173 -0
  9. package/dist/types/autoresearch/types.d.ts +9 -139
  10. package/dist/types/capability/types.d.ts +20 -0
  11. package/dist/types/cli/accounts-cli.d.ts +1 -0
  12. package/dist/types/commands/{rlm.d.ts → autoresearch.d.ts} +2 -4
  13. package/dist/types/config/autorouting-contract.d.ts +146 -0
  14. package/dist/types/config/autorouting-generator.d.ts +40 -0
  15. package/dist/types/config/autorouting-tier-map.d.ts +16844 -0
  16. package/dist/types/config/autorouting.d.ts +35 -0
  17. package/dist/types/config/model-discovery-manager.d.ts +8 -1
  18. package/dist/types/config/model-registry.d.ts +30 -1
  19. package/dist/types/config/provider-selection-policy.d.ts +23 -0
  20. package/dist/types/config/settings-schema.d.ts +61 -3
  21. package/dist/types/config/settings.d.ts +3 -0
  22. package/dist/types/coordinator/contract.d.ts +1 -1
  23. package/dist/types/coordinator-mcp/durability.d.ts +29 -0
  24. package/dist/types/coordinator-mcp/event-webhook.d.ts +50 -0
  25. package/dist/types/coordinator-mcp/question-state.d.ts +1 -1
  26. package/dist/types/coordinator-mcp/server.d.ts +14 -0
  27. package/dist/types/crash/index-store.d.ts +3 -0
  28. package/dist/types/crash/record-loader.d.ts +2 -0
  29. package/dist/types/crash/upstream/relay.d.ts +5 -3
  30. package/dist/types/cursor.d.ts +12 -0
  31. package/dist/types/defaults/gjc-defaults.d.ts +35 -1
  32. package/dist/types/defaults/gjc-skills.generated.d.ts +1 -1
  33. package/dist/types/discovery/helpers.d.ts +1 -1
  34. package/dist/types/edit/index.d.ts +2 -1
  35. package/dist/types/eval/py/executor.d.ts +2 -0
  36. package/dist/types/extensibility/extensions/loader.d.ts +32 -0
  37. package/dist/types/extensibility/gjc-plugins/types.d.ts +1 -1
  38. package/dist/types/extensibility/skills.d.ts +2 -0
  39. package/dist/types/gjc-runtime/autoresearch-runtime.d.ts +194 -0
  40. package/dist/types/gjc-runtime/gc-runtime.d.ts +1 -1
  41. package/dist/types/gjc-runtime/psmux-detect.d.ts +1 -1
  42. package/dist/types/gjc-runtime/python-transcript.d.ts +19 -0
  43. package/dist/types/gjc-runtime/ralplan-runtime.d.ts +2 -8
  44. package/dist/types/gjc-runtime/session-layout.d.ts +6 -3
  45. package/dist/types/gjc-runtime/state-renderer.d.ts +0 -22
  46. package/dist/types/gjc-runtime/state-schema.d.ts +7 -7
  47. package/dist/types/gjc-runtime/tmux-common.d.ts +2 -2
  48. package/dist/types/gjc-runtime/ultragoal-change-set.d.ts +38 -1
  49. package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +2 -24
  50. package/dist/types/gjc-runtime/ultragoal-validation-policy.d.ts +73 -0
  51. package/dist/types/gjc-runtime/workflow-recovery-projection.d.ts +100 -0
  52. package/dist/types/hooks/skill-keywords.d.ts +1 -1
  53. package/dist/types/main.d.ts +0 -10
  54. package/dist/types/modes/acp/acp-agent.d.ts +13 -6
  55. package/dist/types/modes/components/model-selector.d.ts +14 -0
  56. package/dist/types/modes/components/skill-hud/render.d.ts +6 -1
  57. package/dist/types/modes/components/smart-routing-panel.d.ts +82 -0
  58. package/dist/types/modes/controllers/selector-controller.d.ts +14 -0
  59. package/dist/types/modes/interactive-mode.d.ts +3 -0
  60. package/dist/types/modes/types.d.ts +9 -0
  61. package/dist/types/rlm/artifacts.d.ts +4 -4
  62. package/dist/types/runtime/optional-runtime-services.d.ts +2 -1
  63. package/dist/types/runtime/workspace-tree-service.d.ts +5 -1
  64. package/dist/types/runtime-mcp/config.d.ts +6 -0
  65. package/dist/types/runtime-mcp/manager.d.ts +10 -0
  66. package/dist/types/sdk/acp/adapter.d.ts +0 -4
  67. package/dist/types/sdk/broker/broker.d.ts +1 -1
  68. package/dist/types/sdk/broker/lifecycle.d.ts +57 -2
  69. package/dist/types/sdk/broker/session-index.d.ts +15 -0
  70. package/dist/types/sdk/bus/chat-daemon-control.d.ts +6 -0
  71. package/dist/types/sdk/bus/chat-daemon-runtime.d.ts +4 -0
  72. package/dist/types/sdk/bus/index.d.ts +8 -0
  73. package/dist/types/sdk/bus/kind-aware-reconciliation.d.ts +2 -0
  74. package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +12 -2
  75. package/dist/types/sdk/client/client.d.ts +39 -4
  76. package/dist/types/sdk/host/host.d.ts +1 -0
  77. package/dist/types/sdk/host/internal-autorouting-state.d.ts +3 -0
  78. package/dist/types/sdk/host/model-pin.d.ts +29 -0
  79. package/dist/types/sdk/host/session-runtime.d.ts +2 -1
  80. package/dist/types/sdk/lifecycle-session.d.ts +13 -0
  81. package/dist/types/sdk/router/session-router.d.ts +27 -1
  82. package/dist/types/sdk/session.d.ts +11 -1
  83. package/dist/types/session/agent-session.d.ts +36 -6
  84. package/dist/types/session/artifacts.d.ts +16 -1
  85. package/dist/types/session/internal/managed-session-storage.d.ts +7 -0
  86. package/dist/types/session/session-manager.d.ts +84 -2
  87. package/dist/types/session/session-staging-paths.d.ts +3 -0
  88. package/dist/types/skill-state/canonical-skills.d.ts +1 -1
  89. package/dist/types/skill-state/workflow-hud.d.ts +12 -21
  90. package/dist/types/skill-state/workflow-mutation-guard.d.ts +1 -0
  91. package/dist/types/slash-commands/helpers/autorouting-status.d.ts +8 -0
  92. package/dist/types/system-prompt.d.ts +23 -0
  93. package/dist/types/task/executor.d.ts +35 -4
  94. package/dist/types/task/index.d.ts +17 -3
  95. package/dist/types/task/receipt.d.ts +3 -1
  96. package/dist/types/task/types.d.ts +144 -0
  97. package/dist/types/tools/atomic-file-write.d.ts +72 -0
  98. package/dist/types/tools/descriptors.d.ts +1 -0
  99. package/dist/types/tools/index.d.ts +36 -1
  100. package/dist/types/tools/move-session.d.ts +40 -0
  101. package/dist/types/tools/python.d.ts +12 -0
  102. package/dist/types/tools/resource-gc.d.ts +0 -3
  103. package/dist/types/tools/search.d.ts +8 -1
  104. package/dist/types/tools/skill.d.ts +9 -4
  105. package/dist/types/tools/subagent.d.ts +33 -3
  106. package/dist/types/utils/xml-escape.d.ts +2 -0
  107. package/dist/types/workflow/workflow-intent-diff.d.ts +2 -2
  108. package/dist/types/workflow/workflow-intent-report-consumer.d.ts +1 -1
  109. package/package.json +11 -12
  110. package/scripts/build-sdk-package-smoke.ts +7 -5
  111. package/scripts/check-autorouting-tier-map.ts +183 -0
  112. package/scripts/generate-sdk-operation-inventory.ts +12 -0
  113. package/scripts/generate-tool-catalog.ts +40 -5
  114. package/src/async/job-manager.ts +5 -2
  115. package/src/autoresearch/dashboard.ts +134 -314
  116. package/src/autoresearch/data-context.ts +35 -0
  117. package/src/autoresearch/git.ts +186 -26
  118. package/src/autoresearch/{helpers.ts → harness.ts} +65 -40
  119. package/src/autoresearch/{prompt-setup.md → prompts/prompt-setup.md} +7 -7
  120. package/src/autoresearch/prompts/prompt.md +103 -0
  121. package/src/autoresearch/prompts.ts +126 -0
  122. package/src/autoresearch/runs.ts +708 -0
  123. package/src/autoresearch/types.ts +9 -150
  124. package/src/capability/index.ts +4 -4
  125. package/src/capability/types.ts +20 -0
  126. package/src/cli/accounts-cli.ts +21 -5
  127. package/src/cli/config-cli.ts +25 -1
  128. package/src/cli/fast-help.ts +1 -2
  129. package/src/cli/list-models.ts +5 -8
  130. package/src/cli/setup-cli.ts +8 -0
  131. package/src/cli.ts +2 -21
  132. package/src/commands/autoresearch.ts +33 -0
  133. package/src/commands/coordinator.ts +15 -0
  134. package/src/commands/launch.ts +2 -4
  135. package/src/commands/quick-lane.ts +1 -1
  136. package/src/commands/sdk.ts +24 -0
  137. package/src/commands/state.ts +1 -1
  138. package/src/config/autorouting-contract.ts +474 -0
  139. package/src/config/autorouting-generator.ts +238 -0
  140. package/src/config/autorouting-tier-map.ts +6108 -0
  141. package/src/config/autorouting.ts +120 -0
  142. package/src/config/model-discovery-manager.ts +42 -11
  143. package/src/config/model-registry.ts +348 -68
  144. package/src/config/provider-selection-policy.ts +60 -9
  145. package/src/config/settings-schema.ts +195 -6
  146. package/src/config/settings.ts +62 -1
  147. package/src/config.ts +2 -3
  148. package/src/coordinator/contract.ts +0 -1
  149. package/src/coordinator-mcp/codex-handoff.ts +68 -42
  150. package/src/coordinator-mcp/durability.ts +200 -0
  151. package/src/coordinator-mcp/event-webhook.ts +278 -0
  152. package/src/coordinator-mcp/question-state.ts +109 -46
  153. package/src/coordinator-mcp/server.ts +932 -504
  154. package/src/crash/index-store.ts +126 -22
  155. package/src/crash/record-loader.ts +7 -0
  156. package/src/crash/upstream/relay.ts +82 -34
  157. package/src/cursor.ts +84 -1
  158. package/src/debug/index.ts +26 -0
  159. package/src/defaults/gjc/skills/autoresearch/SKILL.md +116 -0
  160. package/src/defaults/gjc/skills/autoresearch/auto-critic.md +46 -0
  161. package/src/defaults/gjc/skills/autoresearch/auto-iterate.md +42 -0
  162. package/src/defaults/gjc/skills/deep-interview/SKILL.md +17 -37
  163. package/src/defaults/gjc/skills/ralplan/SKILL.md +21 -18
  164. package/src/defaults/gjc/skills/ultragoal/SKILL.md +6 -17
  165. package/src/defaults/gjc/skills/ultragoal/ai-slop-cleaner.md +2 -2
  166. package/src/defaults/gjc-defaults.ts +89 -2
  167. package/src/defaults/gjc-skills.generated.ts +24 -17
  168. package/src/discovery/builtin.ts +18 -5
  169. package/src/discovery/helpers.ts +6 -6
  170. package/src/edit/index.ts +5 -2
  171. package/src/eval/py/executor.ts +8 -0
  172. package/src/extensibility/extensions/loader.ts +135 -13
  173. package/src/extensibility/extensions/wrapper.ts +1 -2
  174. package/src/extensibility/gjc-plugins/schema.ts +1 -1
  175. package/src/extensibility/runtime-skill-discovery.ts +2 -2
  176. package/src/extensibility/skill-management.ts +2 -3
  177. package/src/extensibility/skills.ts +16 -8
  178. package/src/gjc-runtime/autoresearch-runtime.ts +1509 -0
  179. package/src/gjc-runtime/gc-render.ts +0 -1
  180. package/src/gjc-runtime/gc-runtime.ts +1 -11
  181. package/src/gjc-runtime/launch-tmux.ts +3 -3
  182. package/src/gjc-runtime/psmux-detect.ts +2 -2
  183. package/src/gjc-runtime/python-transcript.ts +58 -0
  184. package/src/gjc-runtime/ralplan-runtime.ts +6 -18
  185. package/src/gjc-runtime/session-layout.ts +18 -7
  186. package/src/gjc-runtime/session-state-sidecar.ts +58 -9
  187. package/src/gjc-runtime/state-renderer.ts +0 -38
  188. package/src/gjc-runtime/state-runtime.ts +43 -22
  189. package/src/gjc-runtime/state-schema.ts +1 -1
  190. package/src/gjc-runtime/tmux-common.ts +7 -7
  191. package/src/gjc-runtime/ultragoal-change-set.ts +232 -11
  192. package/src/gjc-runtime/ultragoal-runtime.ts +309 -114
  193. package/src/gjc-runtime/ultragoal-validation-policy.ts +247 -0
  194. package/src/gjc-runtime/workflow-command-ref.ts +15 -11
  195. package/src/gjc-runtime/workflow-manifest.generated.json +413 -532
  196. package/src/gjc-runtime/workflow-manifest.ts +43 -83
  197. package/src/gjc-runtime/workflow-recovery-projection.ts +616 -0
  198. package/src/hooks/skill-keywords.ts +3 -3
  199. package/src/hooks/skill-state.ts +2 -2
  200. package/src/internal-urls/docs-index.generated.ts +26 -24
  201. package/src/lsp/index.ts +25 -5
  202. package/src/main.ts +2 -27
  203. package/src/modes/acp/acp-agent.ts +200 -75
  204. package/src/modes/components/model-selector.ts +216 -13
  205. package/src/modes/components/oauth-selector.ts +5 -5
  206. package/src/modes/components/skill-hud/render.ts +173 -35
  207. package/src/modes/components/smart-routing-panel.ts +531 -0
  208. package/src/modes/components/tool-status-header.ts +6 -3
  209. package/src/modes/controllers/command-controller.ts +17 -7
  210. package/src/modes/controllers/input-controller.ts +44 -9
  211. package/src/modes/controllers/runtime-mcp-command-controller.ts +4 -0
  212. package/src/modes/controllers/selector-controller.ts +298 -65
  213. package/src/modes/interactive-mode.ts +8 -1
  214. package/src/modes/shared/agent-wire/workflow-gate-types.ts +1 -1
  215. package/src/modes/types.ts +9 -1
  216. package/src/prompts/agents/planner.md +1 -1
  217. package/src/prompts/system/system-prompt.md +1 -1
  218. package/src/prompts/system/volatile-project-context.md +2 -1
  219. package/src/prompts/tools/move-session.md +15 -0
  220. package/src/prompts/tools/python.md +12 -0
  221. package/src/prompts/tools/skill-discovery.md +1 -1
  222. package/src/prompts/tools/skill.md +2 -2
  223. package/src/prompts/tools/task-summary.md +1 -0
  224. package/src/prompts/tools/task.md +6 -0
  225. package/src/rlm/artifacts.ts +12 -10
  226. package/src/runtime/optional-runtime-services.ts +3 -2
  227. package/src/runtime/workspace-tree-service.ts +11 -3
  228. package/src/runtime-mcp/config.ts +8 -1
  229. package/src/runtime-mcp/manager.ts +33 -0
  230. package/src/sdk/acp/adapter.ts +48 -1
  231. package/src/sdk/broker/broker.ts +30 -2
  232. package/src/sdk/broker/discovery.ts +217 -2
  233. package/src/sdk/broker/lifecycle.ts +385 -59
  234. package/src/sdk/broker/session-index.ts +243 -48
  235. package/src/sdk/broker/transport.ts +1 -0
  236. package/src/sdk/bus/chat-daemon-control.ts +8 -2
  237. package/src/sdk/bus/chat-daemon-runtime.ts +5 -0
  238. package/src/sdk/bus/index.ts +208 -11
  239. package/src/sdk/bus/kind-aware-reconciliation.ts +16 -0
  240. package/src/sdk/bus/reconciliation-store.ts +27 -5
  241. package/src/sdk/bus/telegram-daemon-contract.ts +12 -2
  242. package/src/sdk/client/client.ts +204 -22
  243. package/src/sdk/host/host.ts +16 -0
  244. package/src/sdk/host/internal-autorouting-state.ts +13 -0
  245. package/src/sdk/host/model-pin.ts +101 -0
  246. package/src/sdk/host/reverse-leases.ts +2 -1
  247. package/src/sdk/host/session-runtime.test.ts +224 -1
  248. package/src/sdk/host/session-runtime.ts +125 -37
  249. package/src/sdk/lifecycle/service.ts +3 -1
  250. package/src/sdk/lifecycle-session.ts +42 -1
  251. package/src/sdk/protocol/operation-inventory.generated.json +66 -0
  252. package/src/sdk/router/session-router.ts +375 -36
  253. package/src/sdk/session.ts +547 -47
  254. package/src/session/account-inventory.ts +1 -1
  255. package/src/session/agent-session.ts +765 -189
  256. package/src/session/artifacts.ts +234 -4
  257. package/src/session/internal/managed-session-storage.ts +91 -4
  258. package/src/session/provider-safety-stop-hint.ts +3 -2
  259. package/src/session/session-manager.ts +775 -16
  260. package/src/session/session-staging-paths.ts +8 -0
  261. package/src/setup/hermes/templates/operator-instructions.v1.md +0 -1
  262. package/src/setup/host-plugin-setup.ts +2 -2
  263. package/src/skill-state/active-state.ts +2 -2
  264. package/src/skill-state/canonical-skills.ts +1 -1
  265. package/src/skill-state/initial-phase.ts +1 -1
  266. package/src/skill-state/workflow-hud.ts +41 -41
  267. package/src/skill-state/workflow-mutation-guard.ts +26 -7
  268. package/src/slash-commands/builtin-registry.ts +77 -0
  269. package/src/slash-commands/helpers/autorouting-status.ts +55 -0
  270. package/src/system-prompt.ts +102 -5
  271. package/src/task/discovery.ts +2 -3
  272. package/src/task/executor.ts +617 -43
  273. package/src/task/index.ts +262 -15
  274. package/src/task/receipt.ts +30 -0
  275. package/src/task/render.ts +11 -1
  276. package/src/task/types.ts +316 -0
  277. package/src/tools/atomic-file-write.ts +594 -0
  278. package/src/tools/bisect.ts +3 -2
  279. package/src/tools/descriptors.test.ts +3 -0
  280. package/src/tools/descriptors.ts +24 -0
  281. package/src/tools/image-gen.ts +5 -1
  282. package/src/tools/index.ts +62 -5
  283. package/src/tools/move-session.ts +107 -0
  284. package/src/tools/python.ts +205 -0
  285. package/src/tools/read.ts +88 -15
  286. package/src/tools/renderers.ts +2 -0
  287. package/src/tools/resource-gc.ts +8 -177
  288. package/src/tools/search.ts +30 -6
  289. package/src/tools/skill-discovery.ts +1 -0
  290. package/src/tools/skill.ts +37 -7
  291. package/src/tools/subagent-render.ts +119 -13
  292. package/src/tools/subagent.ts +91 -74
  293. package/src/tools/tool-catalog.generated.ts +64 -2
  294. package/src/tools/write.ts +16 -12
  295. package/src/utils/xml-escape.ts +14 -0
  296. package/src/web/search/providers/codex.ts +2 -2
  297. package/src/workflow/workflow-intent-diff.ts +13 -12
  298. package/src/workflow/workflow-intent-report-consumer.ts +6 -6
  299. package/dist/types/autoresearch/helpers.d.ts +0 -24
  300. package/dist/types/autoresearch/index.d.ts +0 -2
  301. package/dist/types/autoresearch/state.d.ts +0 -17
  302. package/dist/types/autoresearch/storage.d.ts +0 -142
  303. package/dist/types/autoresearch/tools/init-experiment.d.ts +0 -31
  304. package/dist/types/autoresearch/tools/log-experiment.d.ts +0 -23
  305. package/dist/types/autoresearch/tools/run-experiment.d.ts +0 -8
  306. package/dist/types/autoresearch/tools/update-notes.d.ts +0 -12
  307. package/dist/types/commands/team.d.ts +0 -24
  308. package/dist/types/gjc-runtime/team-gc.d.ts +0 -7
  309. package/dist/types/gjc-runtime/team-launch.d.ts +0 -61
  310. package/dist/types/gjc-runtime/team-notify.d.ts +0 -45
  311. package/dist/types/gjc-runtime/team-runtime.d.ts +0 -336
  312. package/dist/types/gjc-runtime/team-store.d.ts +0 -168
  313. package/dist/types/gjc-runtime/team-worker-heartbeat.d.ts +0 -43
  314. package/dist/types/gjc-runtime/team-worker-memory-guard.d.ts +0 -92
  315. package/dist/types/gjc-runtime/team-workers.d.ts +0 -83
  316. package/dist/types/rlm/index.d.ts +0 -47
  317. package/dist/types/rlm/preset.d.ts +0 -23
  318. package/dist/types/rlm/python-tool.d.ts +0 -16
  319. package/src/autoresearch/command-resume.md +0 -14
  320. package/src/autoresearch/index.ts +0 -536
  321. package/src/autoresearch/prompt.md +0 -103
  322. package/src/autoresearch/resume-message.md +0 -10
  323. package/src/autoresearch/state.ts +0 -273
  324. package/src/autoresearch/storage.ts +0 -699
  325. package/src/autoresearch/tools/init-experiment.ts +0 -272
  326. package/src/autoresearch/tools/log-experiment.ts +0 -524
  327. package/src/autoresearch/tools/run-experiment.ts +0 -475
  328. package/src/autoresearch/tools/update-notes.ts +0 -109
  329. package/src/commands/rlm.ts +0 -19
  330. package/src/commands/team.ts +0 -238
  331. package/src/defaults/gjc/skills/deep-interview/auto-research-greenfield.md +0 -42
  332. package/src/defaults/gjc/skills/team/SKILL.md +0 -467
  333. package/src/gjc-runtime/team-gc.ts +0 -49
  334. package/src/gjc-runtime/team-launch.ts +0 -404
  335. package/src/gjc-runtime/team-notify.ts +0 -326
  336. package/src/gjc-runtime/team-runtime.ts +0 -5923
  337. package/src/gjc-runtime/team-store.ts +0 -878
  338. package/src/gjc-runtime/team-worker-heartbeat.ts +0 -149
  339. package/src/gjc-runtime/team-worker-memory-guard.ts +0 -363
  340. package/src/gjc-runtime/team-workers.ts +0 -1123
  341. package/src/prompts/system/rlm-report-command.md +0 -1
  342. package/src/prompts/system/rlm-research.md +0 -23
  343. package/src/rlm/index.ts +0 -365
  344. package/src/rlm/preset.ts +0 -76
  345. package/src/rlm/python-tool.ts +0 -68
package/src/task/index.ts CHANGED
@@ -26,6 +26,8 @@ import {
26
26
  } from "@gajae-code/coding-agent/async";
27
27
  import { $pickenv, prompt, Snowflake } from "@gajae-code/utils";
28
28
  import type { ToolSession } from "..";
29
+ import { normalizeTierSelector, type RoutingOutcome, resolveTaskRouting } from "../config/autorouting";
30
+ import { AUTOROUTING_SELECTOR_MAX_LENGTH, type AutoroutingReasonCode } from "../config/autorouting-contract";
29
31
  import { resolveProfileBindings } from "../config/model-profiles";
30
32
  import { resolveAgentModelPatterns } from "../config/model-resolver";
31
33
  import type { Theme } from "../modes/theme/theme";
@@ -33,7 +35,9 @@ import planModeSubagentPrompt from "../prompts/system/plan-mode-subagent.md" wit
33
35
  import taskDescriptionTemplate from "../prompts/tools/task.md" with { type: "text" };
34
36
  import taskSummaryTemplate from "../prompts/tools/task-summary.md" with { type: "text" };
35
37
  import type { ForkContextSeed } from "../session/agent-session";
38
+ import { splitSelectorThinkingSuffix } from "../thinking";
36
39
  import { formatBytes, formatDuration } from "../tools/render-utils";
40
+ import { escapeXmlAttribute } from "../utils/xml-escape";
37
41
  import {
38
42
  type AgentDefinition,
39
43
  type AgentProgress,
@@ -44,6 +48,7 @@ import {
44
48
  type SingleResult,
45
49
  type TaskItem,
46
50
  type TaskParams,
51
+ type TaskRoutingEvidence,
47
52
  type TaskToolDetails,
48
53
  type TaskToolSchemaInstance,
49
54
  } from "./types";
@@ -66,7 +71,13 @@ import { generateCommitMessage } from "../utils/commit-message-generator";
66
71
  import * as git from "../utils/git";
67
72
  import { loadBundledAgents } from "./agents";
68
73
  import { discoverAgents, filterVisibleAgents, getAgent } from "./discovery";
69
- import { createManagedTaskPersistence, renderSubagentUserPrompt, runSubprocess } from "./executor";
74
+ import {
75
+ buildBoundedRoutingSkips,
76
+ createManagedTaskPersistence,
77
+ renderSubagentUserPrompt,
78
+ runSubprocess,
79
+ } from "./executor";
80
+
70
81
  import { adviseForkContextMode } from "./fork-context-advisory";
71
82
  import { FORK_CONTEXT_TOKEN_BUDGET_BY_MODE } from "./fork-context-budget";
72
83
  import { getTaskIdValidationError, validateAllocatedTaskId } from "./id";
@@ -132,7 +143,7 @@ function renderTaskAssignment(assignment: string, simpleMode: TaskSimpleMode): s
132
143
  export function subagentRunOutcomeFromSingleResult(
133
144
  finalText: string,
134
145
  singleResult:
135
- | (Pick<SingleResult, "aborted" | "exitCode" | "paused" | "setupFailure"> &
146
+ | (Pick<SingleResult, "aborted" | "exitCode" | "paused" | "setupFailure" | "localErrorSummary"> &
136
147
  Partial<Pick<TaskResultReceipt, "status">>)
137
148
  | undefined,
138
149
  ): string | SubagentRunOutcome {
@@ -145,6 +156,9 @@ export function subagentRunOutcomeFromSingleResult(
145
156
  ...(singleResult.setupFailure && !singleResult.aborted
146
157
  ? { setupFailureSummary: singleResult.setupFailure.summary }
147
158
  : {}),
159
+ ...(singleResult.localErrorSummary && !singleResult.aborted
160
+ ? { localErrorSummary: singleResult.localErrorSummary }
161
+ : {}),
148
162
  };
149
163
  }
150
164
  if (singleResult && ((singleResult.aborted ?? false) || singleResult.exitCode !== 0)) {
@@ -154,6 +168,9 @@ export function subagentRunOutcomeFromSingleResult(
154
168
  ...(singleResult.setupFailure && !singleResult.aborted
155
169
  ? { setupFailureSummary: singleResult.setupFailure.summary }
156
170
  : {}),
171
+ ...(singleResult.localErrorSummary && !singleResult.aborted
172
+ ? { localErrorSummary: singleResult.localErrorSummary }
173
+ : {}),
157
174
  };
158
175
  }
159
176
  return finalText;
@@ -316,6 +333,7 @@ function renderDescription(
316
333
  simpleMode: TaskSimpleMode,
317
334
  ircEnabled: boolean,
318
335
  parentSpawns: string,
336
+ autoroutingActive: boolean,
319
337
  ): string {
320
338
  const spawningDisabled = parentSpawns === "";
321
339
  let filteredAgents = filterVisibleAgents(agents);
@@ -333,7 +351,7 @@ function renderDescription(
333
351
  filteredAgents = filteredAgents.filter(a => allowed.has(a.name));
334
352
  }
335
353
  const { contextEnabled, customSchemaEnabled } = getTaskSimpleModeCapabilities(simpleMode);
336
- return prompt.render(taskDescriptionTemplate, {
354
+ const description = prompt.render(taskDescriptionTemplate, {
337
355
  agents: filteredAgents,
338
356
  spawningDisabled,
339
357
  MAX_CONCURRENCY: maxConcurrency,
@@ -345,7 +363,9 @@ function renderDescription(
345
363
  defaultMode: simpleMode === "default",
346
364
  schemaFreeMode: simpleMode === "schema-free",
347
365
  independentMode: simpleMode === "independent",
366
+ autoroutingActive,
348
367
  });
368
+ return description;
349
369
  }
350
370
 
351
371
  function createTaskModeError(text: string): AgentToolResult<TaskToolDetails> {
@@ -479,6 +499,47 @@ export function resolveForkContextMaxTokens(configured: number, model: Model | u
479
499
  return normalizeForkContextCap(configured, fallback, Number.MAX_SAFE_INTEGER);
480
500
  }
481
501
 
502
+ const ROUTING_SUMMARY_UNSAFE_RE = /[\u0000-\u001f\u007f-\u009f\u2028\u2029]/gu;
503
+
504
+ function sanitizeRoutingSummaryValue(value: string): string {
505
+ return value.normalize("NFKC").replace(ROUTING_SUMMARY_UNSAFE_RE, " ").slice(0, AUTOROUTING_SELECTOR_MAX_LENGTH);
506
+ }
507
+
508
+ export function findRoutingSnapshotModel(selector: string, routingSnapshot: readonly Model[]): Model | undefined {
509
+ const slash = selector.indexOf("/");
510
+ if (slash <= 0) return undefined;
511
+ const provider = selector.slice(0, slash).toLowerCase();
512
+ const modelId = selector.slice(slash + 1);
513
+ const providerMatches = routingSnapshot.filter(candidate => candidate.provider.toLowerCase() === provider);
514
+ return (
515
+ providerMatches.find(candidate => candidate.id === modelId) ??
516
+ providerMatches.find(candidate => {
517
+ const suffix = splitSelectorThinkingSuffix(modelId);
518
+ return (
519
+ suffix.invalidSuffix === undefined && suffix.thinkingLevel !== undefined && candidate.id === suffix.selector
520
+ );
521
+ })
522
+ );
523
+ }
524
+
525
+ /**
526
+ * Project routing evidence into the XML-attribute-safe display view consumed by
527
+ * the task-summary template. The template runtime uses noEscape, so every
528
+ * interpolated attribute value must be escaped here.
529
+ */
530
+ export function projectRoutingForSummary(
531
+ routing: TaskRoutingEvidence | undefined,
532
+ ): { tier: string; effectiveModel: string; note: string } | undefined {
533
+ if (!routing) return undefined;
534
+ return {
535
+ tier: escapeXmlAttribute(sanitizeRoutingSummaryValue(routing.tier)),
536
+ effectiveModel: escapeXmlAttribute(
537
+ sanitizeRoutingSummaryValue(routing.effectiveModel ?? routing.requestedSelector),
538
+ ),
539
+ note: escapeXmlAttribute(sanitizeRoutingSummaryValue(routing.note ?? "")),
540
+ };
541
+ }
542
+
482
543
  // ═══════════════════════════════════════════════════════════════════════════
483
544
  // Tool Class
484
545
  // ═══════════════════════════════════════════════════════════════════════════
@@ -559,9 +620,11 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
559
620
  this.#getTaskSimpleMode(),
560
621
  hasAvailableIrcTool(this.session),
561
622
  this.session.getSessionSpawns() ?? "*",
623
+ this.session.settings.getEffectiveAutorouting().active,
562
624
  );
563
625
  }
564
626
  readonly #sessionRepositoryBinding: RepositoryBinding;
627
+ #testRunSubprocess: typeof runSubprocess | undefined;
565
628
 
566
629
  private constructor(
567
630
  private readonly session: ToolSession,
@@ -573,6 +636,10 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
573
636
  this.#sessionRepositoryBinding = sessionRepositoryBinding;
574
637
  }
575
638
 
639
+ #runSubprocess(options: Parameters<typeof runSubprocess>[0]): Promise<SingleResult> {
640
+ return (this.#testRunSubprocess ?? runSubprocess)(options);
641
+ }
642
+
576
643
  #getTaskSimpleMode(): TaskSimpleMode {
577
644
  return this.session.settings.get("task.simple");
578
645
  }
@@ -794,12 +861,13 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
794
861
  * Repository authority is captured from session cwd *before* agent discovery so
795
862
  * multi-repo workspaces fail closed prior to context/discovery (#2901).
796
863
  */
797
- static async create(session: ToolSession): Promise<TaskTool> {
864
+ static async create(session: ToolSession, options?: { runSubprocess?: typeof runSubprocess }): Promise<TaskTool> {
798
865
  const sessionRepositoryBinding = await captureRepositoryBinding(session.cwd, { displayPath: session.cwd });
799
- // Authority check before discovery: session cwd must resolve to a stable binding.
800
866
  await assertExecutionRootMatchesRepositoryBinding(session.cwd, sessionRepositoryBinding);
801
867
  const { agents } = await discoverAgents(session.cwd);
802
- return new TaskTool(session, agents, publicRepositoryBinding(sessionRepositoryBinding));
868
+ const tool = new TaskTool(session, agents, publicRepositoryBinding(sessionRepositoryBinding));
869
+ tool.#testRunSubprocess = options?.runSubprocess;
870
+ return tool;
803
871
  }
804
872
 
805
873
  /** Create catalog metadata from bundled agents only, without ambient filesystem discovery. */
@@ -1941,6 +2009,123 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
1941
2009
  }
1942
2010
  const tasksWithUniqueIds = tasks.map((t, i) => ({ ...t, id: validateAllocatedTaskId(uniqueIds[i] ?? "") }));
1943
2011
 
2012
+ const effectiveAutorouting = this.session.settings.getEffectiveAutorouting();
2013
+ const registry = this.session.modelRegistry as
2014
+ | {
2015
+ getAvailable?: () => Model[];
2016
+ getAll?: () => Model[];
2017
+ getApiKey?: (model: Model, credentialSessionId?: string) => Promise<string | undefined>;
2018
+ }
2019
+ | undefined;
2020
+ const routingSnapshot = registry?.getAll?.() ?? registry?.getAvailable?.();
2021
+ const routingByIndex = new Map<number, RoutingOutcome>();
2022
+ const routingCandidatesByIndex = new Map<number, string[]>();
2023
+ const routingSkipsByIndex = new Map<number, Array<{ selector: string; code: AutoroutingReasonCode }>>();
2024
+ for (let i = 0; i < tasksWithUniqueIds.length; i++) {
2025
+ const task = tasksWithUniqueIds[i];
2026
+ const outcome = routingSnapshot
2027
+ ? resolveTaskRouting({
2028
+ effectiveAutorouting,
2029
+ requestedTier: task.tier,
2030
+ availableModels: routingSnapshot,
2031
+ })
2032
+ : effectiveAutorouting.active
2033
+ ? {
2034
+ kind: "manual-fallback" as const,
2035
+ tier: task.tier ?? "balanced",
2036
+ requestedTier: task.tier,
2037
+ ...(task.tier === undefined ? { defaultTierApplied: true as const } : {}),
2038
+ attemptedSelectorCount: 0,
2039
+ reason: "tier_unmatched" as const,
2040
+ }
2041
+ : { kind: "disabled" as const };
2042
+
2043
+ routingByIndex.set(i, outcome);
2044
+ if (outcome.kind === "disabled" || !routingSnapshot || !effectiveAutorouting.active) continue;
2045
+
2046
+ // Enumerate every configured selector before choosing a routed or manual
2047
+ // outcome. This keeps AC12 evidence complete even when every selector is
2048
+ // missing, malformed, or disabled.
2049
+ const configured = effectiveAutorouting.map[outcome.tier] ?? [];
2050
+ const candidates: string[] = [];
2051
+ const skips: Array<{ selector: string; code: AutoroutingReasonCode }> = [];
2052
+ const disabledProviders = new Set(
2053
+ this.session.settings.get("disabledProviders").map(provider => provider.toLowerCase()),
2054
+ );
2055
+ for (const selector of configured) {
2056
+ const slash = selector.indexOf("/");
2057
+ const provider = slash > 0 ? selector.slice(0, slash).toLowerCase() : "";
2058
+ // Disabled takes precedence over snapshot presence: a disabled provider
2059
+ // that is also absent must remain truthfully classified as disabled.
2060
+ if (disabledProviders.has(provider)) {
2061
+ skips.push({ selector, code: "provider_disabled" });
2062
+ continue;
2063
+ }
2064
+ const normalized = normalizeTierSelector(selector, routingSnapshot);
2065
+ if (!("pinned" in normalized)) {
2066
+ skips.push({
2067
+ selector,
2068
+ code: "rejected" in normalized ? "selector_not_provider_qualified" : "snapshot_missing",
2069
+ });
2070
+ continue;
2071
+ }
2072
+ candidates.push(normalized.pinned);
2073
+ }
2074
+ routingCandidatesByIndex.set(i, candidates);
2075
+ routingSkipsByIndex.set(i, skips);
2076
+ }
2077
+ const resolveAutoroutingCandidates = async (
2078
+ index: number,
2079
+ ): Promise<{
2080
+ candidates: string[];
2081
+ skips: Array<{ selector: string; code: AutoroutingReasonCode }>;
2082
+ preflightErrors: Map<string, unknown>;
2083
+ }> => {
2084
+ const candidates = [...(routingCandidatesByIndex.get(index) ?? [])];
2085
+ const skips = [...(routingSkipsByIndex.get(index) ?? [])];
2086
+ const preflightErrors = new Map<string, unknown>();
2087
+ if (!registry?.getApiKey || !routingSnapshot) return { candidates, skips, preflightErrors };
2088
+ const authenticated: string[] = [];
2089
+ for (const selector of candidates) {
2090
+ const model = findRoutingSnapshotModel(selector, routingSnapshot);
2091
+ if (!model) {
2092
+ skips.push({ selector, code: "snapshot_missing" });
2093
+ continue;
2094
+ }
2095
+ try {
2096
+ const key = await registry.getApiKey(
2097
+ model,
2098
+ this.session.getCredentialSessionId?.() ?? this.session.getSessionId?.() ?? undefined,
2099
+ );
2100
+ if (key) authenticated.push(selector);
2101
+ else skips.push({ selector, code: "credential_unavailable" });
2102
+ } catch (error) {
2103
+ // Preserve the candidate for executor preflight, which records the
2104
+ // terminal lookup failure in the routing ledger.
2105
+ preflightErrors.set(selector, error);
2106
+ authenticated.push(selector);
2107
+ }
2108
+ }
2109
+ return { candidates: authenticated, skips, preflightErrors };
2110
+ };
2111
+ const effectivePatterns = (index: number): string | string[] => {
2112
+ const outcome = routingByIndex.get(index);
2113
+ return outcome?.kind === "routed" ? [outcome.pinnedSelector] : modelOverride;
2114
+ };
2115
+ const routeEvidenceForSynthetic = (outcome: RoutingOutcome | undefined): TaskRoutingEvidence | undefined => {
2116
+ if (!outcome || outcome.kind === "disabled") return undefined;
2117
+ return {
2118
+ tier: outcome.tier,
2119
+ requestedTier: outcome.requestedTier,
2120
+ defaultTierApplied: outcome.defaultTierApplied,
2121
+ requestedSelector: outcome.kind === "routed" ? outcome.pinnedSelector : "manual-model-chain",
2122
+ notExecuted: true,
2123
+ substitutions: [],
2124
+ manualFallbackReason: outcome.kind === "manual-fallback" ? outcome.reason : undefined,
2125
+ note: `${outcome.tier}; ${outcome.kind === "manual-fallback" ? outcome.reason : "not-executed"}`,
2126
+ };
2127
+ };
2128
+
1944
2129
  const availableSkills = [...(this.session.skills ?? [])];
1945
2130
  // Resolve autoload skills from agent definition against available skills
1946
2131
  const resolvedAutoloadSkills =
@@ -1975,7 +2160,7 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
1975
2160
  tokens: 0,
1976
2161
  cost: 0,
1977
2162
  durationMs: 0,
1978
- modelOverride,
2163
+ modelOverride: effectivePatterns(i),
1979
2164
  description: taskItem.description,
1980
2165
  });
1981
2166
  }
@@ -2034,6 +2219,51 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
2034
2219
  const managedPersistence = parentArtifactManager?.getManagedStore()
2035
2220
  ? createManagedTaskPersistence(parentArtifactManager, task.id)
2036
2221
  : undefined;
2222
+
2223
+ const routingOutcome = routingByIndex.get(index);
2224
+
2225
+ const routeEvidence = (
2226
+ outcome: RoutingOutcome | undefined,
2227
+ freshOnResume = false,
2228
+ ): TaskRoutingEvidence | undefined => {
2229
+ if (!outcome || outcome.kind === "disabled") return undefined;
2230
+ const noteParts = [
2231
+ `${outcome.tier}${outcome.defaultTierApplied ? " (default)" : ""}`,
2232
+ outcome.kind === "manual-fallback" ? outcome.reason : undefined,
2233
+ freshOnResume ? "freshOnResume" : undefined,
2234
+ ].filter((part): part is string => part !== undefined);
2235
+ return {
2236
+ tier: outcome.tier,
2237
+ requestedTier: outcome.requestedTier,
2238
+ defaultTierApplied: outcome.defaultTierApplied,
2239
+ requestedSelector: outcome.kind === "routed" ? outcome.pinnedSelector : "manual-model-chain",
2240
+ effectiveModel: outcome.kind === "routed" ? outcome.pinnedSelector : "manual-model-chain",
2241
+ substitutions: [],
2242
+ manualFallbackReason: outcome.kind === "manual-fallback" ? outcome.reason : undefined,
2243
+ freshOnResume: freshOnResume ? true : undefined,
2244
+ note: noteParts.join("; "),
2245
+ };
2246
+ };
2247
+
2248
+ const effectiveRunMode = overrides?.runMode ?? executionOverrides?.runMode;
2249
+ const autoroutingInitial =
2250
+ routingOutcome?.kind === "routed" && (effectiveRunMode ?? "initial") === "initial";
2251
+ const autoroutingData =
2252
+ (effectiveRunMode ?? "initial") === "initial" && routingOutcome && routingOutcome.kind !== "disabled"
2253
+ ? routingOutcome.kind === "routed"
2254
+ ? await resolveAutoroutingCandidates(index)
2255
+ : {
2256
+ candidates: undefined,
2257
+ skips: [...(routingSkipsByIndex.get(index) ?? [])],
2258
+ preflightErrors: new Map(),
2259
+ }
2260
+ : { candidates: undefined, skips: undefined, preflightErrors: new Map() };
2261
+ const routingForRun = routeEvidence(
2262
+ routingOutcome,
2263
+ effectiveRunMode === "resume" || effectiveRunMode === "message",
2264
+ );
2265
+ if (routingForRun && autoroutingData.skips)
2266
+ Object.assign(routingForRun, buildBoundedRoutingSkips(autoroutingData.skips));
2037
2267
  const taskSessionFile = managedPersistence
2038
2268
  ? null
2039
2269
  : (overrides?.sessionFile ?? executionOverrides?.sessionFiles?.get(task.id) ?? null);
@@ -2044,7 +2274,7 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
2044
2274
  }
2045
2275
  if (!isIsolated) {
2046
2276
  await assertExecutionRootMatchesRepositoryBinding(this.session.cwd, taskRepositoryBinding);
2047
- const result = await runSubprocess({
2277
+ const result = await this.#runSubprocess({
2048
2278
  cwd: this.session.cwd,
2049
2279
  agent: effectiveAgent,
2050
2280
  task: renderTaskAssignment(task.assignment, simpleMode),
@@ -2054,12 +2284,13 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
2054
2284
  description: task.description,
2055
2285
  index,
2056
2286
  id: task.id,
2057
- runMode: overrides?.runMode ?? executionOverrides?.runMode,
2287
+ runMode: effectiveRunMode,
2288
+
2058
2289
  resumeMessage: overrides?.resumeMessage ?? executionOverrides?.resumeMessage,
2059
2290
  subagentId: task.id,
2060
2291
  asyncJobManager: manager,
2061
2292
  taskDepth,
2062
- modelOverride,
2293
+ modelOverride: effectivePatterns(index),
2063
2294
  parentActiveModelPattern,
2064
2295
  parentActiveModelProfile: parentOwnedModelProfile,
2065
2296
  parentSessionId: this.session.getSessionId?.() ?? undefined,
@@ -2076,6 +2307,12 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
2076
2307
  enableLsp: subagentLspEnabled,
2077
2308
  signal,
2078
2309
  eventBus: this.session.eventBus,
2310
+ routing: routingForRun,
2311
+ autoroutingCandidates: autoroutingInitial ? autoroutingData.candidates : undefined,
2312
+ autoroutingSkips: autoroutingInitial ? autoroutingData.skips : undefined,
2313
+ autoroutingPreflightErrors: autoroutingInitial ? autoroutingData.preflightErrors : undefined,
2314
+ autoroutingPreflight: autoroutingInitial,
2315
+
2079
2316
  onProgress: progress => {
2080
2317
  progressMap.set(index, {
2081
2318
  ...structuredClone(progress),
@@ -2121,7 +2358,7 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
2121
2358
  // Isolated worktrees must preserve the source repository identity (#2901).
2122
2359
  await assertExecutionRootMatchesRepositoryBinding(isolationDir, taskRepositoryBinding);
2123
2360
 
2124
- const result = await runSubprocess({
2361
+ const result = await this.#runSubprocess({
2125
2362
  cwd: this.session.cwd,
2126
2363
  worktree: isolationDir,
2127
2364
  agent: effectiveAgent,
@@ -2132,12 +2369,13 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
2132
2369
  description: task.description,
2133
2370
  index,
2134
2371
  id: task.id,
2135
- runMode: overrides?.runMode ?? executionOverrides?.runMode,
2372
+ runMode: effectiveRunMode,
2373
+
2136
2374
  resumeMessage: overrides?.resumeMessage ?? executionOverrides?.resumeMessage,
2137
2375
  subagentId: task.id,
2138
2376
  asyncJobManager: manager,
2139
2377
  taskDepth,
2140
- modelOverride,
2378
+ modelOverride: effectivePatterns(index),
2141
2379
  parentActiveModelPattern,
2142
2380
  parentActiveModelProfile: parentOwnedModelProfile,
2143
2381
  parentSessionId: this.session.getSessionId?.() ?? undefined,
@@ -2154,6 +2392,11 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
2154
2392
  enableLsp: subagentLspEnabled,
2155
2393
  signal,
2156
2394
  eventBus: this.session.eventBus,
2395
+ routing: routingForRun,
2396
+ autoroutingCandidates: autoroutingInitial ? autoroutingData.candidates : undefined,
2397
+ autoroutingSkips: autoroutingInitial ? autoroutingData.skips : undefined,
2398
+ autoroutingPreflightErrors: autoroutingInitial ? autoroutingData.preflightErrors : undefined,
2399
+ autoroutingPreflight: autoroutingInitial,
2157
2400
  onProgress: progress => {
2158
2401
  progressMap.set(index, {
2159
2402
  ...structuredClone(progress),
@@ -2285,7 +2528,9 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
2285
2528
  truncated: false,
2286
2529
  durationMs: Date.now() - taskStart,
2287
2530
  tokens: 0,
2288
- modelOverride,
2531
+ modelOverride: effectivePatterns(index),
2532
+ routing: routeEvidenceForSynthetic(routingByIndex.get(index)),
2533
+
2289
2534
  forkContext,
2290
2535
  error: message,
2291
2536
  };
@@ -2325,7 +2570,8 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
2325
2570
  truncated: false,
2326
2571
  durationMs: 0,
2327
2572
  tokens: 0,
2328
- modelOverride,
2573
+ modelOverride: effectivePatterns(index),
2574
+ routing: routeEvidenceForSynthetic(routingByIndex.get(index)),
2329
2575
  error: "Cancelled before start",
2330
2576
  aborted: true,
2331
2577
  abortReason: "Cancelled before start",
@@ -2647,6 +2893,7 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
2647
2893
  charSize: formatBytes(r.outputRef.sizeBytes),
2648
2894
  }
2649
2895
  : undefined,
2896
+ routing: projectRoutingForSummary(r.routing),
2650
2897
  };
2651
2898
  });
2652
2899
 
@@ -1,7 +1,10 @@
1
+ import { AUTOROUTING_SELECTOR_MAX_LENGTH } from "../config/autorouting-contract";
1
2
  import {
3
+ assertRoutingEvidenceInvariant,
2
4
  hasCompleteUsageCostBreakdown,
3
5
  type ReviewFindingsArtifactRef,
4
6
  type SingleResult,
7
+ type TaskRoutingEvidence,
5
8
  type TaskToolDetails,
6
9
  } from "./types";
7
10
  export interface TaskRoi {
@@ -33,6 +36,7 @@ export interface TaskResultReceipt {
33
36
  contextTokens?: number;
34
37
  contextWindow?: number;
35
38
  modelOverride?: string | string[];
39
+ routing?: TaskRoutingEvidence;
36
40
  modelSubstitutionWarning?: SingleResult["modelSubstitutionWarning"];
37
41
  fastMode?: boolean;
38
42
  usage?: SingleResult["usage"];
@@ -42,6 +46,7 @@ export interface TaskResultReceipt {
42
46
  persistence?: SingleResult["persistence"];
43
47
  retryFailure?: { attempt: number; errorSummary: string };
44
48
  setupFailure?: { summary: string };
49
+ localErrorSummary?: SingleResult["localErrorSummary"];
45
50
  errorSummary?: string;
46
51
  duplicateDisposition?: SingleResult["duplicateDisposition"];
47
52
  abortSummary?: string;
@@ -145,6 +150,9 @@ function buildSafeSynopsis(raw: SingleResult, outputRef: TaskResultReceipt["outp
145
150
  if (raw.abortReason) {
146
151
  return `Task ${status}; abort reason recorded.`;
147
152
  }
153
+ if (raw.localErrorSummary) {
154
+ return `Task ${status}; local failure (${raw.localErrorSummary.kind}): ${raw.localErrorSummary.summary}`;
155
+ }
148
156
  if (raw.error) {
149
157
  return `Task ${status}; error recorded.`;
150
158
  }
@@ -243,6 +251,25 @@ export function buildTaskRoiSummary(receipts: readonly TaskResultReceipt[]): Tas
243
251
  lowRoiChildIds: receipts.filter(receipt => receipt.roi?.lowRoi).map(receipt => receipt.id),
244
252
  };
245
253
  }
254
+ function validatedRoutingEvidence(value: TaskRoutingEvidence | undefined): TaskRoutingEvidence | undefined {
255
+ if (!value) return undefined;
256
+ try {
257
+ const bounded: TaskRoutingEvidence = {
258
+ ...value,
259
+ requestedSelector: value.requestedSelector.slice(0, AUTOROUTING_SELECTOR_MAX_LENGTH),
260
+ skips: value.skips
261
+ ?.slice(0, 16)
262
+ .map(skip => ({ ...skip, selector: skip.selector.slice(0, AUTOROUTING_SELECTOR_MAX_LENGTH) })),
263
+ attempts: value.attempts
264
+ ?.slice(0, 6)
265
+ .map(attempt => ({ ...attempt, selector: attempt.selector.slice(0, AUTOROUTING_SELECTOR_MAX_LENGTH) })),
266
+ };
267
+ assertRoutingEvidenceInvariant(bounded);
268
+ return bounded;
269
+ } catch {
270
+ return undefined;
271
+ }
272
+ }
246
273
 
247
274
  export function buildTaskReceipt(raw: SingleResult): TaskResultReceipt {
248
275
  // Receipts only include outputRef when production code kept outputMeta after a
@@ -284,6 +311,7 @@ export function buildTaskReceipt(raw: SingleResult): TaskResultReceipt {
284
311
  contextWindow: raw.contextWindow,
285
312
  modelOverride: raw.modelOverride,
286
313
  modelSubstitutionWarning: raw.modelSubstitutionWarning,
314
+ routing: validatedRoutingEvidence(raw.routing),
287
315
  usage: raw.usage,
288
316
  cost: raw.usage?.cost.total,
289
317
  usageCostBreakdownComplete:
@@ -295,8 +323,10 @@ export function buildTaskReceipt(raw: SingleResult): TaskResultReceipt {
295
323
  ? { attempt: raw.retryFailure.attempt, errorSummary: "Retry failure recorded." }
296
324
  : undefined,
297
325
  duplicateDisposition: raw.duplicateDisposition,
326
+ localErrorSummary: raw.localErrorSummary,
298
327
  errorSummary:
299
328
  raw.setupFailure?.summary ??
329
+ raw.localErrorSummary?.summary ??
300
330
  (raw.error
301
331
  ? "Error recorded."
302
332
  : raw.persistence?.outcome === "recovery_available"
@@ -7,7 +7,7 @@
7
7
  import path from "node:path";
8
8
  import type { Component } from "@gajae-code/tui";
9
9
  import { Text } from "@gajae-code/tui";
10
- import { formatNumber, sanitizeText } from "@gajae-code/utils";
10
+ import { formatNumber, sanitizeDisplayLine, sanitizeText } from "@gajae-code/utils";
11
11
  import type { RenderResultOptions } from "../extensibility/custom-tools/types";
12
12
  import type { Theme } from "../modes/theme/theme";
13
13
  import {
@@ -913,6 +913,16 @@ function renderAgentResult(result: TaskResultReceipt, isLast: boolean, expanded:
913
913
  )}`,
914
914
  );
915
915
  }
916
+ if (result.routing) {
917
+ const model = result.routing.effectiveModel ?? "not-executed";
918
+ const note = result.routing.note ? ` ${result.routing.note}` : "";
919
+ // effectiveModel is provider-reported and note is free-form diagnostic text;
920
+ // receipt validation bounds neither, and an embedded newline would inject a
921
+ // row past the width cap, so flatten to a single sanitized line.
922
+ lines.push(
923
+ `${continuePrefix}${theme.fg("dim", truncateToWidth(replaceTabs(sanitizeDisplayLine(`Routing: ${model}${note}`)), 90))}`,
924
+ );
925
+ }
916
926
  if (result.roi?.lowRoi) {
917
927
  lines.push(`${continuePrefix}${theme.fg("warning", "low ROI: produced no material contribution")}`);
918
928
  }