@gajae-code/coding-agent 0.11.1 → 0.11.3

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 (413) hide show
  1. package/CHANGELOG.md +64 -0
  2. package/dist/types/cli/config-cli.d.ts +11 -1
  3. package/dist/types/cli/launch-disposition.d.ts +23 -0
  4. package/dist/types/cli/notify-cli.d.ts +1 -0
  5. package/dist/types/cli/session-picker.d.ts +2 -1
  6. package/dist/types/cli/setup-cli.d.ts +1 -1
  7. package/dist/types/commands/mcp-serve.d.ts +33 -0
  8. package/dist/types/commands/mcp.d.ts +1 -1
  9. package/dist/types/commands/notify.d.ts +0 -2
  10. package/dist/types/commands/sdk.d.ts +1 -1
  11. package/dist/types/config/atomic-yaml-patch.d.ts +18 -0
  12. package/dist/types/config/file-lock.d.ts +14 -4
  13. package/dist/types/config/keybindings.d.ts +42 -1
  14. package/dist/types/config/model-auth.d.ts +3 -0
  15. package/dist/types/config/model-bindings-applier.d.ts +13 -0
  16. package/dist/types/config/model-discovery-manager.d.ts +49 -0
  17. package/dist/types/config/model-equivalence.d.ts +20 -0
  18. package/dist/types/config/model-registry.d.ts +16 -15
  19. package/dist/types/config/model-resolver.d.ts +28 -18
  20. package/dist/types/config/models-config-schema.d.ts +11 -0
  21. package/dist/types/config/settings-schema.d.ts +141 -11
  22. package/dist/types/config/settings.d.ts +14 -11
  23. package/dist/types/coordinator-mcp/policy.d.ts +5 -0
  24. package/dist/types/coordinator-mcp/question-gate-codec.d.ts +129 -0
  25. package/dist/types/coordinator-mcp/question-state.d.ts +356 -0
  26. package/dist/types/coordinator-mcp/server.d.ts +4 -0
  27. package/dist/types/discovery/helpers.d.ts +0 -6
  28. package/dist/types/edit/index.d.ts +3 -0
  29. package/dist/types/eval/py/env.d.ts +9 -0
  30. package/dist/types/extensibility/extensions/loader.d.ts +2 -1
  31. package/dist/types/extensibility/extensions/runner.d.ts +2 -2
  32. package/dist/types/extensibility/extensions/types.d.ts +34 -3
  33. package/dist/types/extensibility/hooks/runner.d.ts +1 -1
  34. package/dist/types/extensibility/shared-events.d.ts +2 -0
  35. package/dist/types/gjc-runtime/deep-interview-recorder.d.ts +10 -1
  36. package/dist/types/gjc-runtime/deep-interview-state.d.ts +44 -0
  37. package/dist/types/gjc-runtime/goal-mode-request.d.ts +11 -1
  38. package/dist/types/gjc-runtime/launch-worktree.d.ts +4 -2
  39. package/dist/types/gjc-runtime/linux-proc.d.ts +29 -0
  40. package/dist/types/gjc-runtime/session-resolution.d.ts +2 -2
  41. package/dist/types/gjc-runtime/state-migrations.d.ts +1 -1
  42. package/dist/types/gjc-runtime/state-writer.d.ts +2 -1
  43. package/dist/types/gjc-runtime/team-launch.d.ts +59 -0
  44. package/dist/types/gjc-runtime/team-notify.d.ts +45 -0
  45. package/dist/types/gjc-runtime/team-runtime.d.ts +29 -121
  46. package/dist/types/gjc-runtime/team-store.d.ts +160 -0
  47. package/dist/types/gjc-runtime/team-workers.d.ts +78 -0
  48. package/dist/types/gjc-runtime/tmux-owner-isolation.d.ts +8 -0
  49. package/dist/types/gjc-runtime/ultragoal-change-set.d.ts +13 -0
  50. package/dist/types/gjc-runtime/ultragoal-evidence.d.ts +38 -0
  51. package/dist/types/gjc-runtime/ultragoal-guard.d.ts +7 -0
  52. package/dist/types/gjc-runtime/ultragoal-pipeline.d.ts +23 -0
  53. package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +55 -33
  54. package/dist/types/gjc-runtime/workflow-cli-common.d.ts +8 -0
  55. package/dist/types/gjc-runtime/workflow-manifest.d.ts +3 -0
  56. package/dist/types/goals/continuation-timeout-guard.d.ts +22 -0
  57. package/dist/types/goals/runtime.d.ts +1 -0
  58. package/dist/types/goals/state.d.ts +8 -0
  59. package/dist/types/harness-control-plane/lifecycle-reconciliation.d.ts +17 -0
  60. package/dist/types/harness-control-plane/phase-rollup.d.ts +4 -7
  61. package/dist/types/harness-control-plane/receipt-ingest.d.ts +5 -0
  62. package/dist/types/harness-control-plane/receipt-spool.d.ts +4 -0
  63. package/dist/types/hindsight/state.d.ts +11 -4
  64. package/dist/types/lsp/lspmux.d.ts +5 -3
  65. package/dist/types/lsp/path-trust.d.ts +1 -0
  66. package/dist/types/main.d.ts +4 -2
  67. package/dist/types/memories/storage.d.ts +1 -0
  68. package/dist/types/modes/action-registry.d.ts +29 -0
  69. package/dist/types/modes/components/chrome.d.ts +14 -0
  70. package/dist/types/modes/components/command-palette.d.ts +27 -0
  71. package/dist/types/modes/components/custom-provider-wizard.d.ts +1 -0
  72. package/dist/types/modes/components/index.d.ts +2 -1
  73. package/dist/types/modes/components/pet-selector.d.ts +1 -1
  74. package/dist/types/modes/components/plan-preview-overlay.d.ts +43 -0
  75. package/dist/types/modes/components/queue-mode-selector.d.ts +1 -1
  76. package/dist/types/modes/components/queue-pane.d.ts +12 -0
  77. package/dist/types/modes/components/read-tool-group.d.ts +6 -0
  78. package/dist/types/modes/components/session-observer-overlay.d.ts +3 -4
  79. package/dist/types/modes/components/sessions-dashboard.d.ts +32 -0
  80. package/dist/types/modes/components/settings-selector.d.ts +1 -1
  81. package/dist/types/modes/components/show-images-selector.d.ts +1 -1
  82. package/dist/types/modes/components/status-line/types.d.ts +1 -1
  83. package/dist/types/modes/components/status-line.d.ts +1 -94
  84. package/dist/types/modes/components/tasks-pane.d.ts +15 -0
  85. package/dist/types/modes/components/theme-selector.d.ts +1 -1
  86. package/dist/types/modes/components/tool-execution.d.ts +15 -0
  87. package/dist/types/modes/components/tool-status-header.d.ts +111 -0
  88. package/dist/types/modes/components/transcript-viewer-overlay.d.ts +42 -0
  89. package/dist/types/modes/controllers/goal-mode-controller.d.ts +62 -0
  90. package/dist/types/modes/controllers/input-controller.d.ts +8 -1
  91. package/dist/types/modes/controllers/mode-gate.d.ts +8 -0
  92. package/dist/types/modes/controllers/plan-mode-controller.d.ts +65 -0
  93. package/dist/types/modes/controllers/selector-controller.d.ts +15 -3
  94. package/dist/types/modes/controllers/stt-controller.d.ts +26 -0
  95. package/dist/types/modes/interactive-mode.d.ts +19 -17
  96. package/dist/types/modes/jobs-observer.d.ts +2 -0
  97. package/dist/types/modes/prompt-action-autocomplete.d.ts +4 -0
  98. package/dist/types/modes/shared/agent-wire/deep-interview-gate.d.ts +4 -11
  99. package/dist/types/modes/shared/agent-wire/workflow-gate-broker.d.ts +20 -2
  100. package/dist/types/modes/shared/agent-wire/workflow-gate-types.d.ts +31 -0
  101. package/dist/types/modes/tasks-aggregator.d.ts +39 -0
  102. package/dist/types/modes/transcript-item-registry.d.ts +60 -0
  103. package/dist/types/modes/types.d.ts +20 -13
  104. package/dist/types/modes/utils/ui-helpers.d.ts +2 -2
  105. package/dist/types/runtime-mcp/manager.d.ts +1 -1
  106. package/dist/types/sdk/broker/broker.d.ts +78 -12
  107. package/dist/types/sdk/broker/lifecycle-codec.d.ts +4 -0
  108. package/dist/types/sdk/broker/lifecycle-ledger.d.ts +15 -1
  109. package/dist/types/sdk/broker/lifecycle.d.ts +6 -2
  110. package/dist/types/sdk/broker/process-incarnation.d.ts +2 -0
  111. package/dist/types/sdk/broker/session-index.d.ts +0 -1
  112. package/dist/types/sdk/broker/state-version.d.ts +2 -0
  113. package/dist/types/sdk/bus/chat-effect-journal.d.ts +1 -0
  114. package/dist/types/sdk/bus/config-commands.d.ts +1 -1
  115. package/dist/types/sdk/bus/config.d.ts +11 -0
  116. package/dist/types/sdk/bus/daemon-paths.d.ts +1 -0
  117. package/dist/types/sdk/bus/html-format.d.ts +3 -2
  118. package/dist/types/sdk/bus/index.d.ts +29 -0
  119. package/dist/types/sdk/bus/lifecycle-control-runtime.d.ts +4 -0
  120. package/dist/types/sdk/bus/notification-service.d.ts +20 -0
  121. package/dist/types/sdk/bus/rate-limit-pool.d.ts +13 -2
  122. package/dist/types/sdk/bus/recent-activity.d.ts +21 -12
  123. package/dist/types/sdk/bus/rich-document.d.ts +7 -0
  124. package/dist/types/sdk/bus/rich-render.d.ts +18 -41
  125. package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +9 -4
  126. package/dist/types/sdk/bus/telegram-daemon.d.ts +46 -3
  127. package/dist/types/sdk/bus/threaded-inbound.d.ts +1 -1
  128. package/dist/types/sdk/bus/threaded-render.d.ts +13 -0
  129. package/dist/types/sdk/bus/topic-registry.d.ts +24 -2
  130. package/dist/types/sdk/host/control/operations.d.ts +1 -1
  131. package/dist/types/sdk/host/host.d.ts +2 -0
  132. package/dist/types/sdk/index.d.ts +1 -0
  133. package/dist/types/sdk/session-directory.d.ts +56 -0
  134. package/dist/types/sdk/session.d.ts +2 -0
  135. package/dist/types/session/agent-session.d.ts +82 -47
  136. package/dist/types/session/agent-storage.d.ts +7 -0
  137. package/dist/types/session/fallback-chain-controller.d.ts +10 -2
  138. package/dist/types/session/internal/managed-session-scope.d.ts +102 -0
  139. package/dist/types/session/internal/managed-session-storage.d.ts +38 -0
  140. package/dist/types/session/messages.d.ts +16 -0
  141. package/dist/types/session/session-manager-internal.d.ts +14 -0
  142. package/dist/types/session/session-manager.d.ts +82 -12
  143. package/dist/types/session/session-storage.d.ts +43 -3
  144. package/dist/types/session/volatile-context-pruning.d.ts +15 -0
  145. package/dist/types/skill-state/{deep-interview-mutation-guard.d.ts → workflow-mutation-guard.d.ts} +24 -8
  146. package/dist/types/skill-state/workflow-state-contract.d.ts +1 -3
  147. package/dist/types/slash-commands/builtin-registry.d.ts +4 -1
  148. package/dist/types/system-prompt.d.ts +2 -11
  149. package/dist/types/task/executor.d.ts +3 -1
  150. package/dist/types/thinking.d.ts +7 -0
  151. package/dist/types/tool-discovery/tool-index.d.ts +1 -50
  152. package/dist/types/tools/ask.d.ts +64 -2
  153. package/dist/types/tools/bash.d.ts +3 -0
  154. package/dist/types/tools/browser/dead-tab-recovery.d.ts +24 -0
  155. package/dist/types/tools/browser/tab-supervisor.d.ts +21 -0
  156. package/dist/types/tools/cron.d.ts +2 -0
  157. package/dist/types/tools/fetch.d.ts +1 -0
  158. package/dist/types/tools/index.d.ts +30 -15
  159. package/dist/types/tools/output-meta.d.ts +1 -0
  160. package/dist/types/tools/read.d.ts +5 -0
  161. package/dist/types/tools/search-tool-bm25.d.ts +3 -4
  162. package/dist/types/tools/subagent.d.ts +1 -0
  163. package/dist/types/utils/event-bus.d.ts +1 -1
  164. package/dist/types/utils/file-mentions.d.ts +3 -3
  165. package/dist/types/web/search/index.d.ts +3 -1
  166. package/package.json +21 -9
  167. package/scripts/build-sdk-package-smoke.ts +34 -1
  168. package/scripts/generate-hotkeys-docs.ts +74 -0
  169. package/scripts/generate-sdk-operation-inventory.ts +64 -20
  170. package/scripts/verify-gjc-sdk-canonicalization.ts +182 -2
  171. package/src/async/job-manager.ts +49 -6
  172. package/src/cli/config-cli.ts +75 -2
  173. package/src/cli/launch-disposition.ts +42 -0
  174. package/src/cli/notify-cli.ts +92 -26
  175. package/src/cli/session-picker.ts +20 -3
  176. package/src/cli/setup-cli.ts +1 -1
  177. package/src/cli.ts +1 -1
  178. package/src/commands/config.ts +1 -1
  179. package/src/commands/harness.ts +34 -106
  180. package/src/commands/launch.ts +6 -4
  181. package/src/commands/mcp-serve.ts +85 -8
  182. package/src/commands/mcp.ts +2 -2
  183. package/src/commands/notify.ts +8 -4
  184. package/src/commands/sdk.ts +61 -40
  185. package/src/commands/ultragoal.ts +4 -1
  186. package/src/config/atomic-yaml-patch.ts +80 -13
  187. package/src/config/file-lock.ts +105 -43
  188. package/src/config/keybindings.ts +64 -20
  189. package/src/config/model-auth.ts +9 -0
  190. package/src/config/model-bindings-applier.ts +139 -0
  191. package/src/config/model-discovery-manager.ts +224 -0
  192. package/src/config/model-equivalence.ts +66 -0
  193. package/src/config/model-registry.ts +169 -302
  194. package/src/config/model-resolver.ts +361 -369
  195. package/src/config/models-config-schema.ts +5 -4
  196. package/src/config/settings-schema.ts +214 -12
  197. package/src/config/settings.ts +458 -482
  198. package/src/coordinator-mcp/policy.ts +76 -11
  199. package/src/coordinator-mcp/question-gate-codec.ts +274 -0
  200. package/src/coordinator-mcp/question-state.ts +778 -0
  201. package/src/coordinator-mcp/server.ts +1702 -238
  202. package/src/debug/index.ts +1 -1
  203. package/src/defaults/gjc/skills/deep-interview/SKILL.md +13 -1
  204. package/src/defaults/gjc/skills/team/SKILL.md +16 -0
  205. package/src/discovery/helpers.ts +1 -50
  206. package/src/edit/index.ts +64 -0
  207. package/src/eval/py/env.ts +26 -0
  208. package/src/eval/py/kernel.ts +5 -3
  209. package/src/extensibility/extensions/loader.ts +9 -1
  210. package/src/extensibility/extensions/runner.ts +12 -3
  211. package/src/extensibility/extensions/types.ts +39 -1
  212. package/src/extensibility/gjc-plugins/installer.ts +16 -16
  213. package/src/extensibility/hooks/runner.ts +2 -2
  214. package/src/extensibility/runtime-skill-discovery.ts +33 -13
  215. package/src/extensibility/shared-events.ts +2 -0
  216. package/src/gjc-runtime/deep-interview-recorder.ts +63 -3
  217. package/src/gjc-runtime/deep-interview-runtime.ts +76 -27
  218. package/src/gjc-runtime/deep-interview-state.ts +317 -5
  219. package/src/gjc-runtime/goal-mode-request.ts +55 -8
  220. package/src/gjc-runtime/launch-tmux.ts +2 -5
  221. package/src/gjc-runtime/launch-worktree.ts +5 -5
  222. package/src/gjc-runtime/linux-proc.ts +71 -0
  223. package/src/gjc-runtime/ralplan-runtime.ts +74 -90
  224. package/src/gjc-runtime/session-resolution.ts +18 -3
  225. package/src/gjc-runtime/session-state-sidecar.ts +6 -107
  226. package/src/gjc-runtime/state-migrations.ts +1 -1
  227. package/src/gjc-runtime/state-runtime.ts +327 -249
  228. package/src/gjc-runtime/state-writer.ts +36 -40
  229. package/src/gjc-runtime/team-launch.ts +319 -0
  230. package/src/gjc-runtime/team-notify.ts +326 -0
  231. package/src/gjc-runtime/team-runtime.ts +1099 -1482
  232. package/src/gjc-runtime/team-store.ts +828 -0
  233. package/src/gjc-runtime/team-workers.ts +962 -0
  234. package/src/gjc-runtime/tmux-owner-isolation-cli.ts +2 -11
  235. package/src/gjc-runtime/tmux-owner-isolation.ts +14 -9
  236. package/src/gjc-runtime/tmux-sessions.ts +4 -21
  237. package/src/gjc-runtime/ultragoal-change-set.ts +172 -0
  238. package/src/gjc-runtime/ultragoal-evidence.ts +874 -0
  239. package/src/gjc-runtime/ultragoal-guard.ts +29 -5
  240. package/src/gjc-runtime/ultragoal-pipeline.ts +306 -0
  241. package/src/gjc-runtime/ultragoal-runtime.ts +84 -1311
  242. package/src/gjc-runtime/workflow-cli-common.ts +30 -0
  243. package/src/gjc-runtime/workflow-manifest.generated.json +58 -0
  244. package/src/gjc-runtime/workflow-manifest.ts +18 -0
  245. package/src/goals/continuation-timeout-guard.ts +79 -0
  246. package/src/goals/runtime.ts +7 -5
  247. package/src/goals/state.ts +18 -0
  248. package/src/harness-control-plane/lifecycle-reconciliation.ts +64 -0
  249. package/src/harness-control-plane/phase-rollup.ts +4 -7
  250. package/src/harness-control-plane/receipt-ingest.ts +5 -0
  251. package/src/harness-control-plane/receipt-spool.ts +51 -7
  252. package/src/harness-control-plane/session-lease.ts +12 -130
  253. package/src/hindsight/backend.ts +26 -27
  254. package/src/hindsight/state.ts +101 -32
  255. package/src/hooks/skill-state.ts +17 -11
  256. package/src/internal-urls/docs-index.generated.ts +21 -18
  257. package/src/lsp/client.ts +1 -1
  258. package/src/lsp/config.ts +145 -26
  259. package/src/lsp/index.ts +1 -1
  260. package/src/lsp/lspmux.ts +39 -10
  261. package/src/lsp/path-trust.ts +81 -0
  262. package/src/main.ts +127 -30
  263. package/src/memories/index.ts +1 -0
  264. package/src/memories/storage.ts +6 -2
  265. package/src/modes/action-registry.ts +165 -0
  266. package/src/modes/components/chrome.ts +40 -0
  267. package/src/modes/components/command-palette.ts +168 -0
  268. package/src/modes/components/custom-editor.ts +1 -1
  269. package/src/modes/components/custom-provider-wizard.ts +51 -14
  270. package/src/modes/components/index.ts +2 -1
  271. package/src/modes/components/pet-selector.ts +11 -17
  272. package/src/modes/components/plan-preview-overlay.ts +295 -0
  273. package/src/modes/components/queue-mode-selector.ts +10 -27
  274. package/src/modes/components/queue-pane.ts +79 -0
  275. package/src/modes/components/read-tool-group.ts +13 -0
  276. package/src/modes/components/session-observer-overlay.ts +207 -772
  277. package/src/modes/components/sessions-dashboard.ts +181 -0
  278. package/src/modes/components/settings-selector.ts +1 -1
  279. package/src/modes/components/show-images-selector.ts +10 -24
  280. package/src/modes/components/status-line/types.ts +1 -1
  281. package/src/modes/components/status-line.ts +1 -969
  282. package/src/modes/components/tasks-pane.ts +73 -0
  283. package/src/modes/components/theme-selector.ts +11 -31
  284. package/src/modes/components/tool-execution.ts +22 -0
  285. package/src/modes/components/tool-status-header.ts +1056 -0
  286. package/src/modes/components/transcript-viewer-overlay.ts +384 -0
  287. package/src/modes/controllers/btw-controller.ts +1 -0
  288. package/src/modes/controllers/event-controller.ts +3 -2
  289. package/src/modes/controllers/extension-ui-controller.ts +24 -1
  290. package/src/modes/controllers/goal-mode-controller.ts +474 -0
  291. package/src/modes/controllers/input-controller.ts +764 -229
  292. package/src/modes/controllers/mode-gate.ts +24 -0
  293. package/src/modes/controllers/plan-mode-controller.ts +517 -0
  294. package/src/modes/controllers/selector-controller.ts +225 -10
  295. package/src/modes/controllers/stt-controller.ts +125 -0
  296. package/src/modes/interactive-mode.ts +289 -1150
  297. package/src/modes/jobs-observer.ts +3 -0
  298. package/src/modes/prompt-action-autocomplete.ts +7 -0
  299. package/src/modes/runtime-init.ts +8 -0
  300. package/src/modes/shared/agent-wire/deep-interview-gate.ts +31 -106
  301. package/src/modes/shared/agent-wire/workflow-gate-broker.ts +71 -16
  302. package/src/modes/shared/agent-wire/workflow-gate-types.ts +177 -0
  303. package/src/modes/tasks-aggregator.ts +199 -0
  304. package/src/modes/transcript-item-registry.ts +229 -0
  305. package/src/modes/types.ts +45 -18
  306. package/src/modes/utils/ui-helpers.ts +17 -9
  307. package/src/prompts/agent-fragments/ralplan-persistence.md +6 -0
  308. package/src/prompts/agent-fragments/restricted-bash.md +1 -0
  309. package/src/prompts/agents/architect.md +2 -8
  310. package/src/prompts/agents/critic.md +2 -8
  311. package/src/prompts/agents/executor.md +3 -0
  312. package/src/prompts/agents/planner.md +4 -12
  313. package/src/prompts/system/custom-system-prompt.md +1 -23
  314. package/src/prompts/system/plan-mode-approved.md +8 -0
  315. package/src/prompts/system/system-prompt.md +17 -96
  316. package/src/prompts/tools/bash.md +4 -4
  317. package/src/prompts/tools/browser.md +15 -52
  318. package/src/prompts/tools/find.md +2 -2
  319. package/src/prompts/tools/hashline.md +13 -55
  320. package/src/prompts/tools/read.md +3 -6
  321. package/src/prompts/tools/search-tool-bm25.md +0 -2
  322. package/src/prompts/tools/search.md +2 -4
  323. package/src/prompts/tools/skill-discovery.md +1 -2
  324. package/src/prompts/tools/task.md +1 -1
  325. package/src/runtime-mcp/manager.ts +1 -1
  326. package/src/sdk/broker/broker.ts +206 -78
  327. package/src/sdk/broker/discovery.ts +1 -1
  328. package/src/sdk/broker/ensure.ts +3 -1
  329. package/src/sdk/broker/lifecycle-codec.ts +11 -0
  330. package/src/sdk/broker/lifecycle-ledger.ts +371 -74
  331. package/src/sdk/broker/lifecycle.ts +1399 -142
  332. package/src/sdk/broker/process-incarnation.ts +5 -13
  333. package/src/sdk/broker/session-index.ts +145 -56
  334. package/src/sdk/broker/state-version.ts +21 -0
  335. package/src/sdk/broker/transport.ts +1 -1
  336. package/src/sdk/bus/chat-daemon-cli.ts +2 -0
  337. package/src/sdk/bus/chat-daemon-control.ts +8 -28
  338. package/src/sdk/bus/chat-effect-journal.ts +10 -2
  339. package/src/sdk/bus/config-commands.ts +5 -4
  340. package/src/sdk/bus/config.ts +98 -0
  341. package/src/sdk/bus/conversation-store.ts +5 -29
  342. package/src/sdk/bus/daemon-paths.ts +2 -0
  343. package/src/sdk/bus/html-format.ts +20 -3
  344. package/src/sdk/bus/index.ts +782 -94
  345. package/src/sdk/bus/lifecycle-control-runtime.ts +91 -33
  346. package/src/sdk/bus/natives-augment.d.ts +26 -1
  347. package/src/sdk/bus/notification-service.ts +168 -20
  348. package/src/sdk/bus/rate-limit-pool.ts +58 -14
  349. package/src/sdk/bus/recent-activity.ts +188 -74
  350. package/src/sdk/bus/rich-document.ts +258 -0
  351. package/src/sdk/bus/rich-render.ts +62 -70
  352. package/src/sdk/bus/telegram-daemon-cli.ts +15 -59
  353. package/src/sdk/bus/telegram-daemon-contract.ts +9 -4
  354. package/src/sdk/bus/telegram-daemon.ts +1389 -237
  355. package/src/sdk/bus/threaded-inbound.ts +8 -3
  356. package/src/sdk/bus/threaded-render.ts +57 -1
  357. package/src/sdk/bus/topic-registry.ts +120 -13
  358. package/src/sdk/host/control/dispatch.ts +14 -2
  359. package/src/sdk/host/control/operations.ts +6 -1
  360. package/src/sdk/host/host.ts +44 -2
  361. package/src/sdk/host/query/handlers.ts +20 -22
  362. package/src/sdk/host/query/revision-store.ts +80 -57
  363. package/src/sdk/index.ts +1 -0
  364. package/src/sdk/protocol/operation-inventory.generated.json +943 -1907
  365. package/src/sdk/session-directory.ts +141 -0
  366. package/src/sdk/session.ts +462 -480
  367. package/src/session/agent-session.ts +1700 -583
  368. package/src/session/agent-storage.ts +18 -0
  369. package/src/session/cache-economics.ts +25 -8
  370. package/src/session/fallback-chain-controller.ts +53 -3
  371. package/src/session/internal/managed-session-scope.ts +2226 -0
  372. package/src/session/internal/managed-session-storage.ts +453 -0
  373. package/src/session/messages.ts +91 -2
  374. package/src/session/session-manager-internal.ts +33 -0
  375. package/src/session/session-manager.ts +1112 -333
  376. package/src/session/session-storage.ts +444 -39
  377. package/src/session/volatile-context-pruning.ts +78 -0
  378. package/src/setup/credential-auto-import.ts +2 -1
  379. package/src/setup/hermes/templates/operator-instructions.v1.md +1 -1
  380. package/src/setup/provider-onboarding.ts +36 -61
  381. package/src/setup/provider-presets.json +5 -0
  382. package/src/skill-state/active-state.ts +3 -13
  383. package/src/skill-state/{deep-interview-mutation-guard.ts → workflow-mutation-guard.ts} +47 -29
  384. package/src/skill-state/workflow-state-contract.ts +6 -41
  385. package/src/slash-commands/builtin-registry.ts +46 -11
  386. package/src/system-prompt.ts +37 -44
  387. package/src/task/agents.ts +19 -1
  388. package/src/task/executor.ts +90 -6
  389. package/src/task/index.ts +36 -21
  390. package/src/thinking.ts +21 -6
  391. package/src/tool-discovery/tool-index.ts +2 -134
  392. package/src/tools/ask.ts +157 -21
  393. package/src/tools/ast-edit.ts +1 -1
  394. package/src/tools/bash.ts +38 -0
  395. package/src/tools/browser/dead-tab-recovery.ts +78 -0
  396. package/src/tools/browser/tab-supervisor.ts +282 -36
  397. package/src/tools/computer.ts +10 -3
  398. package/src/tools/cron.ts +30 -16
  399. package/src/tools/eval.ts +1 -2
  400. package/src/tools/fetch.ts +11 -4
  401. package/src/tools/index.ts +76 -24
  402. package/src/tools/output-meta.ts +19 -7
  403. package/src/tools/read.ts +219 -64
  404. package/src/tools/resource-gc.ts +2 -2
  405. package/src/tools/search-tool-bm25.ts +11 -74
  406. package/src/tools/subagent.ts +43 -2
  407. package/src/utils/event-bus.ts +9 -4
  408. package/src/utils/file-mentions.ts +4 -4
  409. package/src/web/search/index.ts +3 -3
  410. package/dist/types/discovery/plugin-dir-roots.d.ts +0 -15
  411. package/dist/types/runtime-mcp/discoverable-tool-metadata.d.ts +0 -7
  412. package/src/discovery/plugin-dir-roots.ts +0 -28
  413. package/src/runtime-mcp/discoverable-tool-metadata.ts +0 -25
