@gajae-code/coding-agent 0.13.3 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (599) hide show
  1. package/CHANGELOG.md +184 -2
  2. package/README.md +2 -0
  3. package/bin/gajaessi.js +4 -0
  4. package/dist/types/async/job-manager.d.ts +55 -2
  5. package/dist/types/async/support.d.ts +14 -0
  6. package/dist/types/capability/mcp.d.ts +6 -0
  7. package/dist/types/capability/types.d.ts +6 -0
  8. package/dist/types/cli/accounts-cli.d.ts +19 -0
  9. package/dist/types/cli/args.d.ts +3 -0
  10. package/dist/types/cli/config-cli.d.ts +6 -0
  11. package/dist/types/cli/crash-cli.d.ts +10 -0
  12. package/dist/types/cli/customize-doctor.d.ts +145 -0
  13. package/dist/types/cli/daemon-cli.d.ts +3 -1
  14. package/dist/types/cli/mcp-cli.d.ts +11 -0
  15. package/dist/types/cli/notify-cli.d.ts +1 -1
  16. package/dist/types/cli/root-flags.d.ts +12 -0
  17. package/dist/types/cli/setup-cli.d.ts +3 -1
  18. package/dist/types/cli/skills-cli.d.ts +2 -1
  19. package/dist/types/cli/update-cli.d.ts +19 -1
  20. package/dist/types/cli.d.ts +6 -0
  21. package/dist/types/commands/accounts.d.ts +40 -0
  22. package/dist/types/commands/crash.d.ts +24 -0
  23. package/dist/types/commands/customize.d.ts +22 -0
  24. package/dist/types/commands/daemon.d.ts +1 -1
  25. package/dist/types/commands/launch.d.ts +6 -0
  26. package/dist/types/commands/setup.d.ts +6 -0
  27. package/dist/types/commands/skills.d.ts +6 -1
  28. package/dist/types/commands/update.d.ts +7 -0
  29. package/dist/types/config/atomic-yaml-patch-worker.d.ts +25 -0
  30. package/dist/types/config/atomic-yaml-patch.d.ts +62 -2
  31. package/dist/types/config/config-schema-version.d.ts +10 -0
  32. package/dist/types/config/file-lock.d.ts +18 -0
  33. package/dist/types/config/model-profile-activation.d.ts +3 -1
  34. package/dist/types/config/model-registry.d.ts +9 -1
  35. package/dist/types/config/models-config-schema.d.ts +2 -0
  36. package/dist/types/config/settings-schema.d.ts +70 -6
  37. package/dist/types/config/settings.d.ts +63 -0
  38. package/dist/types/config/skill-settings-defaults.d.ts +26 -0
  39. package/dist/types/config/task-delegation.d.ts +39 -0
  40. package/dist/types/config/update-channel.d.ts +10 -0
  41. package/dist/types/coordinator-mcp/server.d.ts +5 -1
  42. package/dist/types/crash/index-store.d.ts +87 -0
  43. package/dist/types/crash/nudge.d.ts +50 -0
  44. package/dist/types/crash/record-loader.d.ts +17 -0
  45. package/dist/types/crash/report.d.ts +134 -0
  46. package/dist/types/crash/sanitize.d.ts +25 -0
  47. package/dist/types/cursor.d.ts +13 -2
  48. package/dist/types/customization/import.d.ts +37 -0
  49. package/dist/types/customization/index.d.ts +4 -0
  50. package/dist/types/customization/inventory.d.ts +25 -0
  51. package/dist/types/customization/mutations.d.ts +32 -0
  52. package/dist/types/customization/types.d.ts +115 -0
  53. package/dist/types/daemon/control-types.d.ts +8 -1
  54. package/dist/types/daemon/operator-contract.d.ts +4 -4
  55. package/dist/types/discovery/claude.d.ts +39 -1
  56. package/dist/types/discovery/codex.d.ts +39 -1
  57. package/dist/types/discovery/index.d.ts +2 -0
  58. package/dist/types/discovery/mcp-compat.d.ts +30 -0
  59. package/dist/types/edit/renderer.d.ts +23 -0
  60. package/dist/types/extensibility/extensions/runner.d.ts +3 -0
  61. package/dist/types/extensibility/extensions/types.d.ts +21 -4
  62. package/dist/types/extensibility/gjc-plugins/observability.d.ts +6 -1
  63. package/dist/types/extensibility/gjc-plugins/registry.d.ts +8 -1
  64. package/dist/types/extensibility/hooks/loader.d.ts +19 -5
  65. package/dist/types/extensibility/runtime-skill-discovery.d.ts +22 -1
  66. package/dist/types/extensibility/skill-management.d.ts +107 -0
  67. package/dist/types/gjc-runtime/deep-interview-runtime.d.ts +3 -1
  68. package/dist/types/gjc-runtime/launch-worktree.d.ts +8 -0
  69. package/dist/types/gjc-runtime/linux-proc.d.ts +3 -0
  70. package/dist/types/gjc-runtime/ralplan-runtime.d.ts +10 -6
  71. package/dist/types/gjc-runtime/session-state-lock.d.ts +104 -0
  72. package/dist/types/gjc-runtime/session-state-sidecar.d.ts +120 -2
  73. package/dist/types/gjc-runtime/state-writer.d.ts +10 -1
  74. package/dist/types/gjc-runtime/ultragoal-guard.d.ts +4 -2
  75. package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +8 -6
  76. package/dist/types/gjc-runtime/workflow-settings.d.ts +109 -0
  77. package/dist/types/hooks/events.d.ts +60 -0
  78. package/dist/types/hooks/normalize.d.ts +83 -0
  79. package/dist/types/hooks/skill-state.d.ts +9 -2
  80. package/dist/types/main.d.ts +1 -1
  81. package/dist/types/modes/acp/abort-scope.d.ts +13 -0
  82. package/dist/types/modes/acp/acp-agent.d.ts +7 -4
  83. package/dist/types/modes/components/customization/customization-dashboard.d.ts +44 -0
  84. package/dist/types/modes/components/customization/import-wizard.d.ts +21 -0
  85. package/dist/types/modes/components/customization/index.d.ts +1 -0
  86. package/dist/types/modes/components/gajae-pet-widget.d.ts +8 -30
  87. package/dist/types/modes/components/irc-sidebar.d.ts +1 -0
  88. package/dist/types/modes/components/iterm-pet-transport.d.ts +122 -0
  89. package/dist/types/modes/components/oauth-selector.d.ts +10 -4
  90. package/dist/types/modes/components/pet-capability.d.ts +11 -25
  91. package/dist/types/modes/components/status-line/gh.d.ts +6 -2
  92. package/dist/types/modes/controllers/extension-ui-controller.d.ts +1 -1
  93. package/dist/types/modes/controllers/selector-controller.d.ts +7 -8
  94. package/dist/types/modes/controllers/stt-controller.d.ts +1 -1
  95. package/dist/types/modes/interactive-mode.d.ts +1 -0
  96. package/dist/types/modes/types.d.ts +1 -0
  97. package/dist/types/modes/utils/injected-user-submission.d.ts +10 -0
  98. package/dist/types/runtime-mcp/config.d.ts +9 -0
  99. package/dist/types/runtime-mcp/manager.d.ts +26 -1
  100. package/dist/types/runtime-mcp/oauth-discovery.d.ts +4 -0
  101. package/dist/types/runtime-mcp/oauth-flow.d.ts +24 -1
  102. package/dist/types/runtime-mcp/pool-key.d.ts +2 -0
  103. package/dist/types/runtime-mcp/protocol.d.ts +278 -0
  104. package/dist/types/runtime-mcp/scope-config.d.ts +29 -0
  105. package/dist/types/runtime-mcp/tool-bridge.d.ts +15 -13
  106. package/dist/types/runtime-mcp/transports/http.d.ts +9 -0
  107. package/dist/types/runtime-mcp/types.d.ts +102 -10
  108. package/dist/types/sdk/acp/adapter.d.ts +26 -5
  109. package/dist/types/sdk/broker/broker.d.ts +3 -55
  110. package/dist/types/sdk/broker/lifecycle.d.ts +18 -12
  111. package/dist/types/sdk/broker/session-index.d.ts +14 -5
  112. package/dist/types/sdk/broker/startup-failure.d.ts +16 -1
  113. package/dist/types/sdk/bus/chat-daemon-control.d.ts +51 -1
  114. package/dist/types/sdk/bus/chat-daemon-runtime.d.ts +4 -41
  115. package/dist/types/sdk/bus/config.d.ts +11 -0
  116. package/dist/types/sdk/bus/conversation-store.d.ts +0 -1
  117. package/dist/types/sdk/bus/daemon-paths.d.ts +6 -0
  118. package/dist/types/sdk/bus/discord-conversation.d.ts +2 -0
  119. package/dist/types/sdk/bus/discord-daemon.d.ts +17 -12
  120. package/dist/types/sdk/bus/existing-thread-readiness.d.ts +4 -34
  121. package/dist/types/sdk/bus/inbound-reaction-ordering.d.ts +61 -0
  122. package/dist/types/sdk/bus/index.d.ts +85 -99
  123. package/dist/types/sdk/bus/kind-aware-reconciliation.d.ts +29 -7
  124. package/dist/types/sdk/bus/managed-daemon.d.ts +2 -32
  125. package/dist/types/sdk/bus/notification-service.d.ts +52 -0
  126. package/dist/types/sdk/bus/operator-runtime.d.ts +1 -1
  127. package/dist/types/sdk/bus/prompt-reconciliation.d.ts +16 -5
  128. package/dist/types/sdk/bus/reconciliation-store.d.ts +113 -5
  129. package/dist/types/sdk/bus/session-control.d.ts +25 -1
  130. package/dist/types/sdk/bus/slack-conversation.d.ts +4 -0
  131. package/dist/types/sdk/bus/slack-daemon.d.ts +9 -17
  132. package/dist/types/sdk/bus/slack-live-provider.d.ts +3 -1
  133. package/dist/types/sdk/bus/slack-provider.d.ts +4 -0
  134. package/dist/types/sdk/bus/slack-thread-binding.d.ts +6 -40
  135. package/dist/types/sdk/bus/telegram-adoption-intent.d.ts +30 -104
  136. package/dist/types/sdk/bus/telegram-daemon-cli.d.ts +10 -0
  137. package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +262 -19
  138. package/dist/types/sdk/bus/telegram-daemon-orphan-reap.d.ts +125 -0
  139. package/dist/types/sdk/bus/telegram-daemon-owner-registry.d.ts +22 -0
  140. package/dist/types/sdk/bus/telegram-daemon.d.ts +119 -310
  141. package/dist/types/sdk/bus/telegram-reference.d.ts +1 -47
  142. package/dist/types/sdk/bus/threaded-render.d.ts +1 -0
  143. package/dist/types/sdk/bus/topic-registry.d.ts +33 -17
  144. package/dist/types/sdk/cli/rows.d.ts +2 -11
  145. package/dist/types/sdk/cli/session-cli.d.ts +15 -25
  146. package/dist/types/sdk/client/client.d.ts +97 -1
  147. package/dist/types/sdk/host/control/dispatch.d.ts +0 -7
  148. package/dist/types/sdk/host/control/index.d.ts +1 -1
  149. package/dist/types/sdk/host/control/operations.d.ts +18 -5
  150. package/dist/types/sdk/host/control/runtime-gate.d.ts +19 -0
  151. package/dist/types/sdk/host/host.d.ts +15 -1
  152. package/dist/types/sdk/host/query/handlers.d.ts +5 -0
  153. package/dist/types/sdk/host/reverse-leases.d.ts +1 -1
  154. package/dist/types/sdk/host/session-runtime.d.ts +127 -7
  155. package/dist/types/sdk/host/types.d.ts +1 -1
  156. package/dist/types/sdk/host/websocket-transport.d.ts +1 -1
  157. package/dist/types/sdk/index.d.ts +2 -1
  158. package/dist/types/sdk/lifecycle/broker-client.d.ts +8 -0
  159. package/dist/types/sdk/lifecycle/client.d.ts +59 -0
  160. package/dist/types/sdk/lifecycle/index.d.ts +3 -0
  161. package/dist/types/sdk/lifecycle/service.d.ts +232 -0
  162. package/dist/types/sdk/mcp/server.d.ts +9 -6
  163. package/dist/types/sdk/prompt-deadline-lease.d.ts +43 -0
  164. package/dist/types/sdk/prompt-deadline-manager.d.ts +28 -0
  165. package/dist/types/sdk/prompt-status.d.ts +10 -3
  166. package/dist/types/sdk/protocol/operation-registry.d.ts +5 -0
  167. package/dist/types/sdk/receipt-state.d.ts +17 -0
  168. package/dist/types/sdk/reconciliation-extensions.d.ts +2 -0
  169. package/dist/types/sdk/router/index.d.ts +1 -0
  170. package/dist/types/sdk/router/session-router.d.ts +152 -0
  171. package/dist/types/sdk/session-list.d.ts +22 -0
  172. package/dist/types/sdk/session-reconnect.d.ts +43 -1
  173. package/dist/types/sdk/session.d.ts +34 -1
  174. package/dist/types/sdk/transport/serve-cli.d.ts +1 -4
  175. package/dist/types/session/account-inventory.d.ts +86 -0
  176. package/dist/types/session/agent-session.d.ts +145 -6
  177. package/dist/types/session/agent-storage.d.ts +10 -0
  178. package/dist/types/session/coordinator-tool-label.d.ts +28 -0
  179. package/dist/types/session/internal/managed-session-storage.d.ts +23 -4
  180. package/dist/types/session/messages.d.ts +1 -1
  181. package/dist/types/session/secure-token-file.d.ts +3 -0
  182. package/dist/types/session/session-manager.d.ts +40 -1
  183. package/dist/types/session/startup-auth-config.d.ts +44 -0
  184. package/dist/types/session/terminal-abort.d.ts +378 -0
  185. package/dist/types/session/yield-queue.d.ts +7 -0
  186. package/dist/types/session-import/claude.d.ts +26 -0
  187. package/dist/types/session-import/codex.d.ts +88 -0
  188. package/dist/types/session-import/command.d.ts +35 -0
  189. package/dist/types/session-import/detect.d.ts +22 -0
  190. package/dist/types/session-import/index.d.ts +7 -0
  191. package/dist/types/session-import/provider-codex.d.ts +22 -0
  192. package/dist/types/session-import/provider-service.d.ts +38 -0
  193. package/dist/types/session-import/redact.d.ts +23 -0
  194. package/dist/types/session-import/service.d.ts +47 -0
  195. package/dist/types/session-import/types.d.ts +120 -0
  196. package/dist/types/setup/paseo/check.d.ts +16 -0
  197. package/dist/types/setup/paseo/install-saga.d.ts +115 -0
  198. package/dist/types/setup/paseo/json-publisher.d.ts +76 -0
  199. package/dist/types/setup/paseo/orchestration-preferences.d.ts +24 -0
  200. package/dist/types/setup/paseo/paseo-ownership.d.ts +87 -0
  201. package/dist/types/setup/paseo/paseo-setup.d.ts +29 -0
  202. package/dist/types/setup/paseo/provider-config.d.ts +35 -0
  203. package/dist/types/setup/paseo/remove.d.ts +14 -0
  204. package/dist/types/setup/paseo/result-types.d.ts +59 -0
  205. package/dist/types/setup/paseo/setup-deps.d.ts +73 -0
  206. package/dist/types/setup/paseo/skills-bridge.d.ts +44 -0
  207. package/dist/types/skill-state/active-state.d.ts +5 -1
  208. package/dist/types/slash-commands/acp-builtins.d.ts +5 -1
  209. package/dist/types/slash-commands/builtin-registry.d.ts +2 -0
  210. package/dist/types/slash-commands/helpers/credential-switch.d.ts +12 -0
  211. package/dist/types/slash-commands/helpers/usage-report.d.ts +5 -6
  212. package/dist/types/slash-commands/types.d.ts +7 -1
  213. package/dist/types/task/executor.d.ts +10 -0
  214. package/dist/types/task/index.d.ts +3 -1
  215. package/dist/types/tools/ask-answer-registry.d.ts +6 -3
  216. package/dist/types/tools/bash-sleep-advisory.d.ts +14 -0
  217. package/dist/types/tools/bash.d.ts +2 -6
  218. package/dist/types/tools/browser/attach.d.ts +11 -0
  219. package/dist/types/tools/browser/launch.d.ts +20 -0
  220. package/dist/types/tools/browser/profile-discovery.d.ts +6 -0
  221. package/dist/types/tools/browser/render.d.ts +1 -0
  222. package/dist/types/tools/browser.d.ts +3 -1
  223. package/dist/types/tools/image-gen.d.ts +9 -2
  224. package/dist/types/tools/index.d.ts +8 -0
  225. package/dist/types/tools/monitor.d.ts +1 -1
  226. package/dist/types/tools/skill-discovery.d.ts +8 -1
  227. package/dist/types/tools/subagent.d.ts +2 -0
  228. package/dist/types/tools/ultragoal-ask-guard.d.ts +2 -2
  229. package/dist/types/utils/herdr-pane.d.ts +76 -0
  230. package/package.json +19 -9
  231. package/scripts/build-binary.ts +2 -1
  232. package/scripts/build-sdk-package-smoke.ts +2 -13
  233. package/scripts/capture-sticky-viewport-showcase.ts +0 -1
  234. package/scripts/compile-args.ts +2 -0
  235. package/scripts/generate-sdk-adapter-parity-manifest.ts +31 -16
  236. package/scripts/generate-sdk-operation-inventory.ts +28 -0
  237. package/scripts/generate-telegram-baseline-manifest.ts +1 -0
  238. package/scripts/generate-tool-catalog.ts +6 -4
  239. package/scripts/verify-gjc-sdk-canonicalization.ts +5 -39
  240. package/src/async/job-manager.ts +352 -17
  241. package/src/async/support.ts +25 -0
  242. package/src/autoresearch/dashboard.ts +28 -13
  243. package/src/capability/index.ts +3 -1
  244. package/src/capability/mcp.ts +6 -0
  245. package/src/capability/types.ts +6 -0
  246. package/src/cli/accounts-cli.ts +395 -0
  247. package/src/cli/args.ts +23 -0
  248. package/src/cli/auth-broker-cli.ts +136 -66
  249. package/src/cli/auth-gateway-cli.ts +94 -70
  250. package/src/cli/config-cli.ts +27 -2
  251. package/src/cli/crash-cli.ts +78 -0
  252. package/src/cli/customize-doctor.ts +1605 -0
  253. package/src/cli/daemon-cli.ts +12 -3
  254. package/src/cli/fast-help.ts +1 -0
  255. package/src/cli/mcp-cli.ts +128 -60
  256. package/src/cli/notify-cli.ts +8 -14
  257. package/src/cli/root-flags.ts +8 -0
  258. package/src/cli/setup-cli.ts +145 -0
  259. package/src/cli/skills-cli.ts +38 -2
  260. package/src/cli/update-cli.ts +167 -12
  261. package/src/cli.ts +4 -0
  262. package/src/commands/accounts.ts +71 -0
  263. package/src/commands/crash.ts +35 -0
  264. package/src/commands/customize.ts +84 -0
  265. package/src/commands/daemon.ts +2 -2
  266. package/src/commands/deep-interview.ts +2 -0
  267. package/src/commands/launch.ts +1 -0
  268. package/src/commands/mcp.ts +12 -7
  269. package/src/commands/ralplan.ts +2 -0
  270. package/src/commands/sdk.ts +65 -32
  271. package/src/commands/setup.ts +5 -0
  272. package/src/commands/skills.ts +14 -4
  273. package/src/commands/ultragoal.ts +10 -1
  274. package/src/commands/update.ts +26 -15
  275. package/src/config/atomic-yaml-patch-worker.ts +57 -0
  276. package/src/config/atomic-yaml-patch.ts +769 -29
  277. package/src/config/config-schema-version.ts +10 -0
  278. package/src/config/file-lock.ts +70 -1
  279. package/src/config/mcp-schema.json +52 -14
  280. package/src/config/model-profile-activation.ts +20 -1
  281. package/src/config/model-profiles.ts +29 -19
  282. package/src/config/model-registry.ts +132 -21
  283. package/src/config/models-config-schema.ts +1 -1
  284. package/src/config/settings-schema.ts +76 -6
  285. package/src/config/settings.ts +4038 -199
  286. package/src/config/skill-settings-defaults.ts +41 -3
  287. package/src/config/task-delegation.ts +87 -0
  288. package/src/config/update-channel.ts +23 -0
  289. package/src/coordinator-mcp/server.ts +377 -308
  290. package/src/crash/index-store.ts +723 -0
  291. package/src/crash/nudge.ts +89 -0
  292. package/src/crash/record-loader.ts +149 -0
  293. package/src/crash/report.ts +551 -0
  294. package/src/crash/sanitize.ts +129 -0
  295. package/src/cursor.ts +36 -10
  296. package/src/customization/import.ts +946 -0
  297. package/src/customization/index.ts +4 -0
  298. package/src/customization/inventory.ts +335 -0
  299. package/src/customization/mutations.ts +141 -0
  300. package/src/customization/types.ts +223 -0
  301. package/src/daemon/control-types.ts +8 -1
  302. package/src/daemon/operator-contract.ts +6 -6
  303. package/src/defaults/gjc/skills/deep-interview/SKILL.md +11 -22
  304. package/src/defaults/gjc/skills/ralplan/SKILL.md +36 -23
  305. package/src/defaults/gjc/skills/ultragoal/SKILL.md +16 -0
  306. package/src/discovery/claude-hooks.ts +38 -0
  307. package/src/discovery/claude.ts +142 -209
  308. package/src/discovery/codex-hooks.ts +38 -0
  309. package/src/discovery/codex.ts +132 -198
  310. package/src/discovery/helpers.ts +8 -1
  311. package/src/discovery/index.ts +2 -0
  312. package/src/discovery/mcp-compat.ts +234 -0
  313. package/src/discovery/mcp-json.ts +6 -0
  314. package/src/edit/renderer.ts +40 -0
  315. package/src/edit/streaming.ts +4 -2
  316. package/src/exec/bash-executor.ts +24 -20
  317. package/src/export/html/index.ts +51 -2
  318. package/src/extensibility/extensions/loader.ts +9 -1
  319. package/src/extensibility/extensions/runner.ts +17 -4
  320. package/src/extensibility/extensions/types.ts +31 -6
  321. package/src/extensibility/gjc-plugins/constrained-hooks.ts +33 -0
  322. package/src/extensibility/gjc-plugins/installer.ts +9 -5
  323. package/src/extensibility/gjc-plugins/observability.ts +8 -2
  324. package/src/extensibility/gjc-plugins/registry.ts +13 -2
  325. package/src/extensibility/gjc-plugins/runtime-adapters.ts +3 -1
  326. package/src/extensibility/gjc-plugins/schema.ts +222 -39
  327. package/src/extensibility/hooks/loader.ts +154 -10
  328. package/src/extensibility/runtime-skill-discovery.ts +257 -60
  329. package/src/extensibility/skill-management.ts +368 -0
  330. package/src/extensibility/skills.ts +52 -16
  331. package/src/gjc-runtime/deep-interview-runtime.ts +45 -61
  332. package/src/gjc-runtime/launch-worktree.ts +56 -2
  333. package/src/gjc-runtime/linux-proc.ts +7 -1
  334. package/src/gjc-runtime/ralplan-runtime.ts +100 -164
  335. package/src/gjc-runtime/session-state-lock.ts +992 -0
  336. package/src/gjc-runtime/session-state-sidecar.ts +644 -142
  337. package/src/gjc-runtime/state-writer.ts +39 -14
  338. package/src/gjc-runtime/tmux-owner-isolation.ts +5 -4
  339. package/src/gjc-runtime/ultragoal-guard.ts +11 -4
  340. package/src/gjc-runtime/ultragoal-runtime.ts +44 -41
  341. package/src/gjc-runtime/workflow-settings.ts +596 -0
  342. package/src/goals/tools/goal-tool.ts +6 -1
  343. package/src/hooks/events.ts +291 -0
  344. package/src/hooks/native-skill-hook.ts +4 -0
  345. package/src/hooks/normalize.ts +369 -0
  346. package/src/hooks/skill-state.ts +165 -46
  347. package/src/internal-urls/docs-index.generated.ts +40 -32
  348. package/src/main.ts +32 -5
  349. package/src/migrate/mcp-mapper.ts +25 -2
  350. package/src/modes/acp/abort-scope.ts +29 -0
  351. package/src/modes/acp/acp-agent.ts +423 -98
  352. package/src/modes/components/assistant-message.ts +6 -1
  353. package/src/modes/components/customization/customization-dashboard.ts +443 -0
  354. package/src/modes/components/customization/import-wizard.ts +372 -0
  355. package/src/modes/components/customization/index.ts +1 -0
  356. package/src/modes/components/gajae-pet-widget.ts +539 -213
  357. package/src/modes/components/irc-sidebar.ts +11 -0
  358. package/src/modes/components/iterm-pet-transport.ts +585 -0
  359. package/src/modes/components/oauth-selector.ts +277 -86
  360. package/src/modes/components/pet-capability.ts +39 -40
  361. package/src/modes/components/settings-selector.ts +9 -0
  362. package/src/modes/components/status-line/gh.ts +45 -1
  363. package/src/modes/components/status-line/segments.ts +16 -1
  364. package/src/modes/components/tool-status-header.ts +13 -3
  365. package/src/modes/controllers/command-controller.ts +20 -8
  366. package/src/modes/controllers/extension-ui-controller.ts +56 -257
  367. package/src/modes/controllers/input-controller.ts +10 -0
  368. package/src/modes/controllers/runtime-mcp-command-controller.ts +16 -3
  369. package/src/modes/controllers/selector-controller.ts +177 -72
  370. package/src/modes/controllers/stt-controller.ts +3 -3
  371. package/src/modes/interactive-mode.ts +88 -16
  372. package/src/modes/print-mode.ts +35 -6
  373. package/src/modes/runtime-init.ts +21 -68
  374. package/src/modes/types.ts +1 -0
  375. package/src/modes/utils/injected-user-submission.ts +25 -0
  376. package/src/modes/utils/terminal-bell.ts +15 -1
  377. package/src/prompts/agent-fragments/ralplan-persistence.md +1 -1
  378. package/src/prompts/agent-fragments/restricted-bash.md +1 -1
  379. package/src/prompts/agents/critic.md +1 -1
  380. package/src/prompts/system/rlm-research.md +1 -1
  381. package/src/prompts/system/system-prompt.md +0 -2
  382. package/src/prompts/tools/bash.md +7 -1
  383. package/src/prompts/tools/browser.md +1 -1
  384. package/src/prompts/tools/cron.md +6 -6
  385. package/src/prompts/tools/read.md +0 -2
  386. package/src/prompts/tools/skill-discovery.md +4 -2
  387. package/src/runtime-mcp/client.ts +427 -23
  388. package/src/runtime-mcp/config.ts +37 -5
  389. package/src/runtime-mcp/manager-pool.test.ts +56 -0
  390. package/src/runtime-mcp/manager.ts +52 -2
  391. package/src/runtime-mcp/oauth-discovery.ts +13 -0
  392. package/src/runtime-mcp/oauth-flow.ts +46 -1
  393. package/src/runtime-mcp/pool-key.ts +5 -1
  394. package/src/runtime-mcp/pool.test.ts +9 -0
  395. package/src/runtime-mcp/protocol.test.ts +353 -0
  396. package/src/runtime-mcp/protocol.ts +624 -0
  397. package/src/runtime-mcp/scope-config.ts +78 -0
  398. package/src/runtime-mcp/tool-bridge.ts +44 -10
  399. package/src/runtime-mcp/transports/http.ts +84 -13
  400. package/src/runtime-mcp/types.ts +123 -12
  401. package/src/sdk/acp/adapter.ts +328 -97
  402. package/src/sdk/broker/broker.ts +160 -741
  403. package/src/sdk/broker/ensure.ts +9 -1
  404. package/src/sdk/broker/lifecycle.ts +510 -116
  405. package/src/sdk/broker/operation-receipt-ledger.ts +11 -2
  406. package/src/sdk/broker/process-incarnation.ts +19 -0
  407. package/src/sdk/broker/session-index.ts +516 -117
  408. package/src/sdk/broker/startup-failure.ts +28 -5
  409. package/src/sdk/broker/transport.ts +1 -13
  410. package/src/sdk/bus/chat-daemon-cli.ts +4 -2
  411. package/src/sdk/bus/chat-daemon-control.ts +67 -6
  412. package/src/sdk/bus/chat-daemon-runtime.ts +183 -859
  413. package/src/sdk/bus/config.ts +13 -0
  414. package/src/sdk/bus/conversation-store.ts +59 -171
  415. package/src/sdk/bus/daemon-paths.ts +20 -0
  416. package/src/sdk/bus/discord-conversation.ts +2 -0
  417. package/src/sdk/bus/discord-daemon.ts +524 -126
  418. package/src/sdk/bus/discord-live-provider.ts +206 -79
  419. package/src/sdk/bus/existing-thread-readiness.ts +4 -97
  420. package/src/sdk/bus/inbound-reaction-ordering.ts +122 -0
  421. package/src/sdk/bus/index.ts +2480 -456
  422. package/src/sdk/bus/kind-aware-reconciliation.ts +204 -34
  423. package/src/sdk/bus/lifecycle-commands.ts +2 -2
  424. package/src/sdk/bus/managed-daemon.ts +2 -145
  425. package/src/sdk/bus/notification-service.ts +196 -0
  426. package/src/sdk/bus/operator-runtime.ts +1 -1
  427. package/src/sdk/bus/prompt-reconciliation.ts +24 -15
  428. package/src/sdk/bus/reconciliation-store.ts +553 -24
  429. package/src/sdk/bus/session-control.ts +49 -3
  430. package/src/sdk/bus/slack-conversation.ts +4 -0
  431. package/src/sdk/bus/slack-daemon.ts +733 -134
  432. package/src/sdk/bus/slack-live-provider.ts +65 -15
  433. package/src/sdk/bus/slack-provider.ts +4 -0
  434. package/src/sdk/bus/slack-thread-binding.ts +9 -104
  435. package/src/sdk/bus/telegram-adoption-intent.ts +299 -209
  436. package/src/sdk/bus/telegram-daemon-cli.ts +194 -7
  437. package/src/sdk/bus/telegram-daemon-contract.ts +265 -19
  438. package/src/sdk/bus/telegram-daemon-control.ts +3 -13
  439. package/src/sdk/bus/telegram-daemon-orphan-reap.ts +498 -0
  440. package/src/sdk/bus/telegram-daemon-owner-registry.ts +176 -0
  441. package/src/sdk/bus/telegram-daemon.ts +2552 -2280
  442. package/src/sdk/bus/telegram-reference.ts +1 -226
  443. package/src/sdk/bus/telegram-setup.ts +2 -2
  444. package/src/sdk/bus/threaded-render.ts +1 -0
  445. package/src/sdk/bus/topic-registry.ts +279 -146
  446. package/src/sdk/cli/rows.ts +2 -35
  447. package/src/sdk/cli/session-cli.ts +776 -700
  448. package/src/sdk/client/client.ts +892 -1
  449. package/src/sdk/host/control/dispatch.ts +93 -58
  450. package/src/sdk/host/control/index.ts +1 -1
  451. package/src/sdk/host/control/operations.ts +18 -10
  452. package/src/sdk/host/control/runtime-gate.ts +40 -0
  453. package/src/sdk/host/host.ts +63 -12
  454. package/src/sdk/host/query/handlers.ts +66 -7
  455. package/src/sdk/host/reverse-leases.ts +2 -2
  456. package/src/sdk/host/sdk-surface-parity.test.ts +1 -1
  457. package/src/sdk/host/session-runtime.test.ts +2787 -34
  458. package/src/sdk/host/session-runtime.ts +1489 -101
  459. package/src/sdk/host/surface-policy.ts +12 -8
  460. package/src/sdk/host/types.ts +1 -1
  461. package/src/sdk/host/websocket-transport.lifecycle.test.ts +31 -59
  462. package/src/sdk/host/websocket-transport.ts +85 -50
  463. package/src/sdk/index.ts +2 -1
  464. package/src/sdk/lifecycle/broker-client.ts +53 -0
  465. package/src/sdk/lifecycle/client.ts +234 -0
  466. package/src/sdk/lifecycle/index.ts +3 -0
  467. package/src/sdk/{bus/recent-activity.ts → lifecycle/recent-sessions.ts} +5 -2
  468. package/src/sdk/lifecycle/service.ts +720 -0
  469. package/src/sdk/mcp/server.ts +226 -188
  470. package/src/sdk/prompt-deadline-lease.ts +55 -0
  471. package/src/sdk/prompt-deadline-manager.ts +148 -0
  472. package/src/sdk/prompt-status.ts +11 -3
  473. package/src/sdk/protocol/operation-inventory.generated.json +272 -129
  474. package/src/sdk/protocol/operation-registry.ts +71 -8
  475. package/src/sdk/receipt-state.ts +43 -0
  476. package/src/sdk/reconciliation-extensions.ts +14 -0
  477. package/src/sdk/router/index.ts +1 -0
  478. package/src/sdk/router/session-router.ts +1658 -0
  479. package/src/sdk/session-list.ts +93 -0
  480. package/src/sdk/session-reconnect.ts +45 -1
  481. package/src/sdk/session.ts +716 -120
  482. package/src/sdk/transport/serve-cli.ts +18 -34
  483. package/src/session/account-inventory.ts +603 -0
  484. package/src/session/agent-session.ts +2857 -582
  485. package/src/session/agent-storage.ts +35 -0
  486. package/src/session/blob-store.ts +32 -1
  487. package/src/session/coordinator-tool-label.ts +40 -0
  488. package/src/session/internal/managed-session-scope.ts +4 -0
  489. package/src/session/internal/managed-session-storage.ts +173 -12
  490. package/src/session/messages.ts +1 -1
  491. package/src/session/secure-token-file.ts +80 -0
  492. package/src/session/session-manager.ts +730 -58
  493. package/src/session/session-storage.ts +25 -16
  494. package/src/session/startup-auth-config.ts +297 -0
  495. package/src/session/terminal-abort.ts +1253 -0
  496. package/src/session/yield-queue.ts +57 -15
  497. package/src/session-import/claude.ts +334 -0
  498. package/src/session-import/codex.ts +1033 -0
  499. package/src/session-import/command.ts +77 -0
  500. package/src/session-import/detect.ts +133 -0
  501. package/src/session-import/index.ts +7 -0
  502. package/src/session-import/provider-codex.ts +327 -0
  503. package/src/session-import/provider-service.ts +457 -0
  504. package/src/session-import/redact.ts +128 -0
  505. package/src/session-import/service.ts +1264 -0
  506. package/src/session-import/types.ts +148 -0
  507. package/src/setup/paseo/check.ts +213 -0
  508. package/src/setup/paseo/install-saga.ts +293 -0
  509. package/src/setup/paseo/json-publisher.ts +263 -0
  510. package/src/setup/paseo/orchestration-preferences.ts +86 -0
  511. package/src/setup/paseo/paseo-ownership.ts +224 -0
  512. package/src/setup/paseo/paseo-setup.ts +253 -0
  513. package/src/setup/paseo/provider-config.ts +139 -0
  514. package/src/setup/paseo/remove.ts +183 -0
  515. package/src/setup/paseo/result-types.ts +79 -0
  516. package/src/setup/paseo/setup-deps.ts +175 -0
  517. package/src/setup/paseo/skills-bridge.ts +260 -0
  518. package/src/skill-state/active-state.ts +21 -11
  519. package/src/slash-commands/acp-builtins.ts +9 -1
  520. package/src/slash-commands/builtin-registry.ts +98 -16
  521. package/src/slash-commands/helpers/credential-switch.ts +143 -0
  522. package/src/slash-commands/helpers/mcp.ts +9 -3
  523. package/src/slash-commands/helpers/usage-report.ts +61 -56
  524. package/src/slash-commands/types.ts +7 -2
  525. package/src/task/executor.ts +30 -11
  526. package/src/task/index.ts +66 -7
  527. package/src/task/render.ts +23 -9
  528. package/src/tools/ask-answer-registry.ts +23 -7
  529. package/src/tools/ask.ts +8 -4
  530. package/src/tools/bash-sleep-advisory.ts +115 -0
  531. package/src/tools/bash.ts +98 -18
  532. package/src/tools/browser/attach.ts +71 -3
  533. package/src/tools/browser/launch.ts +86 -13
  534. package/src/tools/browser/profile-discovery.ts +57 -7
  535. package/src/tools/browser/render.ts +8 -1
  536. package/src/tools/browser.ts +130 -21
  537. package/src/tools/computer.enforcement.test.ts +1 -1
  538. package/src/tools/computer.ts +3 -2
  539. package/src/tools/cron.ts +12 -1
  540. package/src/tools/image-gen.ts +27 -16
  541. package/src/tools/index.ts +8 -0
  542. package/src/tools/job.ts +33 -6
  543. package/src/tools/monitor.ts +41 -2
  544. package/src/tools/skill-discovery.ts +21 -29
  545. package/src/tools/subagent.ts +142 -24
  546. package/src/tools/tool-catalog.generated.ts +6 -6
  547. package/src/tools/ultragoal-ask-guard.ts +10 -3
  548. package/src/utils/herdr-pane.ts +345 -0
  549. package/src/utils/title-generator.ts +2 -0
  550. package/src/web/scrapers/index.ts +6 -4
  551. package/dist/types/cli/malloc-env-guard.test.d.ts +0 -1
  552. package/dist/types/defaults/gjc-defaults.test.d.ts +0 -1
  553. package/dist/types/memory-backend/lazy-loading.test.d.ts +0 -1
  554. package/dist/types/runtime/boot-ordering.test.d.ts +0 -1
  555. package/dist/types/runtime/lazy-service.test.d.ts +0 -1
  556. package/dist/types/runtime/network-prewarm-service.test.d.ts +0 -1
  557. package/dist/types/runtime/workspace-tree-service.test.d.ts +0 -1
  558. package/dist/types/runtime-mcp/config.test.d.ts +0 -1
  559. package/dist/types/runtime-mcp/http.test.d.ts +0 -1
  560. package/dist/types/runtime-mcp/manager-pool.test.d.ts +0 -1
  561. package/dist/types/runtime-mcp/pool-key.test.d.ts +0 -1
  562. package/dist/types/runtime-mcp/pool.test.d.ts +0 -1
  563. package/dist/types/sdk/bus/lifecycle-control-runtime.d.ts +0 -117
  564. package/dist/types/sdk/bus/lifecycle-orchestrator.d.ts +0 -141
  565. package/dist/types/sdk/bus/telegram-cli.d.ts +0 -31
  566. package/dist/types/sdk/elevation/allowlist.d.ts +0 -20
  567. package/dist/types/sdk/elevation/audit.d.ts +0 -93
  568. package/dist/types/sdk/elevation/capability.d.ts +0 -3
  569. package/dist/types/sdk/elevation/digest.d.ts +0 -72
  570. package/dist/types/sdk/elevation/dispatch-receipt.d.ts +0 -85
  571. package/dist/types/sdk/elevation/grant-ledger.d.ts +0 -163
  572. package/dist/types/sdk/elevation/owner.d.ts +0 -48
  573. package/dist/types/sdk/host/import-graph.test.d.ts +0 -1
  574. package/dist/types/sdk/host/sdk-surface-parity.test.d.ts +0 -1
  575. package/dist/types/sdk/host/session-runtime.test.d.ts +0 -1
  576. package/dist/types/sdk/host/websocket-transport.lifecycle.test.d.ts +0 -1
  577. package/dist/types/session/auth-broker-config.d.ts +0 -13
  578. package/dist/types/session/heap-eviction-artifacts.test.d.ts +0 -1
  579. package/dist/types/skills/skills.test.d.ts +0 -1
  580. package/dist/types/slash-commands/notify-command.test.d.ts +0 -1
  581. package/dist/types/task/token-log.test.d.ts +0 -1
  582. package/dist/types/tools/computer.enforcement.test.d.ts +0 -1
  583. package/dist/types/tools/descriptors.test.d.ts +0 -1
  584. package/scripts/g005-daemon-smoke.ts +0 -180
  585. package/scripts/g011-daemon-path-smoke.ts +0 -153
  586. package/src/sdk/bus/lifecycle-control-runtime.ts +0 -1733
  587. package/src/sdk/bus/lifecycle-orchestrator.ts +0 -489
  588. package/src/sdk/bus/telegram-cli.ts +0 -212
  589. package/src/sdk/elevation/allowlist.ts +0 -54
  590. package/src/sdk/elevation/audit.ts +0 -208
  591. package/src/sdk/elevation/capability.ts +0 -41
  592. package/src/sdk/elevation/digest.ts +0 -129
  593. package/src/sdk/elevation/dispatch-receipt.ts +0 -184
  594. package/src/sdk/elevation/grant-ledger.ts +0 -1142
  595. package/src/sdk/elevation/owner.ts +0 -109
  596. package/src/session/auth-broker-config.ts +0 -109
  597. /package/dist/types/{ai-core-import-gate.test.d.ts → discovery/claude-hooks.d.ts} +0 -0
  598. /package/dist/types/{cli/fixture-report.test.d.ts → discovery/codex-hooks.d.ts} +0 -0
  599. /package/dist/types/sdk/{bus/recent-activity.d.ts → lifecycle/recent-sessions.d.ts} +0 -0
