@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/lsp/index.ts CHANGED
@@ -6,6 +6,7 @@ import type { BunFile } from "bun";
6
6
  import { type Theme, theme } from "../modes/theme/theme";
7
7
  import lspDescription from "../prompts/tools/lsp.md" with { type: "text" };
8
8
  import type { ToolSession } from "../tools";
9
+ import { FileWriteNotPublishedError, writeFileAtomically } from "../tools/atomic-file-write";
9
10
  import { formatPathRelativeToCwd, resolveToCwd } from "../tools/path-utils";
10
11
  import { ToolAbortError, ToolError, throwIfAborted } from "../tools/tool-errors";
11
12
  import { clampTimeout } from "../tools/tool-timeouts";
@@ -745,11 +746,11 @@ export async function writethroughNoop(
745
746
  _batch?: LspWritethroughBatchRequest,
746
747
  _getDeferred?: (dst: string) => WritethroughDeferredHandle | undefined,
747
748
  ): Promise<FileDiagnosticsResult | undefined> {
748
- if (file) {
749
+ if (file !== undefined) {
749
750
  await file.write(content);
750
- } else {
751
- await Bun.write(dst, content);
751
+ return undefined;
752
752
  }
753
+ await writeFileAtomically(dst, content);
753
754
  return undefined;
754
755
  }
755
756
 
@@ -921,7 +922,25 @@ async function runLspWritethrough(
921
922
  const { lspServers, customLinterServers } = splitServers(servers);
922
923
 
923
924
  let finalContent = content;
924
- const writeContent = async (value: string) => (file ? file.write(value) : Bun.write(dst, value));
925
+ let publishedContent = false;
926
+ const writeContent = async (value: string) => {
927
+ try {
928
+ if (file !== undefined) await file.write(value);
929
+ else await writeFileAtomically(dst, value);
930
+ publishedContent = true;
931
+ } catch (error) {
932
+ if (error instanceof FileWriteNotPublishedError) {
933
+ if (publishedContent) {
934
+ throw new FileWriteNotPublishedError(dst, error.cause, {
935
+ destUnchanged: false,
936
+ publicationState: "published",
937
+ });
938
+ }
939
+ throw error;
940
+ }
941
+ throw error;
942
+ }
943
+ };
925
944
  const getWritePromise = once(() => writeContent(finalContent));
926
945
  const useCustomFormatter = enableFormat && customLinterServers.length > 0;
927
946
 
@@ -986,7 +1005,8 @@ async function runLspWritethrough(
986
1005
  });
987
1006
  }
988
1007
  });