@@ -114,7 +114,7 @@ import { AgentSession, type ForkContextSeed } from "../session/agent-session";
114
114
  import { resolveAuthBrokerConfig } from "../session/auth-broker-config";
115
115
  import { AuthBrokerClient, AuthStorage, RemoteAuthCredentialStore } from "../session/auth-storage";
116
116
  import { type CustomMessage, convertToLlm } from "../session/messages";
117
- import { SessionManager } from "../session/session-manager";
117
+ import { createReadonlySessionManager, SessionManager } from "../session/session-manager";
118
118
  import { formatNoModelsAvailableFallback } from "../setup/model-onboarding-guidance";
119
119
  import { closeAllConnections } from "../ssh/connection-manager";
120
120
  import { unmountAll } from "../ssh/sshfs-mount";
@@ -126,12 +126,7 @@ import {
126
126
  } from "../system-prompt";
127
127
  import { AgentOutputManager } from "../task/output-manager";
128
128
  import { parseThinkingLevel, resolveThinkingLevelForModel, toReasoningEffort } from "../thinking";
129
- import {
130
- collectDiscoverableTools,
131
- type DiscoverableTool,
132
- isMCPBridgeTool,
133
- isMCPToolName,
134
- } from "../tool-discovery/tool-index";
129
+ import { isMCPBridgeTool } from "../tool-discovery/tool-index";
135
130
  import {
136
131
  applyConfiguredSearchTimeout,
137
132
  BashTool,
@@ -150,7 +145,6 @@ import {
150
145
  loadSshTool,
151
146
  ReadTool,
152
147
  ResolveTool,
153
- renderSearchToolBm25Description,
154
148
  SearchTool,
155
149
  setPreferredImageProvider,
156
150
  setPreferredSearchProvider,
@@ -396,6 +390,8 @@ export interface CreateAgentSessionOptions {
396
390
  hasUI?: boolean;
397
391
  /** Whether this host mode can own a notification session endpoint. Default: true. */
398
392
  notificationHostModeSupported?: boolean;
393
+ /** Whether this host mode can own the root SDK endpoint. Default: true. */
394
+ sdkHostModeSupported?: boolean;
399
395
 
400
396
  /**
401
397
  * Opt-in OpenTelemetry instrumentation forwarded to the underlying Agent.
@@ -498,7 +494,16 @@ export async function discoverAuthStorage(agentDir: string = getDefaultAgentDir(
498
494
  sourceLabel: `broker ${brokerConfig.url}`,
499
495
  credentialRankingMode,
500
496
  });
501
- await storage.reload();
497
+ try {
498
+ await storage.reload();
499
+ } catch (error) {
500
+ try {
501
+ storage.close();
502
+ } catch {
503
+ // Preserve the initial reload failure.
504
+ }
505
+ throw error;
506
+ }
502
507
  return storage;
503
508
  }
504
509
  const dbPath = getAgentDbPath(agentDir);
@@ -507,7 +512,16 @@ export async function discoverAuthStorage(agentDir: string = getDefaultAgentDir(
507
512
  sourceLabel: `local ${dbPath}`,
508
513
  credentialRankingMode,
509
514
  });
510
- await storage.reload();
515
+ try {
516
+ await storage.reload();
517
+ } catch (error) {
518
+ try {
519
+ storage.close();
520
+ } catch {
521
+ // Preserve the initial reload failure.
522
+ }
523
+ throw error;
524
+ }
511
525
  return storage;
512
526
  }
513
527
 
@@ -629,9 +643,6 @@ function isCustomTool(tool: CustomTool | ToolDefinition): tool is CustomTool {
629
643
 
630
644
  const TOOL_DEFINITION_MARKER = Symbol("__isToolDefinition");
631
645
 
632
- /** Matches the truncation applied to per-server instructions inside `rebuildSystemPrompt`. */
633
- const MAX_MCP_INSTRUCTIONS_LENGTH = 4000;
634
-
635
646
  let sshCleanupRegistered = false;
636
647
 
637
648
  async function cleanupSshResources(): Promise<void> {
@@ -976,6 +987,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
976
987
  // Pin authStorage to modelRegistry.authStorage: ModelRegistry.getApiKey() routes refresh
977
988
  // failures through that instance, so any divergent storage handed to the bridge / mcpManager
978
989
  // / session would silently miss credential_disabled events.
990
+ const ownsAuthStorage = options.modelRegistry === undefined && options.authStorage === undefined;
979
991
  const modelRegistry =
980
992
  options.modelRegistry ??
981
993
  new ModelRegistry(options.authStorage ?? (await logger.time("discoverModels", discoverAuthStorage, agentDir)));
@@ -985,409 +997,424 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
985
997
  "options.authStorage and options.modelRegistry.authStorage must be the same instance when both are provided",
986
998
  );
987
999
  }
988
- // Subscribe before any getApiKey() call so startup model probes can't fire a
989
- // credential_disabled event past us. An embedder's constructor handler makes the
990
- // listener set non-empty from construction, which defeats AuthStorage's no-listener
991
- // buffer — so we can't rely on it to catch startup events for the extension runner.
992
- const startupCredentialDisabledEvents: CredentialDisabledEvent[] = [];
993
- let credentialDisabledTarget: ExtensionRunner | undefined;
994
- let unsubscribeCredentialDisabled: (() => void) | undefined = authStorage.onCredentialDisabled(event => {
995
- if (credentialDisabledTarget) {
996
- // Discard return: any handler error is routed through runner.onError listeners.
997
- void credentialDisabledTarget.emitCredentialDisabled(event);
998
- } else {
999
- startupCredentialDisabledEvents.push(event);
1000
- }
1001
- });
1002
- let runtimeCredentialSelectorInstalled = false;
1003
- const installRuntimeCredentialSelector = (provider: string): void => {
1004
- if (!options.credentialSelector || runtimeCredentialSelectorInstalled) return;
1005
- authStorage.setRuntimeCredentialSelector(provider, options.credentialSelector.selector);
1006
- runtimeCredentialSelectorInstalled = true;
1000
+ let agent: Agent;
1001
+ let session!: AgentSession;
1002
+ let hasSession = false;
1003
+ let hasRegistered = false;
1004
+ let cleanupOwnedMcpManager: (() => Promise<void>) | undefined;
1005
+ const agentRegistry = options.agentRegistry ?? AgentRegistry.global();
1006
+ const resolvedAgentId = options.agentId ?? options.parentTaskPrefix ?? MAIN_AGENT_ID;
1007
+ const resolvedAgentDisplayName = options.agentDisplayName ?? (isCanonicalSubSession ? "sub" : "main");
1008
+ const resolvedAgentRosterLabel = resolveAgentRosterLabel(
1009
+ options.agentRosterLabel,
1010
+ resolvedAgentId,
1011
+ resolvedAgentDisplayName,
1012
+ );
1013
+ const evalKernelOwnerId = `agent-session:${Snowflake.next()}`;
1014
+ let disposeLocalProtocolOverride: (() => void) | undefined;
1015
+ let localProtocolOverrideReleased = false;
1016
+ const releaseLocalProtocolOverride = (): void => {
1017
+ if (localProtocolOverrideReleased) return;
1018
+ localProtocolOverrideReleased = true;
1019
+ disposeLocalProtocolOverride?.();
1020
+ };
1021
+ const enableLsp = options.enableLsp ?? true;
1022
+ let authStorageClosed = false;
1023
+ const closeOwnedAuthStorage = (): void => {
1024
+ if (!ownsAuthStorage || authStorageClosed) return;
1025
+ authStorageClosed = true;
1026
+ authStorage.close();
1027
+ };
1028
+ let unsubscribeCredentialDisabled: (() => void) | undefined;
1029
+ const releaseCredentialDisabledSubscription = (): void => {
1030
+ const unsubscribe = unsubscribeCredentialDisabled;
1031
+ unsubscribeCredentialDisabled = undefined;
1032
+ unsubscribe?.();
1007
1033
  };
1008
- const earlyCredentialSelectorProvider = options.credentialSelector?.provider ?? options.model?.provider;
1009
- if (earlyCredentialSelectorProvider) {
1010
- installRuntimeCredentialSelector(earlyCredentialSelectorProvider);
1011
- }
1012
- const settings = options.settings ?? (await logger.time("settings", Settings.init, { cwd, agentDir }));
1013
- modelRegistry.applyConfiguredModelBindings(settings);
1014
- logger.time("initializeWithSettings", initializeWithSettings, settings);
1015
- const canRefreshModelsBeforeCredentialSelector =
1016
- !options.credentialSelector || runtimeCredentialSelectorInstalled || options.modelRegistry !== undefined;
1017
- if (!options.modelRegistry && canRefreshModelsBeforeCredentialSelector) {
1018
- modelRegistry.refreshInBackground();
1019
- }
1020
- // Kick off workspace tree discovery early. The native workspace scan returns
1021
- // both the rendered-tree input and the AGENTS.md directory-context index, so
1022
- // startup does not perform a second recursive filesystem search. Subagents
1023
- // inherit the parent's resolved values via options.
1024
- const STARTUP_SCAN_DEADLINE_MS = 5000;
1025
- const workspaceTreePromise: Promise<WorkspaceTree> = options.workspaceTree
1026
- ? Promise.resolve(options.workspaceTree)
1027
- : logger.time("buildWorkspaceTree", () => buildWorkspaceTree(cwd, { timeoutMs: STARTUP_SCAN_DEADLINE_MS }));
1028
- workspaceTreePromise.catch(() => {});
1029
-
1030
- // Independent discoveries that depend only on cwd/agentDir — kicked off in parallel and awaited
1031
- // at their respective consumer sites. Their work can overlap with model resolution, secret loading,
1032
- // session-context build, tool creation, MCP discovery, and extension discovery.
1033
- const contextFilesPromise = options.contextFiles
1034
- ? Promise.resolve(options.contextFiles)
1035
- : logger.time("discoverContextFiles", discoverContextFiles, cwd, agentDir);
1036
- contextFilesPromise.catch(() => {});
1037
- const promptTemplatesPromise = options.promptTemplates
1038
- ? Promise.resolve(options.promptTemplates)
1039
- : logger.time("discoverPromptTemplates", discoverPromptTemplates, cwd, agentDir);
1040
- promptTemplatesPromise.catch(() => {});
1041
- const slashCommandsPromise = options.slashCommands ? Promise.resolve(options.slashCommands) : Promise.resolve([]);
1042
- slashCommandsPromise.catch(() => {});
1043
-
1044
- // Initialize provider preferences from settings
1045
- const webSearchProvider = getConfiguredSearchProviderPreference(settings);
1046
- setPreferredSearchProvider(webSearchProvider);
1047
- const webSearchFallback = settings.get("web_search.fallback");
1048
- if (Array.isArray(webSearchFallback)) {
1049
- setSearchFallbackProviders(
1050
- webSearchFallback.filter(value => typeof value === "string" && isConfigurableSearchProviderId(value)),
1051
- );
1052
- }
1053
- applyConfiguredSearchTimeout(settings);
1054
-
1055
- const imageProvider = settings.get("providers.image");
1056
- if (
1057
- imageProvider === "auto" ||
1058
- imageProvider === "openai" ||
1059
- imageProvider === "gemini" ||
1060
- imageProvider === "openrouter" ||
1061
- imageProvider === "antigravity"
1062
- ) {
1063
- setPreferredImageProvider(imageProvider);
1064
- }
1065
1034
 
1066
- const sessionManager =
1067
- options.sessionManager ??
1068
- logger.time("sessionManager", () =>
1069
- SessionManager.create(cwd, SessionManager.getDefaultSessionDir(cwd, agentDir)),
1070
- );
1071
- const logicalSessionId = sessionManager.getSessionId();
1072
- const providerSessionId = options.providerSessionId ?? options.forkContextSeed?.cacheIdentity ?? logicalSessionId;
1073
- const modelApiKeyAvailability = new Map<string, boolean>();
1074
- const getModelAvailabilityKey = (candidate: Model): string =>
1075
- `${candidate.provider}\u0000${candidate.baseUrl ?? ""}`;
1076
- const hasModelApiKey = async (candidate: Model): Promise<boolean> => {
1077
- const availabilityKey = getModelAvailabilityKey(candidate);
1078
- const cached = modelApiKeyAvailability.get(availabilityKey);
1079
- if (cached !== undefined) {
1080
- return cached;
1035
+ try {
1036
+ // Subscribe before any getApiKey() call so startup model probes can't fire a
1037
+ // credential_disabled event past us. An embedder's constructor handler makes the
1038
+ // listener set non-empty from construction, which defeats AuthStorage's no-listener
1039
+ // buffer — so we can't rely on it to catch startup events for the extension runner.
1040
+ const startupCredentialDisabledEvents: CredentialDisabledEvent[] = [];
1041
+ let credentialDisabledTarget: ExtensionRunner | undefined;
1042
+ unsubscribeCredentialDisabled = authStorage.onCredentialDisabled(event => {
1043
+ if (credentialDisabledTarget) {
1044
+ // Discard return: any handler error is routed through runner.onError listeners.
1045
+ void credentialDisabledTarget.emitCredentialDisabled(event);
1046
+ } else {
1047
+ startupCredentialDisabledEvents.push(event);
1048
+ }
1049
+ });
1050
+ let runtimeCredentialSelectorInstalled = false;
1051
+ const installRuntimeCredentialSelector = (provider: string): void => {
1052
+ if (!options.credentialSelector || runtimeCredentialSelectorInstalled) return;
1053
+ authStorage.setRuntimeCredentialSelector(provider, options.credentialSelector.selector);
1054
+ runtimeCredentialSelectorInstalled = true;
1055
+ };
1056
+ const earlyCredentialSelectorProvider = options.credentialSelector?.provider ?? options.model?.provider;
1057
+ if (earlyCredentialSelectorProvider) {
1058
+ installRuntimeCredentialSelector(earlyCredentialSelectorProvider);
1081
1059
  }
1060
+ const settings = options.settings ?? (await logger.time("settings", Settings.init, { cwd, agentDir }));
1061
+ modelRegistry.applyConfiguredModelBindings(settings);
1062
+ logger.time("initializeWithSettings", initializeWithSettings, settings);
1063
+ const canRefreshModelsBeforeCredentialSelector =
1064
+ !options.credentialSelector || runtimeCredentialSelectorInstalled || options.modelRegistry !== undefined;
1065
+ if (!options.modelRegistry && canRefreshModelsBeforeCredentialSelector) {
1066
+ modelRegistry.refreshInBackground();
1067
+ }
1068
+ // Kick off workspace tree discovery early. The native workspace scan returns
1069
+ // both the rendered-tree input and the AGENTS.md directory-context index, so
1070
+ // startup does not perform a second recursive filesystem search. Subagents
1071
+ // inherit the parent's resolved values via options.
1072
+ const STARTUP_SCAN_DEADLINE_MS = 5000;
1073
+ const workspaceTreePromise: Promise<WorkspaceTree> = options.workspaceTree
1074
+ ? Promise.resolve(options.workspaceTree)
1075
+ : logger.time("buildWorkspaceTree", () => buildWorkspaceTree(cwd, { timeoutMs: STARTUP_SCAN_DEADLINE_MS }));
1076
+ workspaceTreePromise.catch(() => {});
1077
+
1078
+ // Independent discoveries that depend only on cwd/agentDir — kicked off in parallel and awaited
1079
+ // at their respective consumer sites. Their work can overlap with model resolution, secret loading,
1080
+ // session-context build, tool creation, MCP discovery, and extension discovery.
1081
+ const contextFilesPromise = options.contextFiles
1082
+ ? Promise.resolve(options.contextFiles)
1083
+ : logger.time("discoverContextFiles", discoverContextFiles, cwd, agentDir);
1084
+ contextFilesPromise.catch(() => {});
1085
+ const promptTemplatesPromise = options.promptTemplates
1086
+ ? Promise.resolve(options.promptTemplates)
1087
+ : logger.time("discoverPromptTemplates", discoverPromptTemplates, cwd, agentDir);
1088
+ promptTemplatesPromise.catch(() => {});
1089
+ const slashCommandsPromise = options.slashCommands ? Promise.resolve(options.slashCommands) : Promise.resolve([]);
1090
+ slashCommandsPromise.catch(() => {});
1091
+
1092
+ // Initialize provider preferences from settings
1093
+ const webSearchProvider = getConfiguredSearchProviderPreference(settings);
1094
+ setPreferredSearchProvider(webSearchProvider);
1095
+ const webSearchFallback = settings.get("web_search.fallback");
1096
+ if (Array.isArray(webSearchFallback)) {
1097
+ setSearchFallbackProviders(
1098
+ webSearchFallback.filter(value => typeof value === "string" && isConfigurableSearchProviderId(value)),
1099
+ );
1100
+ }
1101
+ applyConfiguredSearchTimeout(settings);
1082
1102
 
1083
- const credentialSelector =
1084
- options.credentialSelector && !runtimeCredentialSelectorInstalled
1085
- ? options.credentialSelector.provider === undefined ||
1086
- options.credentialSelector.provider === candidate.provider
1087
- ? options.credentialSelector.selector
1088
- : undefined
1089
- : undefined;
1090
- if (options.credentialSelector?.provider && options.credentialSelector.provider !== candidate.provider) {
1091
- modelApiKeyAvailability.set(availabilityKey, false);
1092
- return false;
1103
+ const imageProvider = settings.get("providers.image");
1104
+ if (
1105
+ imageProvider === "auto" ||
1106
+ imageProvider === "openai" ||
1107
+ imageProvider === "gemini" ||
1108
+ imageProvider === "openrouter" ||
1109
+ imageProvider === "antigravity"
1110
+ ) {
1111
+ setPreferredImageProvider(imageProvider);
1093
1112
  }
1094
- const key = await modelRegistry.getApiKey(candidate, providerSessionId, { credentialSelector }).catch(error => {
1095
- if (credentialSelector) {
1096
- logger.debug("Credential selector did not match model availability candidate", {
1097
- provider: candidate.provider,
1098
- model: candidate.id,
1099
- error: error instanceof Error ? error.message : String(error),
1100
- });
1101
- return undefined;
1113
+
1114
+ const sessionManager =
1115
+ options.sessionManager ??
1116
+ (await logger.time("sessionManager", async () => {
1117
+ const sessionDir = SessionManager.getDefaultSessionDir(cwd, agentDir);
1118
+ return SessionManager.create(cwd, sessionDir);
1119
+ }));
1120
+ const logicalSessionId = sessionManager.getSessionId();
1121
+ const providerSessionId = options.providerSessionId ?? options.forkContextSeed?.cacheIdentity ?? logicalSessionId;
1122
+ const modelApiKeyAvailability = new Map<string, boolean>();
1123
+ const getModelAvailabilityKey = (candidate: Model): string =>
1124
+ `${candidate.provider}\u0000${candidate.baseUrl ?? ""}`;
1125
+ const hasModelApiKey = async (candidate: Model): Promise<boolean> => {
1126
+ const availabilityKey = getModelAvailabilityKey(candidate);
1127
+ const cached = modelApiKeyAvailability.get(availabilityKey);
1128
+ if (cached !== undefined) {
1129
+ return cached;
1102
1130
  }
1103
- throw error;
1104
- });
1105
- const hasKey = Boolean(key) && (!credentialSelector || key !== kNoAuth);
1106
- modelApiKeyAvailability.set(availabilityKey, hasKey);
1107
- return hasKey;
1108
- };
1109
1131
 
1110
- // Load and create secret obfuscator early so resumed session state and prompt warnings
1111
- // reflect actual loaded secrets, not just the setting toggle.
1112
- let obfuscator: SecretObfuscator | undefined;
1113
- if (settings.get("secrets.enabled")) {
1114
- const fileEntries = await logger.time("loadSecrets", loadSecrets, cwd, agentDir);
1115
- const envEntries = collectEnvSecrets();
1116
- const allEntries = [...envEntries, ...fileEntries];
1117
- if (allEntries.length > 0) {
1118
- obfuscator = new SecretObfuscator(allEntries);
1132
+ const credentialSelector =
1133
+ options.credentialSelector && !runtimeCredentialSelectorInstalled
1134
+ ? options.credentialSelector.provider === undefined ||
1135
+ options.credentialSelector.provider === candidate.provider
1136
+ ? options.credentialSelector.selector
1137
+ : undefined
1138
+ : undefined;
1139
+ if (options.credentialSelector?.provider && options.credentialSelector.provider !== candidate.provider) {
1140
+ modelApiKeyAvailability.set(availabilityKey, false);
1141
+ return false;
1142
+ }
1143
+ const key = await modelRegistry
1144
+ .getApiKey(candidate, providerSessionId, { credentialSelector })
1145
+ .catch(error => {
1146
+ if (credentialSelector) {
1147
+ logger.debug("Credential selector did not match model availability candidate", {
1148
+ provider: candidate.provider,
1149
+ model: candidate.id,
1150
+ error: error instanceof Error ? error.message : String(error),
1151
+ });
1152
+ return undefined;
1153
+ }
1154
+ throw error;
1155
+ });
1156
+ const hasKey = Boolean(key) && (!credentialSelector || key !== kNoAuth);
1157
+ modelApiKeyAvailability.set(availabilityKey, hasKey);
1158
+ return hasKey;
1159
+ };
1160
+
1161
+ // Load and create secret obfuscator early so resumed session state and prompt warnings
1162
+ // reflect actual loaded secrets, not just the setting toggle.
1163
+ let obfuscator: SecretObfuscator | undefined;
1164
+ if (settings.get("secrets.enabled")) {
1165
+ const fileEntries = await logger.time("loadSecrets", loadSecrets, cwd, agentDir);
1166
+ const envEntries = collectEnvSecrets();
1167
+ const allEntries = [...envEntries, ...fileEntries];
1168
+ if (allEntries.length > 0) {
1169
+ obfuscator = new SecretObfuscator(allEntries);
1170
+ }
1119
1171
  }
1120
- }
1121
- const secretsEnabled = obfuscator?.hasSecrets() === true;
1172
+ const secretsEnabled = obfuscator?.hasSecrets() === true;
1122
1173
 
1123
- // Check if session has existing data to restore
1124
- const existingSession = logger.time("loadSessionContext", () =>
1125
- deobfuscateSessionContext(sessionManager.buildSessionContext(), obfuscator),
1126
- );
1127
- const existingBranch = logger.time("getSessionBranch", () => sessionManager.getBranch());
1128
- const hasExistingSession = existingBranch.length > 0;
1129
- const hasThinkingEntry = existingBranch.some(entry => entry.type === "thinking_level_change");
1130
- const hasServiceTierEntry = existingBranch.some(entry => entry.type === "service_tier_change");
1131
-
1132
- const hasExplicitModel = options.model !== undefined || options.modelPattern !== undefined;
1133
- const modelMatchPreferences = {
1134
- usageOrder: settings.getStorage()?.getModelUsageOrder(),
1135
- };
1136
- const allowedModels = await logger.time("resolveAllowedModels", () =>
1137
- resolveAllowedModels(modelRegistry, settings, modelMatchPreferences),
1138
- );
1139
- const defaultRoleSpec = logger.time("resolveDefaultModelRole", () =>
1140
- resolveModelRoleValue(settings.getModelRole("default"), allowedModels, {
1141
- settings,
1142
- matchPreferences: modelMatchPreferences,
1143
- modelRegistry,
1144
- }),
1145
- );
1146
- let model = options.model;
1147
- let modelFallbackMessage: string | undefined;
1148
- const persistedDefaultChain = existingSession.configuredModelChains.default?.entries;
1149
- const defaultModelEntries =
1150
- persistedDefaultChain && persistedDefaultChain.length > 0
1151
- ? persistedDefaultChain
1152
- : existingSession.models.default
1153
- ? [existingSession.models.default]
1154
- : [];
1155
- // If session has data, restore its configured default chain rather than the
1156
- // scalar runtime model, which may be a stale fallback from the prior run.
1157
- if (!hasExplicitModel && !model && hasExistingSession && defaultModelEntries.length > 0) {
1158
- await logger.time("restoreSessionModel", async () => {
1159
- const restoredDefaultResolution = await resolveModelChainWithAuth(
1160
- defaultModelEntries,
1161
- modelRegistry,
1174
+ // Check if session has existing data to restore
1175
+ const existingSession = logger.time("loadSessionContext", () =>
1176
+ deobfuscateSessionContext(sessionManager.buildSessionContext(), obfuscator),
1177
+ );
1178
+ const existingBranch = logger.time("getSessionBranch", () => sessionManager.getBranch());
1179
+ const hasExistingSession = existingBranch.length > 0;
1180
+ const hasThinkingEntry = existingBranch.some(entry => entry.type === "thinking_level_change");
1181
+ const hasServiceTierEntry = existingBranch.some(entry => entry.type === "service_tier_change");
1182
+
1183
+ const hasExplicitModel = options.model !== undefined || options.modelPattern !== undefined;
1184
+ const modelMatchPreferences = {
1185
+ usageOrder: settings.getStorage()?.getModelUsageOrder(),
1186
+ };
1187
+ const allowedModels = await logger.time("resolveAllowedModels", () =>
1188
+ resolveAllowedModels(modelRegistry, settings, modelMatchPreferences),
1189
+ );
1190
+ const defaultRoleSpec = logger.time("resolveDefaultModelRole", () =>
1191
+ resolveModelRoleValue(settings.getModelRole("default"), allowedModels, {
1162
1192
  settings,
1163
- providerSessionId,
1164
- { managedFallback: defaultModelEntries.length > 1 },
1165
- );
1166
- model = restoredDefaultResolution.model;
1167
- if (!model) modelFallbackMessage = `Could not restore model ${defaultModelEntries.join(" -> ")}`;
1168
- });
1169
- }
1170
-
1171
- // If still no model, try settings default.
1172
- // Skip settings fallback when an explicit model was requested.
1173
- if (!hasExplicitModel && !model && defaultRoleSpec.model) {
1174
- const settingsDefaultModel = defaultRoleSpec.model;
1175
- logger.time("resolveSettingsDefaultModel", () => {
1176
- // defaultRoleSpec.model already comes from modelRegistry.getAvailable(),
1177
- // so re-validating auth here just repeats the expensive lookup path.
1178
- model = settingsDefaultModel;
1179
- });
1180
- }
1181
-
1182
- const taskDepth = options.taskDepth ?? 0;
1193
+ matchPreferences: modelMatchPreferences,
1194
+ modelRegistry,
1195
+ }),
1196
+ );
1197
+ let model = options.model;
1198
+ let modelFallbackMessage: string | undefined;
1199
+ const persistedDefaultChain = existingSession.configuredModelChains.default?.entries;
1200
+ const defaultModelEntries =
1201
+ persistedDefaultChain && persistedDefaultChain.length > 0
1202
+ ? persistedDefaultChain
1203
+ : existingSession.models.default
1204
+ ? [existingSession.models.default]
1205
+ : [];
1206
+ // If session has data, restore its configured default chain rather than the
1207
+ // scalar runtime model, which may be a stale fallback from the prior run.
1208
+ if (!hasExplicitModel && !model && hasExistingSession && defaultModelEntries.length > 0) {
1209
+ await logger.time("restoreSessionModel", async () => {
1210
+ const restoredDefaultResolution = await resolveModelChainWithAuth(
1211
+ defaultModelEntries,
1212
+ modelRegistry,
1213
+ settings,
1214
+ providerSessionId,
1215
+ { managedFallback: defaultModelEntries.length > 1 },
1216
+ );
1217
+ model = restoredDefaultResolution.model;
1218
+ if (!model) modelFallbackMessage = `Could not restore model ${defaultModelEntries.join(" -> ")}`;
1219
+ });
1220
+ }
1183
1221
 
1184
- let thinkingLevel = options.thinkingLevel;
1185
- const hasExplicitDefaultThinkingLevel = settings.has("defaultThinkingLevel");
1186
- let thinkingLevelFromSchemaDefault = false;
1222
+ // If still no model, try settings default.
1223
+ // Skip settings fallback when an explicit model was requested.
1224
+ if (!hasExplicitModel && !model && defaultRoleSpec.model) {
1225
+ const settingsDefaultModel = defaultRoleSpec.model;
1226
+ logger.time("resolveSettingsDefaultModel", () => {
1227
+ // defaultRoleSpec.model already comes from modelRegistry.getAvailable(),
1228
+ // so re-validating auth here just repeats the expensive lookup path.
1229
+ model = settingsDefaultModel;
1230
+ });
1231
+ }
1187
1232
 
1188
- // If session has data and includes a thinking entry, restore an explicit session
1189
- // override. A persisted inherit marker deliberately re-enters the normal
1190
- // default-role/global/model resolution path instead of resolving to `undefined`.
1191
- const restoredThinkingLevel =
1192
- hasExistingSession && hasThinkingEntry ? parseThinkingLevel(existingSession.thinkingLevel) : undefined;
1193
- if (thinkingLevel === undefined && restoredThinkingLevel !== ThinkingLevel.Inherit) {
1194
- thinkingLevel = restoredThinkingLevel;
1195
- }
1233
+ const taskDepth = options.taskDepth ?? 0;
1196
1234
 
1197
- if (thinkingLevel === undefined && !hasExplicitModel && defaultRoleSpec.explicitThinkingLevel) {
1198
- thinkingLevel = defaultRoleSpec.thinkingLevel;
1199
- }
1235
+ let thinkingLevel = options.thinkingLevel;
1236
+ const hasExplicitDefaultThinkingLevel = settings.has("defaultThinkingLevel");
1237
+ let thinkingLevelFromSchemaDefault = false;
1200
1238
 
1201
- // An explicit user/project default should win over the model's bundled
1202
- // defaultLevel. The schema default is only a final fallback so model metadata
1203
- // can keep driving first-run behavior until the user chooses "Set as default".
1204
- if (thinkingLevel === undefined && hasExplicitDefaultThinkingLevel) {
1205
- thinkingLevel = settings.get("defaultThinkingLevel");
1206
- }
1239
+ // If session has data and includes a thinking entry, restore an explicit session
1240
+ // override. A persisted inherit marker deliberately re-enters the normal
1241
+ // default-role/global/model resolution path instead of resolving to `undefined`.
1242
+ const restoredThinkingLevel =
1243
+ hasExistingSession && hasThinkingEntry ? parseThinkingLevel(existingSession.thinkingLevel) : undefined;
1244
+ if (thinkingLevel === undefined && restoredThinkingLevel !== ThinkingLevel.Inherit) {
1245
+ thinkingLevel = restoredThinkingLevel;
1246
+ }
1207
1247
 
1208
- if (thinkingLevel === undefined && model?.thinking?.defaultLevel !== undefined) {
1209
- thinkingLevel = model.thinking.defaultLevel;
1210
- }
1248
+ if (thinkingLevel === undefined && !hasExplicitModel && defaultRoleSpec.explicitThinkingLevel) {
1249
+ thinkingLevel = defaultRoleSpec.thinkingLevel;
1250
+ }
1211
1251
 
1212
- if (thinkingLevel === undefined) {
1213
- thinkingLevel = settings.get("defaultThinkingLevel");
1214
- thinkingLevelFromSchemaDefault = true;
1215
- }
1216
- if (model) {
1217
- const resolvedModel = model;
1218
- thinkingLevel = logger.time("resolveThinkingLevelForModel", () =>
1219
- resolveThinkingLevelForModel(resolvedModel, thinkingLevel),
1220
- );
1221
- // Fire-and-forget TLS+H2 handshake to the model's host so it overlaps
1222
- // with the rest of session setup (extension/skill load, tool registry,
1223
- // system prompt build). Without this, the first `fetch(...)` pays the
1224
- // full handshake serially — 100–300 ms transcontinental for
1225
- // api.anthropic.com from a residential IP. Every session frontend benefits
1226
- // (interactive, print, SDK, ACP).
1227
- preconnectModelHost(model.baseUrl);
1228
- }
1252
+ // An explicit user/project default should win over the model's bundled
1253
+ // defaultLevel. The schema default is only a final fallback so model metadata
1254
+ // can keep driving first-run behavior until the user chooses "Set as default".
1255
+ if (thinkingLevel === undefined && hasExplicitDefaultThinkingLevel) {
1256
+ thinkingLevel = settings.get("defaultThinkingLevel");
1257
+ }
1229
1258
 
1230
- let skills: Skill[];
1231
- let skillWarnings: SkillWarning[];
1232
- if (options.skills !== undefined) {
1233
- // The four public GJC workflow skills are a product invariant, not
1234
- // ordinary filesystem-discovered skills. Keep them available even for
1235
- // explicit SDK skill lists so startup and command routing survive
1236
- // accidental `.gjc` deletion or overzealous caller filtering.
1237
- skills = withEmbeddedDefaultGjcSkills(options.skills);
1238
- skillWarnings = [];
1239
- } else if (settings.get("skills.enabled")) {
1240
- const skillsResult = await logger.time("loadSkills", loadSkills, {
1241
- ...settings.getGroup("skills"),
1242
- cwd,
1243
- disabledExtensions: settings.get("disabledExtensions"),
1244
- });
1245
- skills = withEmbeddedDefaultGjcSkills(skillsResult.skills);
1246
- skillWarnings = skillsResult.warnings;
1247
- } else {
1248
- // GJC's four public workflow skills are bundled into the binary so the
1249
- // default workflow surface survives accidental .gjc deletion. Arbitrary
1250
- // filesystem skill discovery remains gated by skills.enabled above.
1251
- skills = getEmbeddedDefaultGjcSkills();
1252
- skillWarnings = [];
1253
- }
1259
+ if (thinkingLevel === undefined && model?.thinking?.defaultLevel !== undefined) {
1260
+ thinkingLevel = model.thinking.defaultLevel;
1261
+ }
1254
1262
 
1255
- // Discover rules and bucket them in one pass to avoid repeated scans over large rule sets.
1256
- const { ttsrManager, rulebookRules, alwaysApplyRules } = await logger.time("discoverTtsrRules", async () => {
1257
- const ttsrSettings = settings.getGroup("ttsr");
1258
- const ttsrManager = new TtsrManager(ttsrSettings);
1259
- const rulesResult =
1260
- options.rules !== undefined
1261
- ? { items: options.rules, warnings: undefined }
1262
- : await loadCapability<Rule>(ruleCapability.id, { cwd });
1263
- const rulebookRules: Rule[] = [];
1264
- const alwaysApplyRules: Rule[] = [];
1265
- for (const rule of rulesResult.items) {
1266
- const isTtsrRule = rule.condition && rule.condition.length > 0 ? ttsrManager.addRule(rule) : false;
1267
- if (isTtsrRule) {
1268
- continue;
1269
- }
1270
- if (rule.alwaysApply === true) {
1271
- alwaysApplyRules.push(rule);
1272
- continue;
1273
- }
1274
- if (rule.description) {
1275
- rulebookRules.push(rule);
1276
- }
1263
+ if (thinkingLevel === undefined) {
1264
+ thinkingLevel = settings.get("defaultThinkingLevel");
1265
+ thinkingLevelFromSchemaDefault = true;
1277
1266
  }
1278
- if (ttsrManager.getSettings().enabled !== false) {
1279
- if ((existingSession.ttsrMessageCount ?? 0) > 0) {
1280
- ttsrManager.restoreMessageCount(existingSession.ttsrMessageCount ?? 0);
1281
- }
1282
- if (existingSession.injectedTtsrRuleRecords && existingSession.injectedTtsrRuleRecords.length > 0) {
1283
- ttsrManager.restoreInjected(existingSession.injectedTtsrRuleRecords);
1284
- } else if (existingSession.injectedTtsrRules.length > 0) {
1285
- ttsrManager.restoreInjected(existingSession.injectedTtsrRules);
1286
- }
1267
+ if (model) {
1268
+ const resolvedModel = model;
1269
+ thinkingLevel = logger.time("resolveThinkingLevelForModel", () =>
1270
+ resolveThinkingLevelForModel(resolvedModel, thinkingLevel),
1271
+ );
1272
+ // Fire-and-forget TLS+H2 handshake to the model's host so it overlaps
1273
+ // with the rest of session setup (extension/skill load, tool registry,
1274
+ // system prompt build). Without this, the first `fetch(...)` pays the
1275
+ // full handshake serially — 100–300 ms transcontinental for
1276
+ // api.anthropic.com from a residential IP. Every session frontend benefits
1277
+ // (interactive, print, SDK, ACP).
1278
+ preconnectModelHost(model.baseUrl);
1287
1279
  }
1288
- return { ttsrManager, rulebookRules, alwaysApplyRules };
1289
- });
1290
1280
 
1291
- // Resolve contextFiles up-front (it's needed before tool creation). The
1292
- // workspace tree scan is slow on large repos and we MUST NOT block startup on
1293
- // it. On timeout we forward `undefined` to ToolSession; buildSystemPromptInternal
1294
- // will re-race the same promise through its own withDeadline path. Background
1295
- // work continues so caches still warm.
1296
- const raceWithDeadline = async <T>(name: string, work: Promise<T>): Promise<T | undefined> => {
1297
- let timedOut = false;
1298
- const result = await Promise.race([
1299
- work,
1300
- Bun.sleep(STARTUP_SCAN_DEADLINE_MS).then(() => {
1301
- timedOut = true;
1302
- return undefined;
1303
- }),
1304
- ]);
1305
- if (timedOut) {
1306
- logger.warn("Startup scan exceeded deadline; deferring to system prompt fallback", {
1307
- name,
1308
- timeoutMs: STARTUP_SCAN_DEADLINE_MS,
1281
+ let skills: Skill[];
1282
+ let skillWarnings: SkillWarning[];
1283
+ if (options.skills !== undefined) {
1284
+ // The four public GJC workflow skills are a product invariant, not
1285
+ // ordinary filesystem-discovered skills. Keep them available even for
1286
+ // explicit SDK skill lists so startup and command routing survive
1287
+ // accidental `.gjc` deletion or overzealous caller filtering.
1288
+ skills = withEmbeddedDefaultGjcSkills(options.skills);
1289
+ skillWarnings = [];
1290
+ } else if (settings.get("skills.enabled")) {
1291
+ const skillsResult = await logger.time("loadSkills", loadSkills, {
1292
+ ...settings.getGroup("skills"),
1309
1293
  cwd,
1294
+ disabledExtensions: settings.get("disabledExtensions"),
1310
1295
  });
1296
+ skills = withEmbeddedDefaultGjcSkills(skillsResult.skills);
1297
+ skillWarnings = skillsResult.warnings;
1298
+ } else {
1299
+ // GJC's four public workflow skills are bundled into the binary so the
1300
+ // default workflow surface survives accidental .gjc deletion. Arbitrary
1301
+ // filesystem skill discovery remains gated by skills.enabled above.
1302
+ skills = getEmbeddedDefaultGjcSkills();
1303
+ skillWarnings = [];
1311
1304
  }
1312
- return result;
1313
- };
1314
- const [contextFiles, resolvedWorkspaceTree] = await Promise.all([
1315
- contextFilesPromise,
1316
- raceWithDeadline("buildWorkspaceTree", workspaceTreePromise),
1317
- ]);
1318
1305
 
1319
- let agent: Agent;
1320
- let session!: AgentSession;
1321
- let hasSession = false;
1322
- let hasRegistered = false;
1323
- let cleanupOwnedMcpManager: (() => Promise<void>) | undefined;
1324
- const enableLsp = options.enableLsp ?? true;
1325
- const backgroundJobsEnabled = isBackgroundJobSupportEnabled(settings);
1326
- const asyncMaxJobs = Math.min(100, Math.max(1, settings.get("async.maxJobs") ?? 100));
1327
- const ASYNC_INLINE_RESULT_MAX_CHARS = 12_000;
1328
- const ASYNC_PREVIEW_MAX_CHARS = 4_000;
1329
- const formatAsyncResultForFollowUp = async (result: string): Promise<string> => {
1330
- if (result.length <= ASYNC_INLINE_RESULT_MAX_CHARS) {
1306
+ // Discover rules and bucket them in one pass to avoid repeated scans over large rule sets.
1307
+ const { ttsrManager, rulebookRules, alwaysApplyRules } = await logger.time("discoverTtsrRules", async () => {
1308
+ const ttsrSettings = settings.getGroup("ttsr");
1309
+ const ttsrManager = new TtsrManager(ttsrSettings);
1310
+ const rulesResult =
1311
+ options.rules !== undefined
1312
+ ? { items: options.rules, warnings: undefined }
1313
+ : await loadCapability<Rule>(ruleCapability.id, { cwd });
1314
+ const rulebookRules: Rule[] = [];
1315
+ const alwaysApplyRules: Rule[] = [];
1316
+ for (const rule of rulesResult.items) {
1317
+ const isTtsrRule = rule.condition && rule.condition.length > 0 ? ttsrManager.addRule(rule) : false;
1318
+ if (isTtsrRule) {
1319
+ continue;
1320
+ }
1321
+ if (rule.alwaysApply === true) {
1322
+ alwaysApplyRules.push(rule);
1323
+ continue;
1324
+ }
1325
+ if (rule.description) {
1326
+ rulebookRules.push(rule);
1327
+ }
1328
+ }
1329
+ if (ttsrManager.getSettings().enabled !== false) {
1330
+ if ((existingSession.ttsrMessageCount ?? 0) > 0) {
1331
+ ttsrManager.restoreMessageCount(existingSession.ttsrMessageCount ?? 0);
1332
+ }
1333
+ if (existingSession.injectedTtsrRuleRecords && existingSession.injectedTtsrRuleRecords.length > 0) {
1334
+ ttsrManager.restoreInjected(existingSession.injectedTtsrRuleRecords);
1335
+ } else if (existingSession.injectedTtsrRules.length > 0) {
1336
+ ttsrManager.restoreInjected(existingSession.injectedTtsrRules);
1337
+ }
1338
+ }
1339
+ return { ttsrManager, rulebookRules, alwaysApplyRules };
1340
+ });
1341
+
1342
+ // Resolve contextFiles up-front (it's needed before tool creation). The
1343
+ // workspace tree scan is slow on large repos and we MUST NOT block startup on
1344
+ // it. On timeout we forward `undefined` to ToolSession; buildSystemPromptInternal
1345
+ // will re-race the same promise through its own withDeadline path. Background
1346
+ // work continues so caches still warm.
1347
+ const raceWithDeadline = async <T>(name: string, work: Promise<T>): Promise<T | undefined> => {
1348
+ let timedOut = false;
1349
+ const result = await Promise.race([
1350
+ work,
1351
+ Bun.sleep(STARTUP_SCAN_DEADLINE_MS).then(() => {
1352
+ timedOut = true;
1353
+ return undefined;
1354
+ }),
1355
+ ]);
1356
+ if (timedOut) {
1357
+ logger.warn("Startup scan exceeded deadline; deferring to system prompt fallback", {
1358
+ name,
1359
+ timeoutMs: STARTUP_SCAN_DEADLINE_MS,
1360
+ cwd,
1361
+ });
1362
+ }
1331
1363
  return result;
1332
- }
1364
+ };
1365
+ const [contextFiles, resolvedWorkspaceTree] = await Promise.all([
1366
+ contextFilesPromise,
1367
+ raceWithDeadline("buildWorkspaceTree", workspaceTreePromise),
1368
+ ]);
1333
1369
 
1334
- const preview = `${result.slice(0, ASYNC_PREVIEW_MAX_CHARS)}\n\n[Output truncated. Showing first ${ASYNC_PREVIEW_MAX_CHARS.toLocaleString()} characters.]`;
1335
- try {
1336
- const { path: artifactPath, id: artifactId } = await sessionManager.allocateArtifactPath("async");
1337
- if (artifactPath && artifactId) {
1338
- await Bun.write(artifactPath, result);
1339
- return `${preview}\nFull output: artifact://${artifactId}`;
1370
+ const backgroundJobsEnabled = isBackgroundJobSupportEnabled(settings);
1371
+ const asyncMaxJobs = Math.min(100, Math.max(1, settings.get("async.maxJobs") ?? 100));
1372
+ const ASYNC_INLINE_RESULT_MAX_CHARS = 12_000;
1373
+ const ASYNC_PREVIEW_MAX_CHARS = 4_000;
1374
+ const formatAsyncResultForFollowUp = async (result: string): Promise<string> => {
1375
+ if (result.length <= ASYNC_INLINE_RESULT_MAX_CHARS) {
1376
+ return result;
1340
1377
  }
1341
- } catch (error) {
1342
- logger.warn("Failed to persist async follow-up artifact", {
1343
- error: error instanceof Error ? error.message : String(error),
1344
- });
1345
- }
1346
1378
 
1347
- return preview;
1348
- };
1349
- // Only top-level sessions own an AsyncJobManager. Subagents reach the
1350
- // parent's manager via `AsyncJobManager.instance()` (set below), so creating
1351
- // a second instance here just to leave it orphaned wastes a constructor and
1352
- // risks accidental disposal of the parent's manager on subagent teardown.
1353
- const asyncJobManager =
1354
- backgroundJobsEnabled && !options.parentTaskPrefix
1355
- ? new AsyncJobManager({
1356
- maxRunningJobs: asyncMaxJobs,
1357
- onJobComplete: async (jobId, result, job) => {
1358
- if (!session || asyncJobManager!.isDeliverySuppressed(jobId)) return;
1359
- const formattedResult = await formatAsyncResultForFollowUp(result);
1360
- if (asyncJobManager!.isDeliverySuppressed(jobId)) return;
1361
-
1362
- const durationMs = job ? jobElapsedMs(job) : undefined;
1363
- session.yieldQueue.enqueue<AsyncResultEntry>("async-result", {
1364
- jobId,
1365
- result: formattedResult,
1366
- job,
1367
- durationMs,
1368
- });
1369
- },
1370
- })
1371
- : undefined;
1379
+ const preview = `${result.slice(0, ASYNC_PREVIEW_MAX_CHARS)}\n\n[Output truncated. Showing first ${ASYNC_PREVIEW_MAX_CHARS.toLocaleString()} characters.]`;
1380
+ try {
1381
+ const { path: artifactPath, id: artifactId } = await sessionManager.allocateArtifactPath("async");
1382
+ if (artifactPath && artifactId) {
1383
+ await Bun.write(artifactPath, result);
1384
+ return `${preview}\nFull output: artifact://${artifactId}`;
1385
+ }
1386
+ } catch (error) {
1387
+ logger.warn("Failed to persist async follow-up artifact", {
1388
+ error: error instanceof Error ? error.message : String(error),
1389
+ });
1390
+ }
1372
1391
 
1373
- const agentRegistry = options.agentRegistry ?? AgentRegistry.global();
1374
- const resolvedAgentId = options.agentId ?? options.parentTaskPrefix ?? MAIN_AGENT_ID;
1375
- const resolvedAgentDisplayName = options.agentDisplayName ?? (isCanonicalSubSession ? "sub" : "main");
1376
- const resolvedAgentRosterLabel = resolveAgentRosterLabel(
1377
- options.agentRosterLabel,
1378
- resolvedAgentId,
1379
- resolvedAgentDisplayName,
1380
- );
1381
- const evalKernelOwnerId = `agent-session:${Snowflake.next()}`;
1382
- let disposeLocalProtocolOverride: (() => void) | undefined;
1383
- let localProtocolOverrideReleased = false;
1384
- const releaseLocalProtocolOverride = (): void => {
1385
- if (localProtocolOverrideReleased) return;
1386
- localProtocolOverrideReleased = true;
1387
- disposeLocalProtocolOverride?.();
1388
- };
1392
+ return preview;
1393
+ };
1394
+ // Only top-level sessions own an AsyncJobManager. Subagents reach the
1395
+ // parent's manager via `AsyncJobManager.instance()` (set below), so creating
1396
+ // a second instance here just to leave it orphaned wastes a constructor and
1397
+ // risks accidental disposal of the parent's manager on subagent teardown.
1398
+ const asyncJobManager =
1399
+ backgroundJobsEnabled && !options.parentTaskPrefix
1400
+ ? new AsyncJobManager({
1401
+ maxRunningJobs: asyncMaxJobs,
1402
+ onJobComplete: async (jobId, result, job) => {
1403
+ if (!session || asyncJobManager!.isDeliverySuppressed(jobId)) return;
1404
+ const formattedResult = await formatAsyncResultForFollowUp(result);
1405
+ if (asyncJobManager!.isDeliverySuppressed(jobId)) return;
1406
+
1407
+ const durationMs = job ? jobElapsedMs(job) : undefined;
1408
+ session.yieldQueue.enqueue<AsyncResultEntry>("async-result", {
1409
+ jobId,
1410
+ result: formattedResult,
1411
+ job,
1412
+ durationMs,
1413
+ });
1414
+ },
1415
+ })
1416
+ : undefined;
1389
1417
 
1390
- try {
1391
1418
  let promptMetadataModel: Model | undefined;
1392
1419
  const getActiveModelString = (): string | undefined => {
1393
1420
  const activeModel = promptMetadataModel ?? agent?.state.model;
@@ -1454,11 +1481,6 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1454
1481
  getCompactContext: () => session.formatCompactContext(),
1455
1482
  getTodoPhases: () => session.getTodoPhases(),
1456
1483
  setTodoPhases: phases => session.setTodoPhases(phases),
1457
- isMCPDiscoveryEnabled: () => session.isMCPDiscoveryEnabled(),
1458
- getDiscoverableMCPTools: () => session.getDiscoverableMCPTools(),
1459
- getDiscoverableMCPSearchIndex: () => session.getDiscoverableMCPSearchIndex(),
1460
- getSelectedMCPToolNames: () => session.getSelectedMCPToolNames(),
1461
- activateDiscoveredMCPTools: toolNames => session.activateDiscoveredMCPTools(toolNames),
1462
1484
  // Generic tool discovery (unified — covers built-in + MCP + extension)
1463
1485
  isToolDiscoveryEnabled: () => session.isToolDiscoveryEnabled(),
1464
1486
  getDiscoverableTools: filter => session.getDiscoverableTools(filter),
@@ -1743,7 +1765,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1743
1765
  currentAgentType: options.currentAgentType,
1744
1766
  spawnedByGjc,
1745
1767
  }) ||
1746
- shouldHostSdk(notificationCfg, isTopLevelSdkSession)
1768
+ (shouldHostSdk(notificationCfg, isTopLevelSdkSession) && (options.sdkHostModeSupported ?? true))
1747
1769
  ) {
1748
1770
  inlineExtensions.push(async api => {
1749
1771
  try {
@@ -1754,6 +1776,12 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1754
1776
  settings,
1755
1777
  controller: notificationSessionController,
1756
1778
  spawnedByGjc,
1779
+ sdkHostModeSupported: options.sdkHostModeSupported,
1780
+ runEphemeralTurn: async (promptText, signal) => {
1781
+ if (!session) throw new Error("Ephemeral turns are unavailable.");
1782
+ const { replyText } = await session.runEphemeralTurn({ promptText, signal });
1783
+ return { replyText };
1784
+ },
1757
1785
  });
1758
1786
  } catch (error) {
1759
1787
  lifecycleStartupCapability?.settleFailure(
@@ -1904,12 +1932,11 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1904
1932
  // No runner to forward to; release our subscription. The embedder's own
1905
1933
  // onCredentialDisabled (if any) keeps firing through its own subscription.
1906
1934
  startupCredentialDisabledEvents.length = 0;
1907
- unsubscribeCredentialDisabled?.();
1908
- unsubscribeCredentialDisabled = undefined;
1935
+ releaseCredentialDisabledSubscription();
1909
1936
  }
1910
1937
 
1911
1938
  const getSessionContext = () => ({
1912
- sessionManager,
1939
+ sessionManager: createReadonlySessionManager(sessionManager),
1913
1940
  modelRegistry,
1914
1941
  model: agent.state.model,
1915
1942
  isIdle: () => !session.isStreaming,
@@ -1938,7 +1965,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1938
1965
  // Without extension runner: wrap CustomTools directly with CustomToolAdapter
1939
1966
  // ToolDefinition items require ExtensionContext and cannot be used without a runner
1940
1967
  const customToolContext = (): CustomToolContext => ({
1941
- sessionManager,
1968
+ sessionManager: createReadonlySessionManager(sessionManager),
1942
1969
  modelRegistry,
1943
1970
  model: agent?.state.model,
1944
1971
  isIdle: () => !session?.isStreaming,
@@ -2040,36 +2067,11 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
2040
2067
  candidateModel?: Model,
2041
2068
  ): Promise<BuildSystemPromptResult> => {
2042
2069
  toolContextStore.setToolNames(toolNames);
2043
- const { discoverableBuiltinTools, discoverableMCPTools, promptTools } = (() => {
2070
+ const promptTools = (() => {
2044
2071
  const previousPromptMetadataModel = promptMetadataModel;
2045
2072
  promptMetadataModel = candidateModel;
2046
2073
  try {
2047
- const activeToolNames = new Set(toolNames);
2048
- const discoverableBuiltinTools: DiscoverableTool[] =
2049
- effectiveDiscoveryMode === "all"
2050
- ? collectDiscoverableTools(
2051
- Array.from(tools.values()).filter(
2052
- tool => tool.loadMode === "discoverable" && !activeToolNames.has(tool.name),
2053
- ),
2054
- { source: "builtin" },
2055
- )
2056
- : [];
2057
- const discoverableMCPTools: DiscoverableTool[] = mcpDiscoveryEnabled
2058
- ? collectDiscoverableTools(
2059
- Array.from(tools.values()).filter(
2060
- tool => isMCPToolName(tool.name) && !activeToolNames.has(tool.name),
2061
- ),
2062
- { source: "mcp" },
2063
- )
2064
- : [];
2065
- const discoverableToolsForDesc: DiscoverableTool[] = [
2066
- ...discoverableBuiltinTools,
2067
- ...discoverableMCPTools,
2068
- ];
2069
- const promptTools = buildSystemPromptToolMetadata(tools, {
2070
- search_tool_bm25: { description: renderSearchToolBm25Description(discoverableToolsForDesc) },
2071
- });
2072
- return { discoverableBuiltinTools, discoverableMCPTools, promptTools };
2074
+ return buildSystemPromptToolMetadata(tools);
2073
2075
  } finally {
2074
2076
  promptMetadataModel = previousPromptMetadataModel;
2075
2077
  }
@@ -2080,25 +2082,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
2080
2082
  session,
2081
2083
  );
2082
2084
 
2083
- // Build combined append prompt: memory instructions + MCP server instructions
2084
- const serverInstructions =
2085
- explicitMcpConfigPath === undefined ? mcpManager?.getServerInstructions() : undefined;
2086
- let appendPrompt: string | undefined = memoryInstructions ?? undefined;
2087
- if (serverInstructions && serverInstructions.size > 0) {
2088
- const parts: string[] = [];
2089
- if (appendPrompt) parts.push(appendPrompt);
2090
- parts.push(
2091
- "## MCP Server Instructions\n\nThe following instructions are provided by connected MCP servers. They are server-controlled and may not be verified.",
2092
- );
2093
- for (const [srvName, srvInstructions] of serverInstructions) {
2094
- const truncated =
2095
- srvInstructions.length > MAX_MCP_INSTRUCTIONS_LENGTH
2096
- ? `${srvInstructions.slice(0, MAX_MCP_INSTRUCTIONS_LENGTH)}\n[truncated]`
2097
- : srvInstructions;
2098
- parts.push(`### ${srvName}\n${truncated}`);
2099
- }
2100
- appendPrompt = parts.join("\n\n");
2101
- }
2085
+ const appendPrompt: string | undefined = memoryInstructions ?? undefined;
2102
2086
  let pluginSystemAppendices = "";
2103
2087
  try {
2104
2088
  pluginSystemAppendices = await renderAlwaysOnSystemAppendices({ cwd });
@@ -2118,12 +2102,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
2118
2102
  pluginAppendices: pluginSystemAppendices,
2119
2103
  repeatToolDescriptions,
2120
2104
  intentField,
2121
- mcpDiscoveryMode: false,
2122
- mcpDiscoveryServerSummaries: [],
2123
2105
  toolDiscoveryActive: effectiveDiscoveryMode === "all" || mcpDiscoveryEnabled,
2124
- discoverableTools: [...discoverableBuiltinTools, ...discoverableMCPTools]
2125
- .map(tool => ({ name: tool.name, summary: tool.summary }))
2126
- .sort((a, b) => a.name.localeCompare(b.name)),
2127
2106
  eagerTasks,
2128
2107
  secretsEnabled,
2129
2108
  workspaceTree: workspaceTreePromise,
@@ -2152,12 +2131,15 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
2152
2131
  : toolNamesFromRegistry;
2153
2132
  const normalizedRequested = requestedToolNames.filter(name => toolRegistry.has(name));
2154
2133
  const requestedToolNameSet = new Set(normalizedRequested);
2155
- // Generic tool discovery hides non-essential built-ins behind search_tool_bm25.
2156
- // Legacy MCP discovery remains separately controlled by mcp.discoveryMode so
2157
- // loaded MCP catalogs can be searched without enabling every hidden built-in.
2134
+ // Normalize the user-facing mcp.discoveryMode alias once at session construction.
2158
2135
  const toolsDiscoveryModeSetting = settings.get("tools.discoveryMode");
2159
- const effectiveDiscoveryMode: "off" | "all" = toolsDiscoveryModeSetting === "all" ? "all" : "off";
2160
- const mcpDiscoveryEnabled = settings.get("mcp.discoveryMode") === true;
2136
+ const effectiveDiscoveryMode: "off" | "mcp-only" | "all" =
2137
+ toolsDiscoveryModeSetting !== "off"
2138
+ ? (toolsDiscoveryModeSetting as "mcp-only" | "all")
2139
+ : settings.get("mcp.discoveryMode") || explicitMcpConfigPath !== undefined
2140
+ ? "mcp-only"
2141
+ : "off";
2142
+ const mcpDiscoveryEnabled = effectiveDiscoveryMode !== "off";
2161
2143
  const defaultInactiveToolNames = new Set(
2162
2144
  registeredTools.filter(tool => tool.definition.defaultInactive).map(tool => tool.definition.name),
2163
2145
  );
@@ -2190,7 +2172,11 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
2190
2172
  toolRegistry.has(name),
2191
2173
  );
2192
2174
  defaultSelectedMCPToolNames = [
2193
- ...new Set([...discoveryDefaultServerToolNames, ...explicitlyRequestedMCPToolNames]),
2175
+ ...new Set([
2176
+ ...discoveryDefaultServerToolNames,
2177
+ ...explicitlyRequestedMCPToolNames,
2178
+ ...(explicitMcpConfigPath !== undefined ? exactMcpToolNames : []),
2179
+ ]),
2194
2180
  ];
2195
2181
  initialSelectedMCPToolNames = existingSession.hasPersistedMCPToolSelection
2196
2182
  ? restoredSelectedMCPToolNames
@@ -2303,11 +2289,13 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
2303
2289
  if (!obfuscator?.hasSecrets()) return converted;
2304
2290
  return obfuscateMessages(obfuscator, converted);
2305
2291
  };
2306
- const transformContext = extensionRunner
2307
- ? async (messages: AgentMessage[], _signal?: AbortSignal) => {
2308
- return await extensionRunner.emitContext(messages);
2309
- }
2310
- : undefined;
2292
+ const transformContext = async (messages: AgentMessage[], _signal?: AbortSignal) => {
2293
+ // External Agent events dispatch listeners without awaiting them. The
2294
+ // session-owned barrier makes any pre-admission artifact transformation
2295
+ // visible before this provider context is normalized.
2296
+ await session?.awaitPendingContextTransformations();
2297
+ return extensionRunner ? await extensionRunner.emitContext(messages) : messages;
2298
+ };
2311
2299
  const onPayload = extensionRunner
2312
2300
  ? async (payload: unknown, _model?: Model) => {
2313
2301
  return await extensionRunner.emitBeforeProviderRequest(payload);
@@ -2524,28 +2512,14 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
2524
2512
  onResponse,
2525
2513
  convertToLlm: convertToLlmFinal,
2526
2514
  rebuildSystemPrompt,
2515
+ getMcpServerInstructions:
2516
+ explicitMcpConfigPath === undefined && mcpManager ? () => mcpManager.getServerInstructions() : undefined,
2527
2517
  workspaceTree: resolvedWorkspaceTree,
2528
2518
  reloadSshTool,
2529
2519
  requestedToolNames: requestedToolNameSet,
2530
2520
  discoverableToolAllowedNames: options.discoverableToolAllowedNames,
2531
- getMcpServerInstructions:
2532
- mcpManager && explicitMcpConfigPath === undefined
2533
- ? () => {
2534
- const raw = mcpManager.getServerInstructions();
2535
- if (!raw || raw.size === 0) return raw;
2536
- const out = new Map<string, string>();
2537
- for (const [name, text] of raw) {
2538
- out.set(
2539
- name,
2540
- text.length > MAX_MCP_INSTRUCTIONS_LENGTH
2541
- ? text.slice(0, MAX_MCP_INSTRUCTIONS_LENGTH)
2542
- : text,
2543
- );
2544
- }
2545
- return out;
2546
- }
2547
- : undefined,
2548
2521
  mcpDiscoveryEnabled,
2522
+ discoveryMode: effectiveDiscoveryMode,
2549
2523
  initialSelectedMCPToolNames,
2550
2524
  defaultSelectedMCPToolNames,
2551
2525
  persistInitialMCPToolSelection: !hasExistingSession,
@@ -2580,9 +2554,13 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
2580
2554
  try {
2581
2555
  await originalDispose();
2582
2556
  } finally {
2583
- agentRegistry.unregister(resolvedAgentId);
2584
- unsubscribeCredentialDisabled?.();
2585
- releaseLocalProtocolOverride();
2557
+ try {
2558
+ agentRegistry.unregister(resolvedAgentId);
2559
+ releaseCredentialDisabledSubscription();
2560
+ releaseLocalProtocolOverride();
2561
+ } finally {
2562
+ closeOwnedAuthStorage();
2563
+ }
2586
2564
  }
2587
2565
  };
2588
2566
  }
@@ -2697,9 +2675,8 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
2697
2675
  };
2698
2676
  } catch (error) {
2699
2677
  // Release the subscription if the throw happened after install but before the
2700
- // dispose-wrap took ownership. Idempotent with dispose() — Set.delete is a no-op
2701
- // for already-removed listeners.
2702
- unsubscribeCredentialDisabled?.();
2678
+ // dispose-wrap took ownership.
2679
+ releaseCredentialDisabledSubscription();
2703
2680
  try {
2704
2681
  if (hasSession) {
2705
2682
  await session.dispose();
@@ -2713,6 +2690,11 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
2713
2690
  logger.warn("Failed to clean up createAgentSession resources after startup error");
2714
2691
  } finally {
2715
2692
  releaseLocalProtocolOverride();
2693
+ try {
2694
+ closeOwnedAuthStorage();
2695
+ } catch {
2696
+ logger.warn("Failed to close owned auth storage after startup error");
2697
+ }
2716
2698
  }
2717
2699
  throw error;
2718
2700
  }