@gajae-code/coding-agent 0.11.1 → 0.11.2

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