989
- } catch {
1008
+ } catch (error) {
1009
+ if (error instanceof FileWriteNotPublishedError) throw error;
990
1010
  if (timedOut) {
991
1011
  formatter = undefined;
992
1012
  diagnostics = undefined;
package/src/main.ts CHANGED
@@ -173,6 +173,8 @@ const ACP_DEFAULTED_SETTING_PATHS: SettingPath[] = [
173
173
  "task.maxRecursionDepth",
174
174
  "task.disabledAgents",
175
175
  "task.agentModelOverrides",
176
+ "task.autorouting.enabled",
177
+ "task.autorouting.tiers",
176
178
  // Memory subsystems are off-by-default for embedded (ACP) hosts; embedders
177
179
  // that want memory should opt in explicitly through their own settings layer.
178
180
  "memory.backend",
@@ -1234,16 +1236,6 @@ async function buildSessionOptions(
1234
1236
  return { options };
1235
1237
  }
1236
1238
 
1237
- /**
1238
- * Research-mode (RLM) preset hook. Lets `gjc rlm` augment the session options
1239
- * (system prompt, restricted toolset, custom python tool) and assert the tool
1240
- * boundary once the session's tool registry is fully assembled.
1241
- */
1242
- export interface RlmPreset {
1243
- applyOptions: (options: CreateAgentSessionOptions, settings: Settings) => void;
1244
- onSessionCreated?: (session: AgentSession) => void | Promise<void>;
1245
- }
1246
-
1247
1239
  type RunPrintMode = (session: AgentSession, options: PrintModeOptions) => Promise<void>;
1248
1240
 
1249
1241
  export interface RunRootCommandDependencies {
@@ -1252,7 +1244,6 @@ export interface RunRootCommandDependencies {
1252
1244
  discoverAuthStorage?: typeof discoverAuthStorage;
1253
1245
  runAcpMode?: (options?: { agentDir?: string }) => Promise<void>;
1254
1246
  settings?: Settings;
1255
- rlmPreset?: RlmPreset;
1256
1247
  suppressProcessExit?: boolean;
1257
1248
  startupUpdate?: { check: () => Promise<string | undefined> };
1258
1249
  initTheme?: typeof initTheme;
@@ -1685,8 +1676,6 @@ export async function runRootCommand(
1685
1676
  const deferMemoryBackendStartup = hasRootStartupProfile && !(parsedArgs.authBootstrap === true && isInteractive);
1686
1677
  sessionOptions.deferMemoryBackendStartup = deferMemoryBackendStartup;
1687
1678
 
1688
- // Research-mode (RLM) preset: augment session options before session creation.
1689
- deps.rlmPreset?.applyOptions(sessionOptions, settingsInstance);
1690
1679
  const acpStartupOptions = mode === "acp" ? resolveAcpStartupOptions(parsedArgs, sessionOptions) : undefined;
1691
1680
 
1692
1681
  // Handle CLI credential selection and --api-key as runtime overrides (not persisted).
@@ -1822,20 +1811,6 @@ export async function runRootCommand(
1822
1811
  // Herdr integration: report gjc lifecycle state when running in a Herdr pane.
1823
1812
  installHerdrReporter(listener => session.subscribe(listener));
1824
1813
 
1825
- // Research-mode (RLM) preset: hard tool-boundary assertion after the registry is assembled.
1826
- if (deps.rlmPreset?.onSessionCreated) {
1827
- try {
1828
- await deps.rlmPreset.onSessionCreated(session);
1829
- } catch (error) {
1830
- try {
1831
- await session.dispose();
1832
- } catch {
1833
- logger.warn("Failed to dispose session after RLM post-create error");
1834
- }
1835
- throw error;
1836
- }
1837
- }
1838
-
1839
1814
  let startDeferredModelProfiles: DeferredModelProfileStartup | undefined;
1840
1815
  if (!(parsedArgs.authBootstrap === true && isInteractive)) {
1841
1816
  const profileArgs = {
@@ -165,6 +165,8 @@ type SessionRecord = {
165
165
  /** Bounded set of correlations already settled; they stay closed for publication. */
166
166
  settledPromptCorrelations: PromptCorrelation[];
167
167
  authFailure?: string;
168
+ /** Replayable startup notice captured before ACP bootstrap; emitted once after the session id is known. */
169
+ routingInactiveNotice?: string;
168
170
  activePrompt?: PromptWaiter;
169
171
  /** Set by `session/cancel` so an in-flight prompt settles as `cancelled`, never as an error. */
170
172
  cancelRequested?: boolean;
@@ -830,23 +832,43 @@ export async function collectActiveProviderIds(
830
832
  const MAX_MODEL_CATALOG_PAGES = 100;
831
833
 
832
834
  /**
833
- * Collect every page of `models.list/current` (Q10) into one catalog so the
834
- * active-provider filter never drops models that only appear on later pages.
835
- * The SDK pages Q10 at a fixed byte target (256 KiB), which a fully
836
- * configured catalog can exceed. Returns the same
837
- * `{ result: { page: { items } } }` envelope `pageItems` consumes.
835
+ * Validate one `models.list/current` response and return its page. The SDK pages
836
+ * Q10 at a fixed byte target (256 KiB), which a fully configured catalog can
837
+ * exceed, so every page shape failure fails closed instead of silently dropping
838
+ * models the active-provider filter would need.
838
839
  */
839
- export async function collectModelCatalog(adapter: Pick<AcpSdkAdapter, "query">): Promise<unknown> {
840
+ interface ModelCatalogPage {
841
+ items: unknown[];
842
+ complete?: unknown;
843
+ continuationCursor?: unknown;
844
+ }
845
+
846
+ function requireCatalogPage(response: unknown): ModelCatalogPage {
847
+ const page = queryPage(response);
848
+ if (!page) throw new AcpSdkAdapterError("protocol_error", "models.list/current returned no page.");
849
+ if (!Array.isArray(page.items))
850
+ throw new AcpSdkAdapterError("protocol_error", "models.list/current returned a malformed page.");
851
+ return page as ModelCatalogPage;
852
+ }
853
+
854
+ async function collectModelCatalogContinuation(
855
+ adapter: Pick<AcpSdkAdapter, "query">,
856
+ firstPage: ModelCatalogPage,
857
+ ): Promise<unknown[]> {
858
+ if (firstPage.complete === true) return [];
859
+ if (typeof firstPage.continuationCursor !== "string")
860
+ throw new AcpSdkAdapterError(
861
+ "protocol_error",
862
+ "models.list/current page is incomplete without a continuation cursor.",
863
+ );
864
+ let cursor: string | undefined = firstPage.continuationCursor;
840
865
  const items: unknown[] = [];
841
- let cursor: string | undefined;
842
- for (let pageCount = 0; pageCount < MAX_MODEL_CATALOG_PAGES; pageCount++) {
866
+ // Page 1 was already consumed by the caller, so the remaining budget is one shorter.
867
+ for (let pageCount = 1; pageCount < MAX_MODEL_CATALOG_PAGES; pageCount++) {
843
868
  const response = await adapter.query("models.list/current", {}, cursor);
844
- const page = queryPage(response);
845
- if (!page) throw new AcpSdkAdapterError("protocol_error", "models.list/current returned no page.");
846
- if (!Array.isArray(page.items))
847
- throw new AcpSdkAdapterError("protocol_error", "models.list/current returned a malformed page.");
869
+ const page = requireCatalogPage(response);
848
870
  items.push(...page.items);
849
- if (page.complete === true) return { result: { page: { items } } };
871
+ if (page.complete === true) return items;
850
872
  if (typeof page.continuationCursor !== "string")
851
873
  throw new AcpSdkAdapterError(
852
874
  "protocol_error",
@@ -857,6 +879,32 @@ export async function collectModelCatalog(adapter: Pick<AcpSdkAdapter, "query">)
857
879
  throw new AcpSdkAdapterError("protocol_error", "models.list/current exceeded the page budget.");
858
880
  }
859
881
 
882
+ /**
883
+ * Collect the model catalog (Q10) and the active-provider set (Q29) under one
884
+ * credential ordering. Assembling the first Q10 page is what finalizes host-side
885
+ * credential state — expired or invalid OAuth credentials are refreshed or
886
+ * disabled while that snapshot is built, and continuation pages replay the
887
+ * frozen revision without further side effects — so Q29 must not start until
888
+ * page 1 has resolved. Starting both together would let Q29 snapshot pre-refresh
889
+ * credential state and mix catalog rows and provider availability from different
890
+ * credential states. After page 1, the provider walk overlaps the remaining
891
+ * catalog pages instead of adding its latency after them.
892
+ */
893
+ export async function collectModelCatalogAndActiveProviders(
894
+ adapter: Pick<AcpSdkAdapter, "query">,
895
+ ): Promise<{ modelCatalog: unknown; activeProviders: ReadonlySet<string> | undefined }> {
896
+ const firstResponse = await adapter.query("models.list/current");
897
+ const firstPage = requireCatalogPage(firstResponse);
898
+ const [remaining, activeProviders] = await Promise.all([
899
+ collectModelCatalogContinuation(adapter, firstPage),
900
+ collectActiveProviderIds(adapter),
901
+ ]);
902
+ return {
903
+ modelCatalog: { result: { page: { items: [...firstPage.items, ...remaining] } } },
904
+ activeProviders,
905
+ };
906
+ }
907
+
860
908
  const THINKING_CONFIG_OPTIONS = ["off", "minimal", "low", "medium", "high", "xhigh", "max"].map(value => ({
861
909
  value,
862
910
  name: value,
@@ -1133,13 +1181,23 @@ export class AcpAgent implements Agent {
1133
1181
  readonly #attaching = new Map<string, PendingAttachment>();
1134
1182
  readonly #resolvingExisting = new Map<string, PendingAttachment>();
1135
1183
  readonly #knownSessionCwds = new Map<string, string>();
1184
+ /**
1185
+ * Sessions this connection actually owns, i.e. ones it created or attached to.
1186
+ * Destructive lifecycle control gates on this set, never on `#knownSessionCwds`.
1187
+ *
1188
+ * `session/list` legitimately populates `#knownSessionCwds` for every session a
1189
+ * shared broker reports, so treating that map as ownership let a second ACP
1190
+ * connection enumerate another connection's sessions and then close or delete
1191
+ * them. Knowing a session's cwd is not authority over its lifecycle.
1192
+ */
1193
+ readonly #ownedSessionIds = new Set<string>();
1136
1194
  readonly #knownSessionMcpServers = new Map<string, SessionLifecycleMcpServer[]>();
1137
1195
  readonly #knownSessionMetadata = new Map<string, { title?: string; updatedAt?: string }>();
1138
1196
  readonly #pendingDeleteLocators = new Map<string, { cwd: string; path: string }>();
1139
1197
  readonly #pendingCloseIdempotencyKeys = new Map<string, string>();
1140
1198
  readonly #sessionEpochs = new Map<string, number>();
1141
1199
  readonly #tearingDown = new Map<string, number>();
1142
- readonly #closing = new Map<string, Promise<CloseSessionResponse>>();
1200
+ readonly #lifecycleOperations = new Map<string, Promise<void>>();
1143
1201
  #clientCapabilities: ClientCapabilities | undefined;
1144
1202
  #broker: Promise<BrokerConnection> | undefined;
1145
1203
  readonly #startupOptions: AcpStartupOptions | undefined;
@@ -1276,6 +1334,7 @@ export class AcpAgent implements Agent {
1276
1334
  );
1277
1335
  const id = sessionId(result);
1278
1336
  this.#knownSessionCwds.set(id, params.cwd);
1337
+ this.#ownedSessionIds.add(id);
1279
1338
  this.#knownSessionMcpServers.set(id, mcpServers);
1280
1339
  try {
1281
1340
  await this.#attach(id, params.cwd, undefined, result);
@@ -1331,6 +1390,7 @@ export class AcpAgent implements Agent {
1331
1390
  );
1332
1391
  const id = sessionId(result);
1333
1392
  this.#knownSessionCwds.set(id, params.cwd);
1393
+ this.#ownedSessionIds.add(id);
1334
1394
  this.#knownSessionMcpServers.set(id, mcpServers);
1335
1395
  try {
1336
1396
  await this.#attach(id, params.cwd, undefined, result);
@@ -1381,70 +1441,83 @@ export class AcpAgent implements Agent {
1381
1441
 
1382
1442
  closeSession(params: CloseSessionRequest): Promise<CloseSessionResponse> {
1383
1443
  const record = this.#sessions.get(params.sessionId);
1384
- const cwd = record?.cwd ?? this.#knownSessionCwds.get(params.sessionId);
1385
1444
  // ACP close has no cwd. Only connection-owned sessions may reach broker lifecycle control.
1445
+ if (!this.#ownedSessionIds.has(params.sessionId)) return Promise.resolve({});
1446
+ const cwd = record?.cwd ?? this.#knownSessionCwds.get(params.sessionId);
1386
1447
  if (!cwd) return Promise.resolve({});
1387
- const existing = this.#closing.get(params.sessionId);
1388
- if (existing) return existing;
1389
- const deferred = Promise.withResolvers<CloseSessionResponse>();
1390
- this.#closing.set(params.sessionId, deferred.promise);
1391
- void this.#closeOwnedSession(params.sessionId).then(deferred.resolve, deferred.reject);
1392
- const cleanup = deferred.promise.finally(() => {
1393
- if (this.#closing.get(params.sessionId) === deferred.promise) this.#closing.delete(params.sessionId);
1448
+ return this.#enqueueLifecycleOperation(params.sessionId, async () => {
1449
+ // A preceding delete in the same lifecycle chain already completed the terminal operation.
1450
+ if (!this.#ownedSessionIds.has(params.sessionId) && !this.#sessions.has(params.sessionId)) return {};
1451
+ return this.#closeOwnedSession(params.sessionId);
1394
1452
  });
1395
- void cleanup.catch(() => undefined);
1396
- return deferred.promise;
1397
1453
  }
1398
1454
 
1399
1455
  async deleteSession(params: DeleteSessionRequest): Promise<DeleteSessionResponse> {
1400
1456
  const record = this.#sessions.get(params.sessionId);
1401
1457
  const pendingLocator = this.#pendingDeleteLocators.get(params.sessionId);
1458
+ // Capture authority before joining the lifecycle chain: an admitted delete must
1459
+ // remain authorized when a preceding close retires connection ownership.
1460
+ if (!this.#ownedSessionIds.has(params.sessionId) && pendingLocator === undefined) return {};
1402
1461
  const cwd = record?.cwd ?? this.#knownSessionCwds.get(params.sessionId) ?? pendingLocator?.cwd;
1403
- // ACP's delete request has no cwd. Unknown ids remain the protocol no-op,
1404
- // while the broker can reconstruct an authenticated pending locator from its durable ledger.
1405
- if (!cwd) {
1406
- await (await this.#brokerAdapter()).global(
1407
- "session.delete",
1408
- { sessionId: params.sessionId },
1409
- this.#lifecycleIdempotencyKey(params.sessionId, "session.delete"),
1410
- );
1411
- return {};
1412
- }
1413
- this.#beginTeardown(params.sessionId);
1414
- try {
1415
- // A retained delete locator proves the prior attempt already completed
1416
- // connection/process teardown and reached durable artifact cleanup. Re-closing
1417
- // that terminal session can only replace the authoritative cleanup_pending
1418
- // result with unrelated close uncertainty, so retries resume deletion directly.
1419
- await this.#teardownSession(params.sessionId, "deleted", pendingLocator === undefined || record !== undefined);
1420
- let saved = pendingLocator?.cwd === cwd ? pendingLocator.path : undefined;
1421
- if (!saved) {
1422
- try {
1423
- saved = await this.#resolveSavedSession(params.sessionId, cwd);
1424
- } catch (error) {
1425
- if (error instanceof AcpSdkAdapterError && error.code === "not_found") {
1426
- this.#knownSessionCwds.delete(params.sessionId);
1427
- this.#knownSessionMcpServers.delete(params.sessionId);
1428
- this.#knownSessionMetadata.delete(params.sessionId);
1429
- return {};
1462
+ return this.#enqueueLifecycleOperation(params.sessionId, async () => {
1463
+ // ACP's delete request has no cwd. Unknown ids remain the protocol no-op,
1464
+ // while the broker can reconstruct an authenticated pending locator from its durable ledger.
1465
+ if (!cwd) {
1466
+ await (await this.#brokerAdapter()).global(
1467
+ "session.delete",
1468
+ { sessionId: params.sessionId },
1469
+ this.#lifecycleIdempotencyKey(params.sessionId, "session.delete"),
1470
+ );
1471
+ return {};
1472
+ }
1473
+ this.#beginTeardown(params.sessionId);
1474
+ try {
1475
+ // A preceding close in the lifecycle chain already completed process teardown.
1476
+ // Continue with durable deletion instead of re-closing an already-dead process.
1477
+ const teardownCompleted =
1478
+ !this.#ownedSessionIds.has(params.sessionId) && !this.#sessions.has(params.sessionId);
1479
+ if (!teardownCompleted) {
1480
+ // A retained delete locator proves the prior attempt already completed
1481
+ // connection/process teardown and reached durable artifact cleanup. Re-closing
1482
+ // that terminal session can only replace the authoritative cleanup_pending
1483
+ // result with unrelated close uncertainty, so retries resume deletion directly.
1484
+ await this.#teardownSession(
1485
+ params.sessionId,
1486
+ "deleted",
1487
+ pendingLocator === undefined || record !== undefined,
1488
+ );
1489
+ }
1490
+ let saved = pendingLocator?.cwd === cwd ? pendingLocator.path : undefined;
1491
+ if (!saved) {
1492
+ try {
1493
+ saved = await this.#resolveSavedSession(params.sessionId, cwd);
1494
+ } catch (error) {
1495
+ if (error instanceof AcpSdkAdapterError && error.code === "not_found") {
1496
+ this.#knownSessionCwds.delete(params.sessionId);
1497
+ this.#ownedSessionIds.delete(params.sessionId);
1498
+ this.#knownSessionMcpServers.delete(params.sessionId);
1499
+ this.#knownSessionMetadata.delete(params.sessionId);
1500
+ return {};
1501
+ }
1502
+ throw error;
1430
1503
  }
1431
- throw error;
1432
1504
  }
1505
+ this.#pendingDeleteLocators.set(params.sessionId, { cwd, path: saved });
1506
+ await (await this.#brokerAdapter()).global(
1507
+ "session.delete",
1508
+ { sessionId: params.sessionId, sessionPath: saved, cwd, target: { path: cwd } },
1509
+ this.#lifecycleIdempotencyKey(params.sessionId, "session.delete"),
1510
+ );
1511
+ this.#knownSessionCwds.delete(params.sessionId);
1512
+ this.#ownedSessionIds.delete(params.sessionId);
1513
+ this.#knownSessionMcpServers.delete(params.sessionId);
1514
+ this.#knownSessionMetadata.delete(params.sessionId);
1515
+ this.#pendingDeleteLocators.delete(params.sessionId);
1516
+ return {};
1517
+ } finally {
1518
+ this.#finishTeardown(params.sessionId);
1433
1519
  }
1434
- this.#pendingDeleteLocators.set(params.sessionId, { cwd, path: saved });
1435
- await (await this.#brokerAdapter()).global(
1436
- "session.delete",
1437
- { sessionId: params.sessionId, sessionPath: saved, cwd, target: { path: cwd } },
1438
- this.#lifecycleIdempotencyKey(params.sessionId, "session.delete"),
1439
- );
1440
- this.#knownSessionCwds.delete(params.sessionId);
1441
- this.#knownSessionMcpServers.delete(params.sessionId);
1442
- this.#knownSessionMetadata.delete(params.sessionId);
1443
- this.#pendingDeleteLocators.delete(params.sessionId);
1444
- return {};
1445
- } finally {
1446
- this.#finishTeardown(params.sessionId);
1447
- }
1520
+ });
1448
1521
  }
1449
1522
 
1450
1523
  async setSessionMode(params: SetSessionModeRequest): Promise<SetSessionModeResponse> {
@@ -1911,6 +1984,20 @@ export class AcpAgent implements Agent {
1911
1984
  else this.#tearingDown.delete(id);
1912
1985
  }
1913
1986
 
1987
+ #enqueueLifecycleOperation<T>(id: string, operation: () => Promise<T>): Promise<T> {
1988
+ const previous = this.#lifecycleOperations.get(id) ?? Promise.resolve();
1989
+ const result = previous.catch(() => undefined).then(operation);
1990
+ const settled = result.then(
1991
+ () => undefined,
1992
+ () => undefined,
1993
+ );
1994
+ this.#lifecycleOperations.set(id, settled);
1995
+ void settled.finally(() => {
1996
+ if (this.#lifecycleOperations.get(id) === settled) this.#lifecycleOperations.delete(id);
1997
+ });
1998
+ return result;
1999
+ }
2000
+
1914
2001
  #lifecycleIdempotencyKey(id: string, operation: "session.close" | "session.delete"): string {
1915
2002
  return `acp:${operation}:${id}`;
1916
2003
  }
@@ -2135,6 +2222,7 @@ export class AcpAgent implements Agent {
2135
2222
  this.#pendingRouterAdapters.delete(id);
2136
2223
  this.#pendingRouterFrames.delete(id);
2137
2224
  this.#knownSessionCwds.set(id, cwd);
2225
+ this.#ownedSessionIds.add(id);
2138
2226
  await applyAcpPermissionMode(adapter, this.#clientCapabilities);
2139
2227
  this.#assertSessionEpoch(id, epoch);
2140
2228
  // A successful attachment establishes a new live-owner epoch. Any locator
@@ -2151,6 +2239,7 @@ export class AcpAgent implements Agent {
2151
2239
  await this.#teardownSession(id, "attachment failed", false);
2152
2240
  } finally {
2153
2241
  this.#knownSessionCwds.delete(id);
2242
+ this.#ownedSessionIds.delete(id);
2154
2243
  this.#knownSessionMcpServers.delete(id);
2155
2244
  }
2156
2245
  } else if (adapter) {
@@ -2216,6 +2305,7 @@ export class AcpAgent implements Agent {
2216
2305
  async #discardNewSession(id: string): Promise<void> {
2217
2306
  await this.#teardownSession(id, "discarded", true);
2218
2307
  this.#knownSessionCwds.delete(id);
2308
+ this.#ownedSessionIds.delete(id);
2219
2309
  this.#knownSessionMcpServers.delete(id);
2220
2310
  this.#knownSessionMetadata.delete(id);
2221
2311
  }
@@ -2229,6 +2319,7 @@ export class AcpAgent implements Agent {
2229
2319
  if (attaching) await Promise.allSettled([attaching.task]);
2230
2320
  await this.#teardownSession(id, "closed", true);
2231
2321
  this.#knownSessionCwds.delete(id);
2322
+ this.#ownedSessionIds.delete(id);
2232
2323
  this.#knownSessionMcpServers.delete(id);
2233
2324
  this.#knownSessionMetadata.delete(id);
2234
2325
  return {};
@@ -2244,7 +2335,7 @@ export class AcpAgent implements Agent {
2244
2335
  */
2245
2336
  async #teardownSession(id: string, reason: string, closeRemote: boolean): Promise<void> {
2246
2337
  const record = this.#sessions.get(id);
2247
- const ownershipBound = record !== undefined || this.#knownSessionCwds.has(id);
2338
+ const ownershipBound = record !== undefined || this.#ownedSessionIds.has(id);
2248
2339
  this.#beginTeardown(id);
2249
2340
  try {
2250
2341
  this.#advanceSessionEpoch(id);
@@ -2537,6 +2628,10 @@ export class AcpAgent implements Agent {
2537
2628
  if (!received) return;
2538
2629
  const { event, wirePayload } = received;
2539
2630
  const isTerminal = event.type === "agent_end" || event.type === "agent_failed";
2631
+ if (event.type === "notice" && event.source === "autorouting" && typeof event.message === "string") {
2632
+ record.routingInactiveNotice = event.message;
2633
+ return;
2634
+ }
2540
2635
  const derivedCorrelation = sdkFrameCorrelation(frame, event);
2541
2636
  const correlation = derivedCorrelation ?? {};
2542
2637
  const activePrompt = record.activePrompt;
@@ -2848,15 +2943,26 @@ export class AcpAgent implements Agent {
2848
2943
  const record = this.#sessions.get(id);
2849
2944
  if (!record) throw new AcpSdkAdapterError("not_found", `Unknown session, not found: ${id}`);
2850
2945
  const modelPreset = this.#startupOptions?.modelPreset;
2851
- const [config, modelCatalog] = await Promise.all([
2946
+ const [config, sessionCatalog] = await Promise.all([
2852
2947
  record.adapter.query("config.list/get"),
2853
- modelPreset === undefined ? collectModelCatalog(record.adapter) : record.adapter.query("models.profiles.list"),
2948
+ modelPreset === undefined
2949
+ ? collectModelCatalogAndActiveProviders(record.adapter)
2950
+ : record.adapter
2951
+ .query("models.profiles.list")
2952
+ .then((profiles): { modelCatalog: unknown; activeProviders: undefined } => ({
2953
+ modelCatalog: profiles,
2954
+ activeProviders: undefined,
2955
+ })),
2854
2956
  ]);
2855
- // Resolve usable providers in parallel. Only an older session host that
2856
- // rejects `providers.list/active` with `operation_not_session_owned`
2857
- // falls back to the full catalog; operational failures fail closed so
2858
- // the active-provider contract is not silently widened.
2859
- const activeProviders = modelPreset === undefined ? await collectActiveProviderIds(record.adapter) : undefined;
2957
+ // The active-provider walk overlaps the catalog inside the batch above, but
2958
+ // only after the first models.list/current page has finalized host-side
2959
+ // credential state, so catalog rows and provider availability never mix
2960
+ // pre- and post-refresh credential snapshots. Only an older session host
2961
+ // that rejects `providers.list/active` with `operation_not_session_owned`
2962
+ // falls back to the full catalog; operational failures fail closed so the
2963
+ // active-provider contract is not silently widened.
2964
+ const modelCatalog = sessionCatalog.modelCatalog;
2965
+ const activeProviders = sessionCatalog.activeProviders;
2860
2966
  record.authFailure = undefined;
2861
2967
  if (modelPreset !== undefined) {
2862
2968
  const activePreset = configValues(config).get(MODEL_PRESET_CONFIG_KEY);
@@ -3341,6 +3447,24 @@ export class AcpAgent implements Agent {
3341
3447
  record.adapter,
3342
3448
  );
3343
3449
  }
3450
+ // Not consumed on publish: this mirrors authFailure's lifecycle, where a
3451
+ // later load/resume legitimately re-announces the condition. Clearing here
3452
+ // would also lose the warning outright if the publish below rejected, since
3453
+ // the enclosing bootstrap task swallows failures.
3454
+ if (record.routingInactiveNotice) {
3455
+ const message = record.routingInactiveNotice;
3456
+ await this.#publishSessionUpdate(
3457
+ id,
3458
+ {
3459
+ sessionId: id,
3460
+ update: {
3461
+ sessionUpdate: "agent_thought_chunk",
3462
+ content: { type: "text", text: `[warning:autorouting] ${message}\n` },
3463
+ },
3464
+ },
3465
+ record.adapter,
3466
+ );
3467
+ }
3344
3468
  await this.#publishSessionUpdate(
3345
3469
  id,
3346
3470
  {
@@ -3507,11 +3631,12 @@ export class AcpAgent implements Agent {
3507
3631
  this.#attaching.clear();
3508
3632
  this.#resolvingExisting.clear();
3509
3633
  this.#knownSessionCwds.clear();
3634
+ this.#ownedSessionIds.clear();
3510
3635
  this.#knownSessionMcpServers.clear();
3511
3636
  this.#knownSessionMetadata.clear();
3512
3637
  this.#pendingDeleteLocators.clear();
3513
3638
  this.#pendingCloseIdempotencyKeys.clear();
3514
- if (this.#closing.size === 0) this.#closing.clear();
3639
+ if (this.#lifecycleOperations.size === 0) this.#lifecycleOperations.clear();
3515
3640
  this.#tearingDown.clear();
3516
3641
  if (this.#broker) {
3517
3642
  const broker = this.#broker;