@@ -18,6 +18,7 @@ import {
18
18
  type Message,
19
19
  type Model,
20
20
  type ProviderSessionState,
21
+ resolveOAuthStorageProvider,
21
22
  type SimpleStreamOptions,
22
23
  streamSimple,
23
24
  type ToolResultMessage,
@@ -38,9 +39,16 @@ import {
38
39
  providerSupportsAppendOnlyAuto,
39
40
  resolveAppendOnlyMode,
40
41
  } from "../append-only-mode";
41
- import { type AsyncJob, AsyncJobManager, isBackgroundJobSupportEnabled, jobElapsedMs } from "../async";
42
+ import {
43
+ type AsyncJob,
44
+ AsyncJobManager,
45
+ asyncJobEndpointId as deriveAsyncJobEndpointId,
46
+ isBackgroundJobSupportEnabled,
47
+ jobElapsedMs,
48
+ } from "../async";
42
49
  import { loadCapability } from "../capability";
43
50
  import { type Rule, ruleCapability, setActiveRules } from "../capability/rule";
51
+ import type { SourceMeta } from "../capability/types";
44
52
  import { resolveModelProfileName } from "../config/model-profile-contract";
45
53
  import { resolveProfileBindings } from "../config/model-profiles";
46
54
  import { kNoAuth, ModelRegistry } from "../config/model-registry";
@@ -54,6 +62,7 @@ import {
54
62
  } from "../config/model-resolver";
55
63
  import { loadPromptTemplates as loadPromptTemplatesInternal, type PromptTemplate } from "../config/prompt-templates";
56
64
  import { Settings, type SkillsSettings } from "../config/settings";
65
+ import { resolveEagerTaskDelegation } from "../config/task-delegation";
57
66
  import { CursorExecHandlers } from "../cursor";
58
67
  import type { BashRestrictionProfile } from "../tools/bash-allowed-prefixes";
59
68
  import "../discovery";
@@ -93,9 +102,11 @@ import {
93
102
  gjcActivationGenerationFor,
94
103
  } from "../extensibility/gjc-plugins/runtime-quarantine";
95
104
  import { loadActiveSubskillTools } from "../extensibility/gjc-plugins/tools";
105
+ import { discoverAndLoadHookExtensions } from "../extensibility/hooks/loader";
96
106
  import { loadSkills, type Skill, type SkillWarning, setActiveSkills } from "../extensibility/skills";
97
107
  import type { FileSlashCommand } from "../extensibility/slash-commands";
98
108
  import type { HindsightSessionState } from "../hindsight/state";
109
+ import { normalizePluginHook } from "../hooks/normalize";
99
110
  import { initializeLocalRoot, LocalProtocolHandler, type LocalProtocolOptions } from "../internal-urls";
100
111
  import type { LspStartupServerInfo } from "../lsp";
101
112
  import btwUserPrompt from "../prompts/system/btw-user.md" with { type: "text" };
@@ -106,7 +117,8 @@ import {
106
117
  createOptionalRuntimeServices,
107
118
  type OptionalRuntimeServicesOverrides,
108
119
  } from "../runtime/optional-runtime-services";
109
- import { MCPManager } from "../runtime-mcp";
120
+ import { loadAllMCPConfigs, MCPManager } from "../runtime-mcp";
121
+ import type { MCPServerConfig } from "../runtime-mcp/types";
110
122
  import {
111
123
  getNotificationConfig,
112
124
  isGenericNotificationHostEligible,
@@ -114,15 +126,29 @@ import {
114
126
  SPAWN_PROVENANCE_ENV,
115
127
  shouldRegisterGenericNotificationsExtension,
116
128
  } from "../sdk/bus/config";
129
+ import { createReconciliationStore, type ReconciliationStore } from "../sdk/bus/reconciliation-store";
117
130
  import { NotificationSessionController } from "../sdk/bus/session-control";
118
131
  import { shouldHostSdk } from "../sdk/host";
132
+ import { createSdkSessionRuntimeExtension, registerSdkOnlyNotificationCommand } from "../sdk/host/session-runtime";
133
+ import { createSdkWebSocketTransport } from "../sdk/host/websocket-transport";
119
134
 
120
135
  import type { SecretObfuscator } from "../secrets";
121
136
  import { AgentSession, type ForkContextSeed } from "../session/agent-session";
122
- import { resolveAuthBrokerConfig } from "../session/auth-broker-config";
123
137
  import { AuthBrokerClient, AuthStorage, RemoteAuthCredentialStore } from "../session/auth-storage";
124
138
  import { type CustomMessage, convertToLlm } from "../session/messages";
125
139
  import { createReadonlySessionManager, SessionManager } from "../session/session-manager";
140
+ import {
141
+ parsePersistedCredentialSelector,
142
+ resolveStartupAuthConfig,
143
+ type StartupAuthConfigSnapshot,
144
+ } from "../session/startup-auth-config";
145
+ import {
146
+ isOwnedCompletionEnvelopeAllowed,
147
+ lookupOwnedRegistration,
148
+ type OwnedCompletionEnvelope,
149
+ retireOwnedRegistrationsForEndpoint,
150
+ unregisterOwnedRegistration,
151
+ } from "../session/terminal-abort";
126
152
  import { formatNoModelsAvailableFallback } from "../setup/model-onboarding-guidance";
127
153
  import {
128
154
  type BuildSystemPromptResult,
@@ -163,6 +189,8 @@ type AsyncResultEntry = {
163
189
  result: string;
164
190
  job: AsyncJob | undefined;
165
191
  durationMs: number | undefined;
192
+ /** Exact owned-completion origin when the job is registered left-running work of a terminal turn. */
193
+ ownedCompletion?: OwnedCompletionEnvelope;
166
194
  };
167
195
 
168
196
  type AsyncResultJobDetails = {
@@ -174,6 +202,8 @@ type AsyncResultJobDetails = {
174
202
 
175
203
  type AsyncResultDetails = {
176
204
  jobs: AsyncResultJobDetails[];
205
+ /** Private origin envelope(s); absent = ordinary delivery. Never a public field. */
206
+ ownedCompletions?: OwnedCompletionEnvelope[];
177
207
  };
178
208
 
179
209
  type McpNotificationEntry = {
@@ -183,13 +213,53 @@ type McpNotificationEntry = {
183
213
 
184
214
  function buildAsyncResultBatchMessage(entries: AsyncResultEntry[]): CustomMessage<AsyncResultDetails> | null {
185
215
  if (entries.length === 0) return null;
186
- const jobs = entries.map(entry => ({
216
+ // Partition denied owned-completion entries out ENTIRELY before batch
217
+ // construction (AC 36 zero final calls from stopped work): a denied entry —
218
+ // owned scope, forged tuple, or vanished scope — must never reach
219
+ // followUp/prompt, even inside a mixed batch. Allowed owned-completion and
220
+ // ordinary entries are delivered normally.
221
+ const survivors = entries.filter(
222
+ entry => entry.ownedCompletion === undefined || isOwnedCompletionEnvelopeAllowed(entry.ownedCompletion),
223
+ );
224
+ // Denied entries never reach followUp/prompt (AC 36), so no later
225
+ // settlement boundary sees them — retire their terminal tuples HERE,
226
+ // otherwise an owned_unsettled abort's later completion keeps occupying
227
+ // the global registration and retained-policy capacities (review P2).
228
+ const denied = entries.filter(
229
+ (entry): entry is AsyncResultEntry & { ownedCompletion: OwnedCompletionEnvelope } =>
230
+ entry.ownedCompletion !== undefined && !isOwnedCompletionEnvelopeAllowed(entry.ownedCompletion),
231
+ );
232
+ if (denied.length > 0) {
233
+ // Resolve the manager from the registration's OWN endpoint: the
234
+ // process-global instance is the last-created session, so when B's
235
+ // manager is global with the same local job id still running, the
236
+ // terminality check would observe B's running job and refuse to
237
+ // unregister A's already-terminal tuple (review thread P2).
238
+ const manager =
239
+ AsyncJobManager.forEndpoint(denied[0]?.ownedCompletion.registration.endpointId) ?? AsyncJobManager.instance();
240
+ for (const entry of denied) {
241
+ const registration = entry.ownedCompletion.registration;
242
+ const job = manager?.getJob(registration.jobId);
243
+ const status = job?.generation === registration.jobGeneration ? job?.status : undefined;
244
+ if (job === undefined || status === "completed" || status === "cancelled" || status === "failed") {
245
+ unregisterOwnedRegistration(registration);
246
+ }
247
+ }
248
+ }
249
+ if (survivors.length === 0) return null;
250
+ const jobs = survivors.map(entry => ({
187
251
  jobId: entry.jobId,
188
252
  result: entry.result,
189
253
  type: entry.job?.type,
190
254
  label: entry.job?.label,
191
255
  durationMs: entry.durationMs,
192
256
  }));
257
+ const ownedCompletions = survivors
258
+ .filter(
259
+ (entry): entry is AsyncResultEntry & { ownedCompletion: OwnedCompletionEnvelope } =>
260
+ entry.ownedCompletion !== undefined,
261
+ )
262
+ .map(entry => entry.ownedCompletion);
193
263
  const details: AsyncResultDetails = {
194
264
  jobs: jobs.map(job => ({
195
265
  jobId: job.jobId,
@@ -197,6 +267,10 @@ function buildAsyncResultBatchMessage(entries: AsyncResultEntry[]): CustomMessag
197
267
  label: job.label,
198
268
  durationMs: job.durationMs,
199
269
  })),
270
+ // Private origin envelope for the AgentSession injector; absent for
271
+ // ordinary deliveries. Only ALLOWED owned-completion entries survive
272
+ // partitioning, so the injector never sees a denied envelope here.
273
+ ...(ownedCompletions.length > 0 ? { ownedCompletions } : {}),
200
274
  };
201
275
  return {
202
276
  role: "custom",
@@ -224,6 +298,20 @@ function buildAsyncResultBatchMessage(entries: AsyncResultEntry[]): CustomMessag
224
298
  * result for any such trailing unpaired tool call so a resumed session always
225
299
  * starts from a valid, paired history. No-op for well-formed transcripts.
226
300
  */
301
+ /** Whether the cached SDK-only reconciliation store still matches the current
302
+ * session identity. session_switch/session_branch can move to a DIFFERENT
303
+ * transcript that retains the same copied session id, so the cache is keyed
304
+ * by BOTH the session id and the session-file path (including a null-to-file
305
+ * transition); a mismatch means the store must be recreated or the successor
306
+ * reads/writes the predecessor's reconciliation file (review thread P2). */
307
+ export function sdkOnlyStoreMatches(
308
+ cached: { sessionId: string; sessionFile: string | undefined } | undefined,
309
+ sessionId: string,
310
+ sessionFile: string | undefined,
311
+ ): boolean {
312
+ return cached !== undefined && cached.sessionId === sessionId && cached.sessionFile === sessionFile;
313
+ }
314
+
227
315
  export function reconcileTrailingToolCalls(messages: AgentMessage[]): AgentMessage[] {
228
316
  let lastAssistantIdx = -1;
229
317
  for (let i = messages.length - 1; i >= 0; i--) {
@@ -345,6 +433,10 @@ export interface CreateAgentSessionOptions {
345
433
  credentialSessionId?: string;
346
434
  /** Runtime credential selector for multi-account auth pools. */
347
435
  credentialSelector?: { provider?: string; selector: AuthCredentialSelector; raw: string };
436
+ /** Soft runtime credential preference; quota/rate-limit failures may rotate away from it. */
437
+ preferredCredentialSelector?: { provider?: string; selector: AuthCredentialSelector; raw: string };
438
+ /** Durable global pin intent loaded from startup-auth config; session bootstrap resolves it without mutating shared storage. */
439
+ startupAuthConfig?: StartupAuthConfigSnapshot;
348
440
 
349
441
  /** Custom tools to register (in addition to built-in tools). Accepts both CustomTool and ToolDefinition. */
350
442
  customTools?: (CustomTool | ToolDefinition)[];
@@ -354,6 +446,8 @@ export interface CreateAgentSessionOptions {
354
446
  extensions?: ExtensionFactory[];
355
447
  /** Additional extension paths to load (merged with discovery). */
356
448
  additionalExtensionPaths?: string[];
449
+ /** Explicit hook module paths to load in addition to native discovery. */
450
+ hookPaths?: string[];
357
451
  /** Disable extension discovery (explicit paths still load). */
358
452
  disableExtensionDiscovery?: boolean;
359
453
  /**
@@ -386,6 +480,14 @@ export interface CreateAgentSessionOptions {
386
480
  /** Load MCP tools for a top-level session only from this caller-owned absolute config file path.
387
481
  * Mutually exclusive with mcpManager. */
388
482
  mcpConfigPath?: string;
483
+ /**
484
+ * Whether conventional MCP autoload is enabled for a top-level standalone
485
+ * session (default: true). When false, native user `~/.gjc/agent/mcp.json`
486
+ * and project `.gjc/mcp.json` registrations are not discovered or connected
487
+ * at startup. Plugin-bundle MCPs and `mcpConfigPath` exact-file sessions
488
+ * are unaffected. CLI: `--no-mcp`.
489
+ */
490
+ enableMcpAutoload?: boolean;
389
491
  /**
390
492
  * Defer connecting an exact MCP config until the interactive UI is ready.
391
493
  * @internal CLI-only startup optimization; SDK callers retain synchronous loading by default.
@@ -439,6 +541,8 @@ export interface CreateAgentSessionOptions {
439
541
  agentRegistry?: AgentRegistry;
440
542
  /** Parent task ID prefix for nested artifact naming (e.g., "6-Extensions") */
441
543
  parentTaskPrefix?: string;
544
+ /** Parent manager borrowed by a child session; never disposed by the child. */
545
+ inheritedAsyncJobManager?: AsyncJobManager;
442
546
  /**
443
547
  * W6b: the parent's scope-held MCP facade, handed to a canonical sub-session so
444
548
  * it can inherit always-on MCP tools without owning the manager. Replaces the
@@ -554,9 +658,13 @@ function getDefaultAgentDir(): string {
554
658
  * back into the broker through the {@link AuthStorageOptions.refreshOAuthCredential}
555
659
  * override to re-mint access tokens when needed.
556
660
  */
557
- export async function discoverAuthStorage(agentDir: string = getDefaultAgentDir()): Promise<AuthStorage> {
558
- const brokerConfig = await resolveAuthBrokerConfig();
559
- const credentialRankingMode = resolveCredentialRankingMode();
661
+ export async function discoverAuthStorage(
662
+ agentDir: string = getDefaultAgentDir(),
663
+ startupAuth?: StartupAuthConfigSnapshot,
664
+ ): Promise<AuthStorage> {
665
+ const resolvedStartupAuth = startupAuth ?? (await resolveStartupAuthConfig(agentDir));
666
+ const brokerConfig = resolvedStartupAuth.broker;
667
+ const credentialRankingMode = resolvedStartupAuth.credentialRankingMode;
560
668
  if (brokerConfig) {
561
669
  const client = new AuthBrokerClient({ url: brokerConfig.url, token: brokerConfig.token });
562
670
  const initialResult = await client.fetchSnapshot();
@@ -600,18 +708,7 @@ export async function discoverAuthStorage(agentDir: string = getDefaultAgentDir(
600
708
  return storage;
601
709
  }
602
710
 
603
- /**
604
- * Opt-in multi-account credential ranking mode, read from the
605
- * `GJC_CREDENTIAL_RANKING_MODE` env var. Unset/unknown → `undefined`, leaving
606
- * {@link AuthStorage}'s default (`balanced`) untouched. `earliest-reset`
607
- * switches to earliest-expiry-first selection so soon-to-reset tumbling-window
608
- * quota is drained before it is lost.
609
- */
610
- function resolveCredentialRankingMode(): "balanced" | "earliest-reset" | undefined {
611
- const raw = process.env.GJC_CREDENTIAL_RANKING_MODE?.trim();
612
- if (raw === "balanced" || raw === "earliest-reset") return raw;
613
- return undefined;
614
- }
711
+ /** Ranking is resolved as part of the typed startup-auth snapshot. */
615
712
 
616
713
  /**
617
714
  * Discover extensions from cwd.
@@ -847,12 +944,26 @@ function createCustomToolsExtension(tools: CustomTool[]): ExtensionFactory {
847
944
 
848
945
  export function createPluginHooksExtension(hooks: ConstrainedPluginHook[]): ExtensionFactory {
849
946
  return api => {
850
- for (const hook of hooks) {
851
- // Constrained hooks have one explicit execution phase. `tool_call` is
852
- // pre-execution; an after-phase tool_call hook is registered on the
853
- // post-execution tool_result event so it cannot block the tool.
854
- const registrationEvent = hook.event === "tool_call" && hook.phase === "after" ? "tool_result" : hook.event;
855
- const target = hook.target;
947
+ const normalizedHooks = hooks.map(hook => ({
948
+ hook,
949
+ result: normalizePluginHook({
950
+ declaredEvent: hook.event,
951
+ target: hook.target,
952
+ phase: hook.phase,
953
+ plugin: hook.plugin,
954
+ source: `plugin:${hook.plugin}`,
955
+ }),
956
+ }));
957
+ const diagnostics = normalizedHooks.flatMap(entry => entry.result.diagnostics);
958
+ if (normalizedHooks.some(entry => !entry.result.hook)) {
959
+ throw new Error(diagnostics.map(diagnostic => `${diagnostic.code}: ${diagnostic.message}`).join("; "));
960
+ }
961
+
962
+ for (const { hook, result } of normalizedHooks) {
963
+ const normalized = result.hook;
964
+ if (!normalized) throw new Error("Hook normalization invariant violated");
965
+ const registrationEvent = normalized.runtimeEvent;
966
+ const target = normalized.toolName === "*" ? undefined : normalized.toolName;
856
967
  const handler = target
857
968
  ? (event: { toolName?: string; tool?: { name?: string }; name?: string }, ...rest: unknown[]) => {
858
969
  const toolName = event?.toolName ?? event?.tool?.name ?? event?.name;
@@ -949,10 +1060,11 @@ function buildMCPPromptCommands(manager: MCPManager): LoadedCustomCommand[] {
949
1060
 
950
1061
  function withEmbeddedDefaultGjcSkills(skills: Skill[]): Skill[] {
951
1062
  const byName = new Map(skills.map(skill => [skill.name, skill]));
1063
+ // The four public GJC workflow skills are a product invariant: even if a
1064
+ // caller-supplied or filesystem skill shares a name, the bundled definition
1065
+ // wins so workflow routing can never be silently hijacked.
952
1066
  for (const defaultSkill of getEmbeddedDefaultGjcSkills()) {
953
- if (!byName.has(defaultSkill.name)) {
954
- byName.set(defaultSkill.name, defaultSkill);
955
- }
1067
+ byName.set(defaultSkill.name, defaultSkill);
956
1068
  }
957
1069
  return [...byName.values()];
958
1070
  }
@@ -983,6 +1095,7 @@ const MAX_EXACT_MCP_TOOL_COLLISION_NAMES = 10;
983
1095
  const DEFERRED_MCP_CONFIG_STARTUP_ERROR = "MCP tools could not be loaded.";
984
1096
  const MAX_EXACT_MCP_TOOL_NAME_LENGTH = 100;
985
1097
  const pluginMcpManagerServers = new WeakMap<MCPManager, ReadonlySet<string>>();
1098
+ const conventionalMcpManagerServers = new WeakMap<MCPManager, ReadonlySet<string>>();
986
1099
 
987
1100
  class ExactMcpToolNameCollisionError extends Error {
988
1101
  constructor(toolNames: Iterable<string>) {
@@ -1075,6 +1188,11 @@ function safeErrorForLog(value: unknown): unknown {
1075
1188
  return safeErrorDescription(value);
1076
1189
  }
1077
1190
 
1191
+ function sanitizeProviderForLog(provider: string): string {
1192
+ const sanitized = provider.replace(/[^\x20-\x7e]/g, "?").slice(0, 128);
1193
+ return sanitized.length > 0 ? sanitized : "<empty>";
1194
+ }
1195
+
1078
1196
  function attachMcpCleanupDiagnostic(primary: unknown, cleanup: unknown): unknown {
1079
1197
  const diagnostic = { code: "MCP_MANAGER_CLEANUP_FAILED" as const, cause: cleanup };
1080
1198
  if (primary && (typeof primary === "object" || typeof primary === "function")) {
@@ -1158,14 +1276,23 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1158
1276
  const explicitMcpConfigPath = !isCanonicalSubSession && !options.mcpManager ? options.mcpConfigPath : undefined;
1159
1277
  const agentDir = options.agentDir ?? getDefaultAgentDir();
1160
1278
  const eventBus = options.eventBus ?? new EventBus();
1279
+ const hasInjectedAuth = options.authStorage !== undefined || options.modelRegistry !== undefined;
1161
1280
 
1162
1281
  // Pin authStorage to modelRegistry.authStorage: ModelRegistry.getApiKey() routes refresh
1163
1282
  // failures through that instance, so any divergent storage handed to the bridge / mcpManager
1164
1283
  // / session would silently miss credential_disabled events.
1284
+ // Injected auth is already the caller's authority; do not discover global
1285
+ // startup auth or apply persistent pins while constructing this session.
1286
+ const startupAuthConfig = hasInjectedAuth
1287
+ ? undefined
1288
+ : (options.startupAuthConfig ?? (await resolveStartupAuthConfig(agentDir)));
1165
1289
  const ownsAuthStorage = options.modelRegistry === undefined && options.authStorage === undefined;
1166
1290
  const modelRegistry =
1167
1291
  options.modelRegistry ??
1168
- new ModelRegistry(options.authStorage ?? (await logger.time("discoverModels", discoverAuthStorage, agentDir)));
1292
+ new ModelRegistry(
1293
+ options.authStorage ??
1294
+ (await logger.time("discoverModels", () => discoverAuthStorage(agentDir, startupAuthConfig))),
1295
+ );
1169
1296
  const authStorage = modelRegistry.authStorage;
1170
1297
  if (options.authStorage && options.authStorage !== authStorage) {
1171
1298
  throw new Error(
@@ -1177,6 +1304,9 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1177
1304
  let session!: AgentSession;
1178
1305
  let hasSession = false;
1179
1306
  let hasRegistered = false;
1307
+ let asyncJobManager: AsyncJobManager | undefined;
1308
+ let asyncJobManagerAdmitted = false;
1309
+ let priorAsyncJobManager: AsyncJobManager | undefined;
1180
1310
  let cleanupOwnedMcpManager: (() => Promise<void>) | undefined;
1181
1311
  const agentRegistry = options.agentRegistry ?? AgentRegistry.global();
1182
1312
  const resolvedAgentId = options.agentId ?? options.parentTaskPrefix ?? MAIN_AGENT_ID;
@@ -1196,7 +1326,13 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1196
1326
  };
1197
1327
  const enableLsp = options.enableLsp ?? true;
1198
1328
  let authStorageClosed = false;
1329
+ let credentialScopeId: string | undefined;
1330
+ let credentialScopeLeased = false;
1199
1331
  const closeOwnedAuthStorage = (): void => {
1332
+ if (!hasSession && credentialScopeLeased && credentialScopeId) {
1333
+ authStorage.releaseCredentialScope(credentialScopeId);
1334
+ credentialScopeLeased = false;
1335
+ }
1200
1336
  if (!ownsAuthStorage || authStorageClosed) return;
1201
1337
  authStorageClosed = true;
1202
1338
  authStorage.close();
@@ -1223,23 +1359,14 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1223
1359
  startupCredentialDisabledEvents.push(event);
1224
1360
  }
1225
1361
  });
1226
- let runtimeCredentialSelectorInstalled = false;
1227
- const installRuntimeCredentialSelector = (provider: string): void => {
1228
- if (!options.credentialSelector || runtimeCredentialSelectorInstalled) return;
1229
- authStorage.setRuntimeCredentialSelector(provider, options.credentialSelector.selector);
1230
- runtimeCredentialSelectorInstalled = true;
1362
+ const applyCredentialSelector = (scopeId: string, provider: string, selector: AuthCredentialSelector): void => {
1363
+ authStorage.setSessionCredentialSelector(scopeId, provider, selector);
1231
1364
  };
1232
- const earlyCredentialSelectorProvider = options.credentialSelector?.provider ?? options.model?.provider;
1233
- if (earlyCredentialSelectorProvider) {
1234
- installRuntimeCredentialSelector(earlyCredentialSelectorProvider);
1235
- }
1236
1365
  const settings = options.settings ?? (await logger.time("settings", Settings.init, { cwd, agentDir }));
1237
1366
  const runtimeServices = createOptionalRuntimeServices(settings, options.runtimeServices, { cwd });
1238
1367
  modelRegistry.applyConfiguredModelBindings(settings);
1239
1368
  logger.time("initializeWithSettings", initializeWithSettings, settings);
1240
- const canRefreshModelsBeforeCredentialSelector =
1241
- !options.credentialSelector || runtimeCredentialSelectorInstalled || options.modelRegistry !== undefined;
1242
- if (!options.modelRegistry && canRefreshModelsBeforeCredentialSelector) {
1369
+ if (!options.modelRegistry) {
1243
1370
  modelRegistry.refreshInBackground();
1244
1371
  }
1245
1372
  // Resolve the workspace tree through its runtime service. The compatibility
@@ -1304,7 +1431,46 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1304
1431
  // session_id upstream, where session-owning transports reject the extra
1305
1432
  // downstreams (owner_busy) and degrade those turns to uncached HTTP.
1306
1433
  const providerSessionId = options.providerSessionId ?? logicalSessionId;
1434
+ // AsyncJobManager ownership is distinct from both the persisted logical
1435
+ // header and provider cache affinity. Managed child transcripts may share a
1436
+ // logical header, while unrelated top-level sessions may intentionally share
1437
+ // providerSessionId. Bind explicit provider scopes to the independently
1438
+ // persisted transcript path so ownership is collision-free and stable across
1439
+ // detached resume; ordinary sessions keep the transition-aware logical id.
1440
+ const sessionFile = sessionManager.getSessionFile();
1441
+ const asyncJobEndpointId = deriveAsyncJobEndpointId(
1442
+ options.providerSessionId === undefined ? undefined : providerSessionId,
1443
+ logicalSessionId,
1444
+ sessionFile,
1445
+ );
1307
1446
  const credentialSessionId = options.credentialSessionId ?? providerSessionId;
1447
+ credentialScopeId = credentialSessionId;
1448
+
1449
+ const scopeAlreadyLeased = authStorage.hasCredentialScopeLease(credentialSessionId);
1450
+ authStorage.acquireCredentialScope(credentialSessionId);
1451
+ credentialScopeLeased = true;
1452
+ const configuredPins = hasInjectedAuth ? {} : (startupAuthConfig?.credentialPins ?? {});
1453
+ const persistedPinStoreMatches =
1454
+ startupAuthConfig?.credentialPinStoreIdentity === startupAuthConfig?.credentialStoreIdentity;
1455
+ // AuthStorage has no scope-level "pinned but unavailable" marker. Keep the
1456
+ // discarded durable pin identity local to this credential scope so startup
1457
+ // config cannot immediately replace it with a different account; later
1458
+ // request-time resolution for this provider remains ordinary AUTO ranking.
1459
+ const staleDurableCredentialPins = new Set<string>();
1460
+ if (options.credentialSelector) {
1461
+ const provider = options.credentialSelector.provider ?? options.model?.provider;
1462
+ if (provider) applyCredentialSelector(credentialSessionId, provider, options.credentialSelector.selector);
1463
+ }
1464
+ const preferredCredentialProvider = options.preferredCredentialSelector
1465
+ ? (options.preferredCredentialSelector.provider ??
1466
+ authStorage.resolveRuntimePreferredCredentialSelectorProvider(options.preferredCredentialSelector.selector))
1467
+ : undefined;
1468
+ if (options.preferredCredentialSelector && preferredCredentialProvider) {
1469
+ authStorage.setRuntimePreferredCredentialSelector(
1470
+ preferredCredentialProvider,
1471
+ options.preferredCredentialSelector.selector,
1472
+ );
1473
+ }
1308
1474
  const modelApiKeyAvailability = new Map<string, boolean>();
1309
1475
  const getModelAvailabilityKey = (candidate: Model): string =>
1310
1476
  `${candidate.provider}\u0000${candidate.baseUrl ?? ""}`;
@@ -1315,17 +1481,22 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1315
1481
  return cached;
1316
1482
  }
1317
1483
 
1318
- const credentialSelector =
1319
- options.credentialSelector && !runtimeCredentialSelectorInstalled
1320
- ? options.credentialSelector.provider === undefined ||
1321
- options.credentialSelector.provider === candidate.provider
1322
- ? options.credentialSelector.selector
1323
- : undefined
1324
- : undefined;
1484
+ const credentialSelector = options.credentialSelector?.provider
1485
+ ? undefined
1486
+ : options.credentialSelector?.selector;
1325
1487
  if (options.credentialSelector?.provider && options.credentialSelector.provider !== candidate.provider) {
1326
1488
  modelApiKeyAvailability.set(availabilityKey, false);
1327
1489
  return false;
1328
1490
  }
1491
+ // A preferred (soft) credential is always installed synchronously above, so
1492
+ // availability just needs to exclude candidates from a different provider —
1493
+ // `--prefer-credential` names one provider's account, and letting a
1494
+ // different-provider default model win here would silently strand the
1495
+ // preference.
1496
+ if (preferredCredentialProvider && preferredCredentialProvider !== candidate.provider) {
1497
+ modelApiKeyAvailability.set(availabilityKey, false);
1498
+ return false;
1499
+ }
1329
1500
  const key = await modelRegistry
1330
1501
  .getApiKey(candidate, credentialSessionId, { credentialSelector })
1331
1502
  .catch(error => {
@@ -1373,6 +1544,83 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1373
1544
  const hasThinkingEntry = existingBranch.some(entry => entry.type === "thinking_level_change");
1374
1545
  const hasServiceTierEntry = existingBranch.some(entry => entry.type === "service_tier_change");
1375
1546
 
1547
+ for (const entry of existingBranch) {
1548
+ if (entry.type !== "custom" || entry.customType !== "auth-credential-pin") continue;
1549
+ const data = entry.data;
1550
+ if (!data || typeof data !== "object") continue;
1551
+ const record = data as {
1552
+ v?: unknown;
1553
+ scopeId?: unknown;
1554
+ provider?: unknown;
1555
+ pin?: unknown;
1556
+ credentialStoreIdentity?: unknown;
1557
+ };
1558
+ if (record.v !== 1 || record.scopeId !== credentialSessionId || typeof record.provider !== "string") continue;
1559
+ const pin = record.pin as { auto?: unknown; kind?: unknown; value?: unknown } | undefined;
1560
+ // Durable replay must never abort session startup: a pinned account may
1561
+ // have been removed, disabled, or deduplicated since the pin was written.
1562
+ // Pin identity is preserved by leaving the provider unpinned on failure —
1563
+ // never silently retarget to another account; the user re-pins or picks AUTO.
1564
+ try {
1565
+ if (pin?.auto === true) {
1566
+ authStorage.setSessionCredentialAuto(record.provider, credentialSessionId);
1567
+ staleDurableCredentialPins.delete(resolveOAuthStorageProvider(record.provider));
1568
+ } else if (
1569
+ pin &&
1570
+ (pin.kind === "id" || pin.kind === "email" || pin.kind === "account" || pin.kind === "project") &&
1571
+ typeof pin.value === "string"
1572
+ ) {
1573
+ if (
1574
+ pin.kind === "id" &&
1575
+ (!startupAuthConfig?.credentialStoreIdentity ||
1576
+ record.credentialStoreIdentity !== startupAuthConfig.credentialStoreIdentity)
1577
+ ) {
1578
+ throw new Error("Durable numeric credential pin authority changed");
1579
+ }
1580
+ authStorage.setSessionCredentialSelector(credentialSessionId, record.provider, {
1581
+ kind: pin.kind,
1582
+ value: pin.value,
1583
+ });
1584
+ staleDurableCredentialPins.delete(resolveOAuthStorageProvider(record.provider));
1585
+ }
1586
+ } catch {
1587
+ // A newer stale pin must not leave an earlier replayed selector active.
1588
+ authStorage.clearSessionCredentialSelector(record.provider, credentialSessionId);
1589
+ staleDurableCredentialPins.add(resolveOAuthStorageProvider(record.provider));
1590
+ }
1591
+ }
1592
+ if (staleDurableCredentialPins.size > 0) {
1593
+ logger.warn("Stale durable credential pin discarded; re-pin or select AUTO explicitly", {
1594
+ providers: [...staleDurableCredentialPins].map(sanitizeProviderForLog),
1595
+ });
1596
+ }
1597
+ if (!scopeAlreadyLeased) {
1598
+ for (const [provider, rawSelector] of Object.entries(configuredPins)) {
1599
+ if (staleDurableCredentialPins.has(resolveOAuthStorageProvider(provider))) continue;
1600
+ if (
1601
+ authStorage.hasSessionCredentialSelector(provider, credentialSessionId) ||
1602
+ authStorage.hasSessionCredentialAuto(provider, credentialSessionId)
1603
+ )
1604
+ continue;
1605
+ const selector = parsePersistedCredentialSelector(rawSelector);
1606
+ if (!selector) continue;
1607
+ if (selector.kind === "id" && !persistedPinStoreMatches) {
1608
+ logger.warn("Numeric persistent credential pin discarded after credential-store authority changed", {
1609
+ provider: sanitizeProviderForLog(provider),
1610
+ });
1611
+ continue;
1612
+ }
1613
+ applyCredentialSelector(credentialSessionId, provider, selector);
1614
+ }
1615
+ }
1616
+ if (options.credentialSelector?.provider) {
1617
+ applyCredentialSelector(
1618
+ credentialSessionId,
1619
+ options.credentialSelector.provider,
1620
+ options.credentialSelector.selector,
1621
+ );
1622
+ }
1623
+
1376
1624
  const hasExplicitModel = options.model !== undefined || options.modelPattern !== undefined;
1377
1625
  const modelMatchPreferences = {
1378
1626
  usageOrder: settings.getStorage()?.getModelUsageOrder(),
@@ -1450,13 +1698,25 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1450
1698
  },
1451
1699
  );
1452
1700
  model = restoredDefaultResolution.model;
1701
+ // A restored session model from a different provider than an active
1702
+ // `--prefer-credential` preference is discarded rather than kept: the
1703
+ // preference names one provider's account, and silently resuming on
1704
+ // another provider would strand it without any error.
1705
+ if (model && preferredCredentialProvider && model.provider !== preferredCredentialProvider) {
1706
+ model = undefined;
1707
+ }
1453
1708
  if (!model) modelFallbackMessage = `Could not restore model ${defaultModelEntries.join(" -> ")}`;
1454
1709
  });
1455
1710
  }
1456
1711
 
1457
1712
  // If still no model, try settings default.
1458
1713
  // Skip settings fallback when an explicit model was requested.
1459
- if (!hasExplicitModel && !model && defaultRoleSpec.model) {
1714
+ if (
1715
+ !hasExplicitModel &&
1716
+ !model &&
1717
+ defaultRoleSpec.model &&
1718
+ (!preferredCredentialProvider || defaultRoleSpec.model.provider === preferredCredentialProvider)
1719
+ ) {
1460
1720
  const settingsDefaultModel = defaultRoleSpec.model;
1461
1721
  logger.time("resolveSettingsDefaultModel", () => {
1462
1722
  // defaultRoleSpec.model already comes from modelRegistry.getAvailable(),
@@ -1536,8 +1796,9 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1536
1796
  skillWarnings = skillsResult.warnings;
1537
1797
  } else {
1538
1798
  // GJC's four public workflow skills are bundled into the binary so the
1539
- // default workflow surface survives accidental .gjc deletion. Arbitrary
1540
- // filesystem skill discovery remains gated by skills.enabled above.
1799
+ // default workflow surface survives accidental .gjc deletion. Filesystem
1800
+ // skill discovery is enabled by default (`skills.enabled`), so this
1801
+ // branch only runs when the user explicitly disabled it.
1541
1802
  skills = getEmbeddedDefaultGjcSkills();
1542
1803
  skillWarnings = [];
1543
1804
  }
@@ -1612,12 +1873,17 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1612
1873
  const asyncMaxJobs = Math.min(100, Math.max(1, settings.get("async.maxJobs") ?? 100));
1613
1874
  const ASYNC_INLINE_RESULT_MAX_CHARS = 12_000;
1614
1875
  const ASYNC_PREVIEW_MAX_CHARS = 4_000;
1615
- const formatAsyncResultForFollowUp = async (result: string): Promise<string> => {
1876
+ const formatAsyncResultForFollowUp = async (result: string, allowArtifact = true): Promise<string> => {
1616
1877
  if (result.length <= ASYNC_INLINE_RESULT_MAX_CHARS) {
1617
1878
  return result;
1618
1879
  }
1619
1880
 
1620
1881
  const preview = `${result.slice(0, ASYNC_PREVIEW_MAX_CHARS)}\n\n[Output truncated. Showing first ${ASYNC_PREVIEW_MAX_CHARS.toLocaleString()} characters.]`;
1882
+ // A delivery already denied by a scope:"owned" gate never reaches the
1883
+ // model: allocating an artifact for it would leave the stopped job's
1884
+ // output in an unreferenced artifact after the flush drops it (review
1885
+ // thread P2). Only the inline preview is produced.
1886
+ if (!allowArtifact) return preview;
1621
1887
  try {
1622
1888
  const { path: artifactPath, id: artifactId } = await sessionManager.allocateArtifactPath("async");
1623
1889
  if (artifactPath && artifactId) {
@@ -1636,14 +1902,49 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1636
1902
  // parent's manager via `AsyncJobManager.instance()` (set below), so creating
1637
1903
  // a second instance here just to leave it orphaned wastes a constructor and
1638
1904
  // risks accidental disposal of the parent's manager on subagent teardown.
1639
- const asyncJobManager =
1905
+ asyncJobManager =
1640
1906
  backgroundJobsEnabled && !options.parentTaskPrefix
1641
1907
  ? new AsyncJobManager({
1642
1908
  maxRunningJobs: asyncMaxJobs,
1643
1909
  onJobComplete: async (jobId, result, job) => {
1644
1910
  if (!session) return;
1645
- const formattedResult = await formatAsyncResultForFollowUp(result);
1911
+ // Mandated boundary comment (corrected turn semantics):
1912
+ // turn-scope abort blocks only deliveries whose origin is a
1913
+ // continuation of the aborted turn. Owned-completion deliveries
1914
+ // from work deliberately left running are intentionally allowed
1915
+ // to resume the agent through the normal followUp/prompt path
1916
+ // and receive a fresh turn attempt. Recover the immutable origin
1917
+ // BEFORE formatting or artifact allocation; missing metadata
1918
+ // fails closed to an ordinary delivery.
1919
+ // Preserve ownership on the queued entry REGARDLESS of whether a
1920
+ // terminal scope exists yet: the registration is determined at
1921
+ // job-registration time, and the scope is determined at abort
1922
+ // time (or later, at flush). A completion finished before the
1923
+ // abort must not become an ordinary entry that owned cleanup
1924
+ // cannot identify/purge (review thread P1).
1925
+ // Owned tuples are keyed by the LOGICAL endpoint, not a
1926
+ // provider-facing session id. providerSessionId may differ
1927
+ // from sessionManager.getSessionId(), so session.sessionId
1928
+ // would miss the tuple and turn a left-running completion
1929
+ // into an ordinary follow-up (review thread P1).
1930
+ const endpointId = AsyncJobManager.endpointIdOf(asyncJobManager) ?? asyncJobEndpointId;
1931
+ const registration = job ? lookupOwnedRegistration(jobId, job.generation, endpointId) : undefined;
1932
+ const ownedCompletion = registration
1933
+ ? {
1934
+ lineageIdHash: registration.lineageIdHash,
1935
+ promptAttemptEpoch: registration.promptAttemptEpoch,
1936
+ registration,
1937
+ }
1938
+ : undefined;
1939
+ // Check suppression and classify the captured envelope BEFORE
1940
+ // formatting/artifact allocation: a delivery already suppressed
1941
+ // by the manager (owned-stop cancel/acknowledge) or already
1942
+ // denied by a scope:"owned" gate must not allocate an
1943
+ // unreferenced artifact (review thread P2).
1646
1944
  if (asyncJobManager!.isDeliverySuppressed(jobId, job?.generation)) return;
1945
+ const deniedOwnedDelivery =
1946
+ ownedCompletion !== undefined && !isOwnedCompletionEnvelopeAllowed(ownedCompletion);
1947
+ const formattedResult = await formatAsyncResultForFollowUp(result, !deniedOwnedDelivery);
1647
1948
 
1648
1949
  const durationMs = job ? jobElapsedMs(job) : undefined;
1649
1950
  session.yieldQueue.enqueue<AsyncResultEntry>("async-result", {
@@ -1652,10 +1953,19 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1652
1953
  result: formattedResult,
1653
1954
  job,
1654
1955
  durationMs,
1956
+ ...(ownedCompletion
1957
+ ? {
1958
+ ownedCompletion: {
1959
+ lineageIdHash: ownedCompletion.lineageIdHash,
1960
+ promptAttemptEpoch: ownedCompletion.promptAttemptEpoch,
1961
+ registration: ownedCompletion.registration,
1962
+ },
1963
+ }
1964
+ : {}),
1655
1965
  });
1656
1966
  },
1657
1967
  })
1658
- : undefined;
1968
+ : options.inheritedAsyncJobManager;
1659
1969
 
1660
1970
  let promptMetadataModel: Model | undefined;
1661
1971
  const getActiveModelString = (): string | undefined => {
@@ -1691,7 +2001,14 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1691
2001
  assertEvalExecutionAllowed: () => session?.assertEvalExecutionAllowed(),
1692
2002
  trackEvalExecution: (execution, abortController) =>
1693
2003
  session ? session.trackEvalExecution(execution, abortController) : execution,
1694
- getSessionId: () => sessionManager.getSessionId?.() ?? null,
2004
+ getAsyncJobManager: () => asyncJobManager,
2005
+ // Subagents inherit the parent's manager; its registered endpoint is
2006
+ // authoritative for owned-registration keying and endpoint-first
2007
+ // manager lookup (the child's own id is never registered), so tools
2008
+ // pass the same endpoint the manager's completion callback resolves
2009
+ // (review thread P1). For a top-level session this equals the
2010
+ // session id.
2011
+ getSessionId: () => AsyncJobManager.endpointIdOf(asyncJobManager) ?? asyncJobEndpointId,
1695
2012
  getCredentialSessionId: () => session?.credentialSessionId ?? credentialSessionId,
1696
2013
  getMcpManager: () => mcpManager ?? options.inheritedMcpManager,
1697
2014
  isManagedSessionDestination: () => sessionManager.isManagedDestination(),
@@ -1799,7 +2116,26 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1799
2116
  if (!options.parentTaskPrefix) {
1800
2117
  setActiveSkills(skills);
1801
2118
  setActiveRules([...rulebookRules, ...alwaysApplyRules]);
1802
- if (asyncJobManager) AsyncJobManager.setInstance(asyncJobManager);
2119
+ if (asyncJobManager) {
2120
+ // Register under the session endpoint so concurrent sessions'
2121
+ // owned work settles in the correct manager (review thread P1).
2122
+ // `session` is not yet constructed here; the provider identity is
2123
+ // the endpoint identity. ADMIT THE ENDPOINT FIRST: a second
2124
+ // top-level session constructed or resumed under an endpoint id
2125
+ // already held by another LIVE manager must fail construction
2126
+ // BEFORE the global instance is replaced — otherwise the
2127
+ // rejected construction leaves this orphan manager as the
2128
+ // process-global instance, redirecting global-manager
2129
+ // consumers away from the live session (review thread P1).
2130
+ if (!AsyncJobManager.registerForEndpoint(asyncJobEndpointId, asyncJobManager)) {
2131
+ throw new Error(
2132
+ `Cannot construct session "${asyncJobEndpointId}": the endpoint id is already held by another live async job manager`,
2133
+ );
2134
+ }
2135
+ asyncJobManagerAdmitted = true;
2136
+ priorAsyncJobManager = AsyncJobManager.instance();
2137
+ AsyncJobManager.setInstance(asyncJobManager);
2138
+ }
1803
2139
  }
1804
2140
  await initializeLocalRoot(localProtocolOptions);
1805
2141
  if (options.localProtocolOptions) {
@@ -1823,16 +2159,18 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1823
2159
  // Create built-in tools (already wrapped with meta notice formatting)
1824
2160
  const builtinTools = await logger.time("createAllTools", createTools, toolSession, options.toolNames);
1825
2161
 
1826
- // MCP runtime discovery is quarantined for the GJC surface. A top-level
1827
- // session may load only a caller-supplied exact config; project and user
1828
- // MCP configs are never discovered here. Existing managers remain available
1829
- // for legacy in-process callers, and plugin-bundle managers are created
1830
- // below after `customTools` is populated.
2162
+ // A top-level session loads MCP tools from three bounded surfaces: a
2163
+ // caller-supplied exact config (`--mcp-config`), conventional native
2164
+ // user/project registrations (`~/.gjc/agent/mcp.json` and `.gjc/mcp.json`
2165
+ // written by `gjc mcp add`; disabled by `--no-mcp`), and plugin-bundle
2166
+ // MCP servers (created below after `customTools` is populated). Existing
2167
+ // caller-supplied managers remain available for legacy in-process callers.
1831
2168
  let mcpManager: MCPManager | undefined = options.mcpManager;
1832
2169
  let ownsMcpManager = false;
1833
2170
  const customTools: CustomTool[] = [];
1834
2171
  const exactMcpToolNames: string[] = [];
1835
2172
  const pluginMcpToolNames: string[] = [];
2173
+ const conventionalMcpToolNames: string[] = [];
1836
2174
  let deferredExactMcpConfig: { manager: MCPManager; configPath: string } | undefined;
1837
2175
 
1838
2176
  // Add image tools when an image role model is configured.
@@ -1953,28 +2291,77 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1953
2291
  }
1954
2292
  }
1955
2293
  } else if (!mcpManager && !isCanonicalSubSession) {
1956
- // Always-on GJC plugin-bundle MCP servers. Top-level sessions own a manager
1957
- // and connect the validated servers; subagents inherit the parent's manager
2294
+ // Conventional MCP autoload: top-level standalone sessions consume
2295
+ // enabled registrations from GJC's own native configs — project
2296
+ // `.gjc/mcp.json` and user `~/.gjc/agent/mcp.json` (written by
2297
+ // `gjc mcp add`). Native project scope wins over native user scope on
2298
+ // name collisions (capability priority), and plugin-bundle MCPs below
2299
+ // override conventional entries with the same name. Claude Code/Codex
2300
+ // MCP files are explicit import sources into `.gjc` (#4291), never
2301
+ // implicit runtime authorities here. `--no-mcp` opts a session out;
2302
+ // `--mcp-config` exact-file sessions never reach here. The owned manager's
2303
+ // tools are surfaced as always-on custom tools (like plugin MCPs), so an
2304
+ // ordinary session exposes them without needing MCP discovery mode.
2305
+ let conventionalConfigs: Record<string, MCPServerConfig> = {};
2306
+ let conventionalSources: Record<string, SourceMeta> = {};
2307
+ if (options.enableMcpAutoload !== false) {
2308
+ try {
2309
+ const loaded = await loadAllMCPConfigs(cwd, {
2310
+ // Project-scope native config loads by default; only an
2311
+ // explicitly configured `mcp.enableProjectConfig: false`
2312
+ // disables it (the legacy schema default stays false for
2313
+ // foreign-format discovery consumers of the capability system).
2314
+ enableProjectConfig: settings.has("mcp.enableProjectConfig")
2315
+ ? settings.get("mcp.enableProjectConfig")
2316
+ : true,
2317
+ autoloadOnly: true,
2318
+ // Runtime authority is GJC's native `.gjc` config in both scopes;
2319
+ // Claude Code/Codex MCP files are explicit import sources into
2320
+ // `.gjc`, not implicit competing runtime authorities.
2321
+ nativeOnly: true,
2322
+ });
2323
+ conventionalConfigs = loaded.configs;
2324
+ conventionalSources = loaded.sources;
2325
+ for (const warning of loaded.warnings) {
2326
+ logger.warn("MCP conventional discovery warning", { warning });
2327
+ }
2328
+ if (loaded.configurationWarning) {
2329
+ logger.warn("MCP configuration unavailable.");
2330
+ }
2331
+ } catch (error) {
2332
+ logger.warn("Failed to discover conventional MCP servers", { error: safeErrorForLog(error) });
2333
+ }
2334
+ }
2335
+ // Always-on GJC plugin-bundle MCP servers, merged over conventional
2336
+ // servers on name collisions. Top-level sessions own a manager and
2337
+ // connect the validated servers; subagents inherit the parent's manager
1958
2338
  // via options.mcpManager and never spawn their own (prevents duplicate
1959
- // processes and leaks). Per the plugin product contract, connected MCP tools
1960
- // are surfaced as always-on tools rather than gated behind MCP selection.
2339
+ // processes and leaks). Per the plugin product contract, connected MCP
2340
+ // tools are surfaced as always-on tools rather than gated behind MCP
2341
+ // selection.
1961
2342
  try {
1962
2343
  const { configs, quarantine } = await buildPluginMcpConfigs({ cwd });
1963
2344
  for (const q of quarantine) {
1964
2345
  gjcFindings.add({ identity: q.identity, surfaceId: q.surfaceId, code: q.code, message: q.message });
1965
2346
  logger.warn("Quarantined GJC plugin MCP", { plugin: q.plugin, surface: q.surfaceId, code: q.code });
1966
2347
  }
1967
- if (Object.keys(configs).length > 0) {
2348
+ const pluginNames = new Set(Object.keys(configs));
2349
+ const mergedConfigs = { ...conventionalConfigs, ...configs };
2350
+ const mergedSources = {
2351
+ ...conventionalSources,
2352
+ ...Object.fromEntries(
2353
+ Object.keys(configs).map(name => [
2354
+ name,
2355
+ { provider: "gjc-plugins", providerName: "GJC plugin bundle", level: "project" as const },
2356
+ ]),
2357
+ ),
2358
+ };
2359
+ if (Object.keys(mergedConfigs).length > 0) {
1968
2360
  const owned = new MCPManager(cwd, null, { sharedPoolIdleMs: settings.get("mcp.sharedPoolIdleMs") });
2361
+ owned.setAuthStorage(authStorage);
1969
2362
  cleanupOwnedMcpManager = () => owned.disconnectAll();
1970
2363
  try {
1971
- const sources = Object.fromEntries(
1972
- Object.keys(configs).map(name => [
1973
- name,
1974
- { provider: "gjc-plugins", providerName: "GJC plugin bundle", level: "project" as const },
1975
- ]),
1976
- );
1977
- const result = await owned.connectServers(configs, sources as never);
2364
+ const result = await owned.connectServers(mergedConfigs, mergedSources as never);
1978
2365
  for (const [server, err] of result.errors) {
1979
2366
  // A server that failed to connect leaves this generation
1980
2367
  // incomplete: its surfaces produced no evidence, so publishing
@@ -1989,9 +2376,22 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1989
2376
  mcpManager = owned;
1990
2377
  ownsMcpManager = true;
1991
2378
  customTools.push(...(result.tools as CustomTool[]));
1992
- pluginMcpManagerServers.set(owned, new Set(result.connectedServers));
1993
- owned.sealConnectionSet();
1994
- pluginMcpToolNames.push(...result.tools.map(tool => tool.name));
2379
+ const connectedPluginNames = new Set(
2380
+ result.connectedServers.filter(name => pluginNames.has(name)),
2381
+ );
2382
+ pluginMcpManagerServers.set(owned, connectedPluginNames);
2383
+ conventionalMcpManagerServers.set(owned, new Set(result.connectedServers));
2384
+ for (const tool of result.tools) {
2385
+ const serverName = tool.mcpServerName;
2386
+ if (serverName === undefined) continue;
2387
+ if (connectedPluginNames.has(serverName)) pluginMcpToolNames.push(tool.name);
2388
+ else conventionalMcpToolNames.push(tool.name);
2389
+ }
2390
+ // Plugin-bundle connections are fixed for the session lifetime
2391
+ // (existing plugin contract). Sessions without plugin MCPs keep
2392
+ // a mutable connection set so `/mcp reload` can re-discover
2393
+ // conventional registrations.
2394
+ if (connectedPluginNames.size > 0) owned.sealConnectionSet();
1995
2395
  } else {
1996
2396
  try {
1997
2397
  await owned.disconnectAll();
@@ -2026,22 +2426,22 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
2026
2426
  });
2027
2427
  }
2028
2428
  } else if (isCanonicalSubSession) {
2029
- // Subagents inherit the parent's always-on plugin MCP tools WITHOUT
2030
- // owning the manager (no connect, no callbacks, no disposal). The facade
2031
- // is carried explicitly by the parent session scope; process-global state
2032
- // is never consulted for MCP routing.
2429
+ // Subagents inherit the parent's always-on plugin and conventional MCP
2430
+ // tools WITHOUT owning the manager (no connect, no callbacks, no
2431
+ // disposal). The facade is carried explicitly by the parent session
2432
+ // scope; process-global state is never consulted for MCP routing.
2033
2433
  const inherited = mcpManager ?? options.inheritedMcpManager;
2034
2434
  if (inherited) {
2035
2435
  try {
2036
2436
  const inheritedTools = inherited.getTools();
2037
2437
  if (inheritedTools.length > 0) customTools.push(...(inheritedTools as CustomTool[]));
2038
2438
  const pluginServers = pluginMcpManagerServers.get(inherited);
2039
- if (pluginServers) {
2040
- pluginMcpToolNames.push(
2041
- ...inheritedTools
2042
- .filter(tool => tool.mcpServerName !== undefined && pluginServers.has(tool.mcpServerName))
2043
- .map(tool => tool.name),
2044
- );
2439
+ const conventionalServers = conventionalMcpManagerServers.get(inherited);
2440
+ for (const tool of inheritedTools) {
2441
+ const serverName = tool.mcpServerName;
2442
+ if (serverName === undefined) continue;
2443
+ if (pluginServers?.has(serverName)) pluginMcpToolNames.push(tool.name);
2444
+ else if (conventionalServers?.has(serverName)) conventionalMcpToolNames.push(tool.name);
2045
2445
  }
2046
2446
  } catch (error) {
2047
2447
  logger.warn("Failed to inherit MCP tools in subagent", { error: safeErrorForLog(error) });
@@ -2050,13 +2450,25 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
2050
2450
  }
2051
2451
  // MCP routing is scope-held; no process-global manager registration.
2052
2452
 
2053
- // Custom tool and extension discovery is quarantined from the public GJC utility surface.
2054
- // Explicit SDK extension factories are still honored; callers use them to
2055
- // register in-process tools/providers without enabling filesystem discovery.
2453
+ // General extension discovery is quarantined from the public SDK surface.
2454
+ // Recognized hook conventions are the bounded exception: their descriptors
2455
+ // normalize before import and then adapt into the authoritative ExtensionRunner.
2056
2456
  const inlineExtensions: ExtensionFactory[] = [...(options.extensions ?? [])];
2457
+ const discoveredHookExtensions: Array<{ factory: ExtensionFactory; name: string }> = [];
2057
2458
  if (customTools.length > 0) {
2058
2459
  inlineExtensions.push(createCustomToolsExtension(customTools));
2059
2460
  }
2461
+ if (!options.disableExtensionDiscovery) {
2462
+ try {
2463
+ const hookExtensions = await discoverAndLoadHookExtensions(options.hookPaths ?? [], cwd);
2464
+ discoveredHookExtensions.push(...hookExtensions.factories);
2465
+ for (const error of hookExtensions.errors) {
2466
+ logger.warn("Rejected discovered hook", { path: error.path, error: error.error });
2467
+ }
2468
+ } catch (error) {
2469
+ logger.warn("Failed to discover hook extensions", { error: safeErrorForLog(error) });
2470
+ }
2471
+ }
2060
2472
 
2061
2473
  // Always-on constrained plugin hooks (validated registry surfaces). Additive
2062
2474
  // and a no-op without installed plugins; the loader denies all dangerous APIs.
@@ -2130,13 +2542,41 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
2130
2542
  if (lifecycleStartupCapability) attachLifecycleStartupCapability(api, lifecycleStartupCapability);
2131
2543
  if (lifecycleStartupCapability && process.env.GJC_SDK_TEST_FACTORY_FAILURE === cwd)
2132
2544
  throw new Error(process.env.GJC_SDK_TEST_FACTORY_SECRET ?? "Lifecycle factory test failure.");
2133
- if (notificationsExtensionEligible || sdkHostEligible) {
2545
+ if (notificationsExtensionEligible) {
2134
2546
  const createNotificationsExtension = await notificationAdapterService.get("session-extension");
2135
2547
  createNotificationsExtension(api, {
2136
2548
  settings,
2137
2549
  controller: notificationSessionController,
2138
2550
  spawnedByGjc,
2139
2551
  sdkHostModeSupported: options.sdkHostModeSupported,
2552
+ // INTERNAL terminal-abort seams, threaded directly from the
2553
+ // owning session (NOT on the public extension context).
2554
+ terminalAbortSeams: {
2555
+ getTerminalTurnEpoch: () => {
2556
+ if (!session) throw new Error("Terminal abort session is not initialized.");
2557
+ return session.getTerminalTurnEpoch();
2558
+ },
2559
+ cancelPendingPreflightForTerminalAbort: () => {
2560
+ if (!session) throw new Error("Terminal abort session is not initialized.");
2561
+ session.cancelPendingPreflightForTerminalAbort();
2562
+ },
2563
+ captureTerminalAbortSteeringSnapshot: () => {
2564
+ if (!session) throw new Error("Terminal abort session is not initialized.");
2565
+ return session.captureTerminalAbortSteeringSnapshot();
2566
+ },
2567
+ discardTerminalAbortSteeringSnapshot: (token: number) => {
2568
+ if (!session) throw new Error("Terminal abort session is not initialized.");
2569
+ session.discardTerminalAbortSteeringSnapshot(token);
2570
+ },
2571
+ rebindTerminalAbortSteeringSnapshot: (token: number) => {
2572
+ if (!session) throw new Error("Terminal abort session is not initialized.");
2573
+ session.rebindTerminalAbortSteeringSnapshot(token);
2574
+ },
2575
+ abortPromptAndWaitWithTerminal: (handle, seamOptions) => {
2576
+ if (!session) throw new Error("Terminal abort session is not initialized.");
2577
+ return session.abortPromptAndWait(handle, seamOptions);
2578
+ },
2579
+ },
2140
2580
  ensureProviderDaemon: options.ensureNotificationProviderDaemon,
2141
2581
  runBtwTurn: async (question, signal) => {
2142
2582
  if (!session) throw new Error("Ephemeral turns are unavailable.");
@@ -2148,6 +2588,75 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
2148
2588
  return { replyText };
2149
2589
  },
2150
2590
  });
2591
+ } else if (sdkHostEligible) {
2592
+ registerSdkOnlyNotificationCommand(api);
2593
+ let sdkOnlyReconciliationStore:
2594
+ | { sessionId: string; sessionFile: string | undefined; store: ReconciliationStore }
2595
+ | undefined;
2596
+ createSdkSessionRuntimeExtension(api, {
2597
+ agentDir,
2598
+ brokerRegistrationRequired: lifecycleStartupCapability !== undefined,
2599
+ createTransport: input => createSdkWebSocketTransport(input),
2600
+ settings,
2601
+ configOverrides: new Map(),
2602
+ // INTERNAL terminal-abort seams, threaded directly from the
2603
+ // owning session (NOT on the public extension context).
2604
+ terminalAbortSeams: {
2605
+ getReconciliationStore: () => {
2606
+ const sessionId = sessionManager.getSessionId();
2607
+ const sessionFile = sessionManager.getSessionFile();
2608
+ // session_switch/session_branch may move to a DIFFERENT transcript
2609
+ // that retains the same copied session id: the store must be
2610
+ // recreated (keyed by session id AND file path, including a
2611
+ // null-to-file transition) or the successor reads/writes the
2612
+ // predecessor's reconciliation file, spuriously replaying or
2613
+ // conflicting with its keys and losing its own replay authority
2614
+ // after restart (review thread P2).
2615
+ if (
2616
+ !sdkOnlyReconciliationStore ||
2617
+ !sdkOnlyStoreMatches(sdkOnlyReconciliationStore, sessionId, sessionFile)
2618
+ ) {
2619
+ sdkOnlyReconciliationStore = {
2620
+ sessionId,
2621
+ sessionFile,
2622
+ store: createReconciliationStore({
2623
+ sessionFile,
2624
+ sessionId,
2625
+ }),
2626
+ };
2627
+ }
2628
+ return sdkOnlyReconciliationStore.store;
2629
+ },
2630
+ getTerminalTurnEpoch: () => {
2631
+ if (!session) throw new Error("Terminal abort session is not initialized.");
2632
+ return session.getTerminalTurnEpoch();
2633
+ },
2634
+ getActivePromptHandle: () => {
2635
+ if (!session) throw new Error("Terminal abort session is not initialized.");
2636
+ return session.activePromptHandle;
2637
+ },
2638
+ cancelPendingPreflightForTerminalAbort: () => {
2639
+ if (!session) throw new Error("Terminal abort session is not initialized.");
2640
+ session.cancelPendingPreflightForTerminalAbort();
2641
+ },
2642
+ captureTerminalAbortSteeringSnapshot: () => {
2643
+ if (!session) throw new Error("Terminal abort session is not initialized.");
2644
+ return session.captureTerminalAbortSteeringSnapshot();
2645
+ },
2646
+ discardTerminalAbortSteeringSnapshot: (token: number) => {
2647
+ if (!session) throw new Error("Terminal abort session is not initialized.");
2648
+ session.discardTerminalAbortSteeringSnapshot(token);
2649
+ },
2650
+ rebindTerminalAbortSteeringSnapshot: (token: number) => {
2651
+ if (!session) throw new Error("Terminal abort session is not initialized.");
2652
+ session.rebindTerminalAbortSteeringSnapshot(token);
2653
+ },
2654
+ abortPromptAndWaitWithTerminal: (handle, seamOptions) => {
2655
+ if (!session) throw new Error("Terminal abort session is not initialized.");
2656
+ return session.abortPromptAndWait(handle, seamOptions);
2657
+ },
2658
+ },
2659
+ });
2151
2660
  }
2152
2661
  } catch (error) {
2153
2662
  lifecycleStartupCapability?.settleFailure(
@@ -2193,6 +2702,16 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
2193
2702
  extensionsResult.extensions.push(loaded);
2194
2703
  }
2195
2704
  }
2705
+ for (const entry of discoveredHookExtensions) {
2706
+ const loaded = await loadExtensionFromFactory(
2707
+ entry.factory,
2708
+ cwd,
2709
+ eventBus,
2710
+ extensionsResult.runtime,
2711
+ entry.name,
2712
+ );
2713
+ extensionsResult.extensions.push(loaded);
2714
+ }
2196
2715
 
2197
2716
  // Process provider registrations queued during extension loading.
2198
2717
  // This must happen before the runner is created so that models registered by
@@ -2258,18 +2777,25 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
2258
2777
  : formatNoModelsAvailableFallback();
2259
2778
  }
2260
2779
  }
2780
+ if (options.credentialSelector && !options.credentialSelector.provider && model) {
2781
+ applyCredentialSelector(credentialSessionId, model.provider, options.credentialSelector.selector);
2782
+ }
2261
2783
 
2262
- if (options.credentialSelector && !runtimeCredentialSelectorInstalled) {
2263
- const credentialProvider = options.credentialSelector.provider ?? model?.provider;
2264
- if (!credentialProvider) {
2265
- throw new Error(
2266
- `--credential ${options.credentialSelector.raw} requires a resolved model or an explicit provider prefix`,
2267
- );
2268
- }
2269
- installRuntimeCredentialSelector(credentialProvider);
2270
- if (!options.modelRegistry && !canRefreshModelsBeforeCredentialSelector) {
2271
- modelRegistry.refreshInBackground();
2272
- }
2784
+ if (options.credentialSelector && !options.credentialSelector.provider && !model?.provider) {
2785
+ throw new Error(
2786
+ `--credential ${options.credentialSelector.raw} requires a resolved model or an explicit provider prefix`,
2787
+ );
2788
+ }
2789
+ // Safety net: every resolution branch above already filters candidates by
2790
+ // `preferredCredentialProvider` (session restore, settings default, fallback
2791
+ // scan via `hasModelApiKey`), but an explicit `--model`/`--models` request for
2792
+ // a different provider takes priority earlier in resolution and would
2793
+ // otherwise silently ignore the preference. Fail closed with a clear error
2794
+ // instead.
2795
+ if (model && preferredCredentialProvider && model.provider !== preferredCredentialProvider) {
2796
+ throw new Error(
2797
+ `--prefer-credential ${options.preferredCredentialSelector?.raw ?? ""} matches ${preferredCredentialProvider}, but the resolved model uses ${model.provider}`,
2798
+ );
2273
2799
  }
2274
2800
  const customCommandsResult: CustomCommandsLoadResult = { commands: [], errors: [] };
2275
2801
 
@@ -2354,9 +2880,20 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
2354
2880
 
2355
2881
  // All built-in tools are active (conditional tools like git/ask return null from factory if disabled)
2356
2882
  const toolRegistry = new Map<string, Tool>();
2883
+ /**
2884
+ * Identity — not name — provenance for the coordinator-visible tool label.
2885
+ *
2886
+ * Every tool object this builder constructs from a built-in descriptor is recorded
2887
+ * here as it is created, BEFORE extension, MCP, and custom tools overwrite registry
2888
+ * names below. An extension or custom tool that registers itself as `bash` replaces
2889
+ * the registry entry with an object that was never recorded, so the label resolves to
2890
+ * `custom` instead of impersonating the built-in.
2891
+ */
2892
+ const builtinToolIdentities = new Set<object>();
2357
2893
  let builtinCandidateTools = [...builtinTools];
2358
2894
  for (const tool of builtinTools) {
2359
2895
  toolRegistry.set(tool.name, tool);
2896
+ builtinToolIdentities.add(tool);
2360
2897
  }
2361
2898
  const goalStateToolNames = ["goal"] as const;
2362
2899
  if (settings.get("goal.enabled")) {
@@ -2371,6 +2908,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
2371
2908
  const wrappedGoalStateTool = wrapToolWithMetaNotice(goalStateTool);
2372
2909
  builtinCandidateTools.push(wrappedGoalStateTool);
2373
2910
  toolRegistry.set(wrappedGoalStateTool.name, wrappedGoalStateTool);
2911
+ builtinToolIdentities.add(wrappedGoalStateTool);
2374
2912
  }
2375
2913
  }
2376
2914
  }
@@ -2379,7 +2917,11 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
2379
2917
  }
2380
2918
  if (extensionRunner) {
2381
2919
  for (const tool of toolRegistry.values()) {
2382
- toolRegistry.set(tool.name, new ExtensionToolWrapper(tool, extensionRunner));
2920
+ const wrapped = new ExtensionToolWrapper(tool, extensionRunner);
2921
+ // A wrapper built from a proven built-in inherits that provenance; one built
2922
+ // from an extension/custom tool does not.
2923
+ if (builtinToolIdentities.has(tool)) builtinToolIdentities.add(wrapped);
2924
+ toolRegistry.set(tool.name, wrapped);
2383
2925
  }
2384
2926
  }
2385
2927
  if (model?.provider === "cursor") {
@@ -2396,6 +2938,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
2396
2938
  const wrappedResolveTool = wrapToolWithMetaNotice(resolveTool);
2397
2939
  builtinCandidateTools.push(wrappedResolveTool);
2398
2940
  toolRegistry.set(wrappedResolveTool.name, wrappedResolveTool);
2941
+ builtinToolIdentities.add(wrappedResolveTool);
2399
2942
  }
2400
2943
  }
2401
2944
  // Exact-config MCP tools cannot claim a name already represented by the final candidate catalog.
@@ -2434,7 +2977,18 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
2434
2977
  });
2435
2978
 
2436
2979
  const repeatToolDescriptions = settings.get("repeatToolDescriptions");
2437
- const eagerTasks = settings.get("task.eager");
2980
+ // Re-resolved per prompt build so a profile activated after session start is
2981
+ // reflected on the next refresh. Vendor-separated worker roles imply eager
2982
+ // delegation unless `task.eager` is configured explicitly.
2983
+ const resolveEagerTasks = (): boolean => {
2984
+ const profileName = startupActiveModelProfile ?? settings.get("modelProfile.default");
2985
+ const resolvedProfileName = profileName ? resolveModelProfileName(profileName, persistedProfiles) : undefined;
2986
+ return resolveEagerTaskDelegation({
2987
+ settings,
2988
+ profile: resolvedProfileName ? persistedProfiles.get(resolvedProfileName) : undefined,
2989
+ }).eagerTasks;
2990
+ };
2991
+ const eagerTasks = resolveEagerTasks();
2438
2992
  const intentTracingEnabled = resolveIntentTracingEnabled(settings.get("tools.intentTracing"), {
2439
2993
  subSession: isCanonicalSubSession,
2440
2994
  });
@@ -2519,7 +3073,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
2519
3073
  repeatToolDescriptions,
2520
3074
  intentField,
2521
3075
  toolDiscoveryActive: effectiveDiscoveryMode === "all" || mcpDiscoveryEnabled,
2522
- eagerTasks,
3076
+ eagerTasks: resolveEagerTasks(),
2523
3077
  secretsEnabled,
2524
3078
  workspaceTree: workspaceTreePromise,
2525
3079
  subagent: options.parentTaskPrefix !== undefined,
@@ -2612,6 +3166,9 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
2612
3166
  ...new Set([
2613
3167
  ...discoveryDefaultServerToolNames,
2614
3168
  ...(explicitMcpConfigPath !== undefined ? exactMcpToolNames : []),
3169
+ // Conventional autoload servers are selected by default so their
3170
+ // tools are exposed in sessions that enable MCP discovery mode.
3171
+ ...conventionalMcpToolNames,
2615
3172
  ]),
2616
3173
  ];
2617
3174
  hasExplicitMCPToolSelection =
@@ -2631,11 +3188,13 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
2631
3188
 
2632
3189
  // Custom, extension-registered, and plugin-bundle MCP tools are always
2633
3190
  // included regardless of the caller's built-in tool filter. Plugin MCPs
2634
- // remain always-on even when generic MCP discovery is disabled.
3191
+ // and conventional autoload MCPs remain always-on even when generic MCP
3192
+ // discovery is disabled.
2635
3193
  const alwaysInclude: string[] = [
2636
3194
  ...(options.customTools?.map(t => (isCustomTool(t) ? t.name : t.name)) ?? []),
2637
3195
  ...registeredTools.filter(t => !t.definition.defaultInactive).map(t => t.definition.name),
2638
3196
  ...pluginMcpToolNames,
3197
+ ...conventionalMcpToolNames,
2639
3198
  ];
2640
3199
  const pluginMcpToolNameSet = new Set(pluginMcpToolNames);
2641
3200
  for (const name of alwaysInclude) {
@@ -2794,6 +3353,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
2794
3353
  activeSkillState: session?.getActiveSkillState(),
2795
3354
  sessionId: sessionManager.getSessionId?.() ?? null,
2796
3355
  }),
3356
+ () => session?.getSessionAgentDir(),
2797
3357
  ),
2798
3358
  );
2799
3359
 
@@ -2872,20 +3432,13 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
2872
3432
  getApiKey: async provider => {
2873
3433
  // Read agent.sessionId at call time so credential selection stays aligned
2874
3434
  // with metadataResolver after /new, fork, resume, or branch switches.
2875
- const key = await modelRegistry.getApiKeyForProvider(
2876
- provider,
2877
- options.credentialSessionId ?? agent.providerSessionId ?? agent.sessionId,
2878
- );
3435
+ const key = await modelRegistry.getApiKeyForProvider(provider, credentialSessionId);
2879
3436
  if (!key) {
2880
3437
  throw new Error(`No API key found for provider "${provider}"`);
2881
3438
  }
2882
3439
  return key;
2883
3440
  },
2884
- getAuthCredentialType: provider =>
2885
- modelRegistry.getSessionCredentialType(
2886
- provider,
2887
- options.credentialSessionId ?? agent.providerSessionId ?? agent.sessionId,
2888
- ),
3441
+ getAuthCredentialType: provider => modelRegistry.getSessionCredentialType(provider, credentialSessionId),
2889
3442
  streamFn: async (streamModel, context, streamOptions) => {
2890
3443
  const requestStartedAt = performance.now();
2891
3444
  let stream: Awaited<ReturnType<typeof streamSimple>>;
@@ -2893,8 +3446,6 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
2893
3446
  stream = await streamSimple(streamModel, context, {
2894
3447
  ...streamOptions,
2895
3448
  onAuthError: async (provider, oldKey, error) => {
2896
- const credentialSessionId =
2897
- options.credentialSessionId ?? agent.providerSessionId ?? agent.sessionId;
2898
3449
  await modelRegistry.authStorage.invalidateCredentialMatching(provider, oldKey, {
2899
3450
  signal: streamOptions?.signal,
2900
3451
  sessionId: credentialSessionId,
@@ -3002,6 +3553,12 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
3002
3553
  thinkingLevel,
3003
3554
  sessionManager,
3004
3555
  settings,
3556
+ // The session's REQUESTED agent directory when the caller explicitly
3557
+ // supplied it, independent of the reused global Settings singleton
3558
+ // (which may belong to an earlier session). When the option is
3559
+ // absent, the injected settings instance's own profile wins via the
3560
+ // AgentSession fallback - never the process default.
3561
+ agentDir: options.agentDir,
3005
3562
  memoryBackend: runtimeServices.memoryBackend,
3006
3563
  notificationSessionController,
3007
3564
  evalKernelOwnerId,
@@ -3010,6 +3567,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
3010
3567
  // AsyncJobManager on teardown; subagents inherit the parent's and
3011
3568
  // **MUST NOT** tear it down.
3012
3569
  ownedAsyncJobManager: asyncJobManager,
3570
+ disposeAsyncJobManager: !options.parentTaskPrefix,
3013
3571
  // Only an MCP manager owned by this session is torn down on dispose;
3014
3572
  // subagents and callers that merely observe a manager must not (see
3015
3573
  // AgentSession.dispose).
@@ -3027,6 +3585,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
3027
3585
  taskDepth,
3028
3586
  workflowGatePublication: isCanonicalSubSession ? "local" : "endpoint",
3029
3587
  toolRegistry,
3588
+ builtinToolIdentities,
3030
3589
  workflowGateToolSession: toolSession,
3031
3590
  transformContext,
3032
3591
  onPayload,
@@ -3064,8 +3623,10 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
3064
3623
  obfuscator,
3065
3624
  agentId: resolvedAgentId,
3066
3625
  agentRegistry,
3626
+ asyncJobProviderSessionId: options.providerSessionId,
3067
3627
  providerSessionId: options.providerSessionId,
3068
- credentialSessionId: options.credentialSessionId,
3628
+ credentialSessionId,
3629
+ credentialStoreIdentity: startupAuthConfig?.credentialStoreIdentity,
3069
3630
  providerCacheSessionId: providerSessionId,
3070
3631
  forkContextSeed: options.forkContextSeed,
3071
3632
  providerSessionState: options.providerSessionState,
@@ -3073,9 +3634,17 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
3073
3634
  session.setActiveModelProfile(startupActiveModelProfile);
3074
3635
  session.configWarnings.push(...contextFileWarnings);
3075
3636
  hasSession = true;
3076
- if (asyncJobManager) {
3637
+ const sessionAsyncJobManager = asyncJobManager;
3638
+ if (sessionAsyncJobManager) {
3077
3639
  session.yieldQueue.register<AsyncResultEntry>("async-result", {
3078
- isStale: entry => asyncJobManager.isDeliverySuppressed(entry.jobId, entry.generation),
3640
+ isStale: entry => sessionAsyncJobManager.isDeliverySuppressed(entry.jobId, entry.generation),
3641
+ // Build one message per ownership origin so an owned-scope drop of
3642
+ // one turn's message never suppresses other turns'/ordinary
3643
+ // completions batched in the same flush (review thread P2).
3644
+ groupKey: entry =>
3645
+ entry.ownedCompletion
3646
+ ? `${entry.ownedCompletion.lineageIdHash}\u0000${entry.ownedCompletion.promptAttemptEpoch}`
3647
+ : "ordinary",
3079
3648
  build: buildAsyncResultBatchMessage,
3080
3649
  });
3081
3650
  }
@@ -3098,6 +3667,20 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
3098
3667
  releaseCredentialDisabledSubscription();
3099
3668
  releaseLocalProtocolOverride();
3100
3669
  } finally {
3670
+ // The endpoint is gone: its owned registrations can never
3671
+ // reach a delivery settlement boundary, and foreign-endpoint
3672
+ // tuples are deliberately never classified terminal by other
3673
+ // managers — retire them before unregistering the manager so
3674
+ // repeated session churn cannot saturate the registry
3675
+ // (review thread P2). The endpoint is the manager's live
3676
+ // registration key, which survives newSession/switchSession
3677
+ // rekeying and may differ from the persisted logical session id.
3678
+ if (!options.parentTaskPrefix) {
3679
+ retireOwnedRegistrationsForEndpoint(
3680
+ AsyncJobManager.endpointIdOf(asyncJobManager) ?? asyncJobEndpointId,
3681
+ );
3682
+ AsyncJobManager.unregisterManager(asyncJobManager);
3683
+ }
3101
3684
  closeOwnedAuthStorage();
3102
3685
  }
3103
3686
  }
@@ -3316,6 +3899,19 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
3316
3899
  await session.dispose();
3317
3900
  } else {
3318
3901
  if (hasRegistered) agentRegistry.unregister(resolvedAgentId);
3902
+ // Admission happens before session construction. Any later startup
3903
+ // failure must remove THIS manager's endpoint mapping and restore
3904
+ // the prior global only when this manager is still global: otherwise
3905
+ // a retry under the same endpoint is falsely rejected and an orphan
3906
+ // redirects global-manager consumers away from the live session
3907
+ // (review thread P1).
3908
+ if (asyncJobManagerAdmitted && asyncJobManager) {
3909
+ AsyncJobManager.unregisterManager(asyncJobManager);
3910
+ if (AsyncJobManager.instance() === asyncJobManager) {
3911
+ AsyncJobManager.setInstance(priorAsyncJobManager);
3912
+ }
3913
+ await asyncJobManager.dispose({ timeoutMs: 100 });
3914
+ }
3319
3915
  await cleanupOwnedMcpManager?.();
3320
3916
  const [{ disposeKernelSessionsByOwner }, { disposeVmContextsByOwner }] = await Promise.all([
3321
3917
  import("../eval/py/executor"),