@oh-my-pi/pi-coding-agent 17.0.8 → 17.1.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 (340) hide show
  1. package/CHANGELOG.md +87 -1
  2. package/README.md +1 -1
  3. package/dist/cli.js +5495 -5021
  4. package/dist/types/async/job-manager.d.ts +37 -1
  5. package/dist/types/cli/args.d.ts +2 -0
  6. package/dist/types/cli/bench-cli.d.ts +41 -0
  7. package/dist/types/commands/bench.d.ts +15 -3
  8. package/dist/types/commands/launch.d.ts +4 -0
  9. package/dist/types/config/__tests__/model-registry.test.d.ts +1 -0
  10. package/dist/types/config/model-registry.d.ts +30 -1
  11. package/dist/types/config/models-config-schema.d.ts +18 -6
  12. package/dist/types/config/models-config.d.ts +14 -5
  13. package/dist/types/config/provider-globals.d.ts +2 -2
  14. package/dist/types/config/service-tier.d.ts +5 -1
  15. package/dist/types/config/settings-schema.d.ts +350 -50
  16. package/dist/types/config/settings.d.ts +7 -0
  17. package/dist/types/dap/client.d.ts +20 -0
  18. package/dist/types/exec/bash-executor.d.ts +38 -0
  19. package/dist/types/exec/direnv.d.ts +33 -0
  20. package/dist/types/export/html/args.d.ts +15 -0
  21. package/dist/types/export/html/index.d.ts +11 -26
  22. package/dist/types/export/html/web-palette.d.ts +55 -116
  23. package/dist/types/extensibility/extensions/loader.d.ts +3 -0
  24. package/dist/types/extensibility/extensions/types.d.ts +17 -2
  25. package/dist/types/extensibility/legacy-pi-coding-agent-shim.d.ts +1 -0
  26. package/dist/types/extensibility/legacy-pi-tui-shim.d.ts +10 -0
  27. package/dist/types/extensibility/plugins/legacy-pi-compat.d.ts +14 -1
  28. package/dist/types/internal-urls/registry-helpers.d.ts +11 -0
  29. package/dist/types/live/controller.d.ts +40 -0
  30. package/dist/types/live/protocol.d.ts +85 -0
  31. package/dist/types/live/protocol.test.d.ts +1 -0
  32. package/dist/types/live/transport.d.ts +48 -0
  33. package/dist/types/live/visualizer.d.ts +33 -0
  34. package/dist/types/lsp/client.d.ts +18 -1
  35. package/dist/types/mcp/config-writer.test.d.ts +1 -0
  36. package/dist/types/mcp/manager.d.ts +6 -2
  37. package/dist/types/mcp/oauth-discovery.d.ts +2 -0
  38. package/dist/types/mcp/render.d.ts +2 -2
  39. package/dist/types/mcp/smithery-auth.d.ts +1 -1
  40. package/dist/types/mcp/smithery-auth.test.d.ts +1 -0
  41. package/dist/types/mcp/tool-bridge.d.ts +7 -3
  42. package/dist/types/mcp/types.d.ts +6 -0
  43. package/dist/types/memory-backend/tool-names.d.ts +2 -0
  44. package/dist/types/modes/components/login-dialog.d.ts +2 -2
  45. package/dist/types/modes/components/oauth-selector.d.ts +8 -0
  46. package/dist/types/modes/components/plan-review-overlay.d.ts +26 -0
  47. package/dist/types/modes/components/settings-defs.d.ts +8 -1
  48. package/dist/types/modes/components/status-line/component.jj-cache.test.d.ts +1 -0
  49. package/dist/types/modes/components/tool-execution.d.ts +9 -1
  50. package/dist/types/modes/controllers/command-controller.d.ts +1 -1
  51. package/dist/types/modes/controllers/event-controller.d.ts +6 -1
  52. package/dist/types/modes/controllers/live-command-controller.d.ts +14 -0
  53. package/dist/types/modes/controllers/mcp-command-controller.d.ts +3 -0
  54. package/dist/types/modes/interactive-mode.d.ts +5 -0
  55. package/dist/types/modes/rpc/rpc-client.d.ts +10 -1
  56. package/dist/types/modes/rpc/rpc-frame.d.ts +16 -0
  57. package/dist/types/modes/rpc/rpc-messages.d.ts +26 -0
  58. package/dist/types/modes/rpc/rpc-types.d.ts +40 -0
  59. package/dist/types/modes/setup-wizard/scenes/sign-in.d.ts +1 -1
  60. package/dist/types/modes/setup-wizard/scenes/types.d.ts +9 -1
  61. package/dist/types/modes/setup-wizard/scenes/web-search.d.ts +1 -1
  62. package/dist/types/modes/theme/theme.d.ts +1 -1
  63. package/dist/types/modes/types.d.ts +3 -1
  64. package/dist/types/modes/utils/context-usage.d.ts +12 -2
  65. package/dist/types/plan-mode/plan-files.d.ts +10 -0
  66. package/dist/types/registry/agent-lifecycle.d.ts +21 -9
  67. package/dist/types/registry/agent-registry.d.ts +11 -4
  68. package/dist/types/sdk.d.ts +15 -1
  69. package/dist/types/secrets/index.d.ts +15 -2
  70. package/dist/types/secrets/obfuscator.d.ts +113 -15
  71. package/dist/types/session/acp-permission-gate.d.ts +19 -0
  72. package/dist/types/session/agent-session-events.d.ts +84 -0
  73. package/dist/types/session/agent-session-types.d.ts +314 -0
  74. package/dist/types/session/agent-session.d.ts +120 -750
  75. package/dist/types/session/async-job-delivery.d.ts +28 -0
  76. package/dist/types/session/bash-runner.d.ts +75 -0
  77. package/dist/types/session/blob-store.d.ts +11 -1
  78. package/dist/types/session/blob-store.test.d.ts +1 -0
  79. package/dist/types/session/checkpoint-entries.d.ts +28 -0
  80. package/dist/types/session/eval-runner.d.ts +49 -0
  81. package/dist/types/session/irc-bridge.d.ts +46 -0
  82. package/dist/types/session/messages.d.ts +22 -0
  83. package/dist/types/session/model-controls.d.ts +180 -0
  84. package/dist/types/session/prewalk.d.ts +46 -0
  85. package/dist/types/session/queued-messages.d.ts +22 -0
  86. package/dist/types/session/redis-session-storage.d.ts +1 -0
  87. package/dist/types/session/retry-fallback-chains.d.ts +74 -0
  88. package/dist/types/session/role-models.d.ts +15 -0
  89. package/dist/types/session/session-advisors.d.ts +228 -0
  90. package/dist/types/session/session-entries.d.ts +8 -0
  91. package/dist/types/session/session-handoff.d.ts +70 -0
  92. package/dist/types/session/session-history-format.d.ts +6 -1
  93. package/dist/types/session/session-loader.d.ts +6 -4
  94. package/dist/types/session/session-maintenance.d.ts +246 -0
  95. package/dist/types/session/session-manager.d.ts +41 -0
  96. package/dist/types/session/session-memory.d.ts +56 -0
  97. package/dist/types/session/session-provider-boundary.d.ts +62 -0
  98. package/dist/types/session/session-stats.d.ts +48 -0
  99. package/dist/types/session/session-tools.d.ts +143 -0
  100. package/dist/types/session/session-workspace.d.ts +32 -0
  101. package/dist/types/session/stream-guards.d.ts +44 -0
  102. package/dist/types/session/todo-tracker.d.ts +56 -0
  103. package/dist/types/session/ttsr-coordinator.d.ts +51 -0
  104. package/dist/types/session/turn-recovery.d.ts +225 -0
  105. package/dist/types/system-prompt.d.ts +2 -0
  106. package/dist/types/task/executor.d.ts +4 -1
  107. package/dist/types/task/output-manager.d.ts +3 -1
  108. package/dist/types/task/types.d.ts +14 -0
  109. package/dist/types/tools/approval.d.ts +1 -0
  110. package/dist/types/tools/browser/launch.d.ts +5 -0
  111. package/dist/types/tools/hub/jobs.d.ts +1 -1
  112. package/dist/types/tools/image-gen.d.ts +4 -4
  113. package/dist/types/tools/image-providers.d.ts +38 -0
  114. package/dist/types/tools/index.d.ts +8 -0
  115. package/dist/types/tools/path-utils.d.ts +8 -0
  116. package/dist/types/tools/report-tool-issue.d.ts +24 -9
  117. package/dist/types/tools/todo.d.ts +10 -4
  118. package/dist/types/tools/xdev.d.ts +5 -1
  119. package/dist/types/tts/streaming-player.d.ts +14 -0
  120. package/dist/types/tts/vocalizer.d.ts +5 -0
  121. package/dist/types/utils/jj.d.ts +29 -0
  122. package/dist/types/utils/lang-from-path.d.ts +1 -0
  123. package/dist/types/utils/title-generator.d.ts +30 -0
  124. package/dist/types/vibe/runtime.d.ts +36 -8
  125. package/dist/types/web/search/index.d.ts +1 -1
  126. package/dist/types/web/search/provider.d.ts +15 -5
  127. package/dist/types/web/search/providers/firecrawl.d.ts +9 -0
  128. package/dist/types/web/search/types.d.ts +95 -1
  129. package/package.json +13 -12
  130. package/scripts/generate-share-viewer.ts +4 -6
  131. package/scripts/legacy-pi-virtual-module.ts +1 -1
  132. package/src/advisor/__tests__/advisor.test.ts +643 -0
  133. package/src/advisor/runtime.ts +144 -43
  134. package/src/async/job-manager.ts +90 -2
  135. package/src/cli/args.ts +2 -0
  136. package/src/cli/auth-gateway-cli.ts +18 -3
  137. package/src/cli/bench-cli.ts +395 -29
  138. package/src/cli/flag-tables.ts +3 -0
  139. package/src/cli/gc-cli.ts +2 -2
  140. package/src/cli/grievances-cli.ts +2 -2
  141. package/src/cli/usage-cli.ts +11 -2
  142. package/src/collab/host.ts +3 -2
  143. package/src/commands/bench.ts +18 -4
  144. package/src/commands/launch.ts +4 -0
  145. package/src/config/__tests__/model-registry.test.ts +147 -0
  146. package/src/config/model-registry.ts +50 -2
  147. package/src/config/model-resolver.ts +0 -1
  148. package/src/config/models-config-schema.ts +15 -4
  149. package/src/config/provider-globals.ts +9 -9
  150. package/src/config/service-tier.ts +26 -1
  151. package/src/config/settings-schema.ts +250 -54
  152. package/src/config/settings.ts +81 -2
  153. package/src/dap/client.ts +37 -4
  154. package/src/edit/index.ts +49 -12
  155. package/src/eval/py/executor.ts +103 -33
  156. package/src/exec/bash-executor.ts +89 -6
  157. package/src/exec/direnv.ts +145 -0
  158. package/src/export/html/args.ts +20 -0
  159. package/src/export/html/index.ts +45 -47
  160. package/src/export/html/template.css +19 -1
  161. package/src/export/html/template.html +6 -0
  162. package/src/export/html/template.js +21 -0
  163. package/src/export/html/tool-views.generated.js +31 -31
  164. package/src/export/html/web-palette.ts +116 -132
  165. package/src/export/share.ts +249 -49
  166. package/src/extensibility/extensions/loader.ts +29 -1
  167. package/src/extensibility/extensions/runner.ts +6 -0
  168. package/src/extensibility/extensions/types.ts +33 -2
  169. package/src/extensibility/legacy-pi-coding-agent-shim.ts +1 -0
  170. package/src/extensibility/legacy-pi-tui-shim.ts +10 -0
  171. package/src/extensibility/plugins/legacy-pi-compat.ts +870 -311
  172. package/src/hindsight/state.ts +64 -6
  173. package/src/internal-urls/local-protocol.ts +2 -1
  174. package/src/internal-urls/memory-protocol.ts +2 -2
  175. package/src/internal-urls/registry-helpers.ts +40 -0
  176. package/src/internal-urls/skill-protocol.ts +2 -2
  177. package/src/internal-urls/ssh-protocol.ts +2 -1
  178. package/src/internal-urls/vault-protocol.ts +2 -1
  179. package/src/live/audio-worklet.txt +59 -0
  180. package/src/live/browser-runtime.txt +221 -0
  181. package/src/live/controller.ts +464 -0
  182. package/src/live/prompts/agent-final-message.md +3 -0
  183. package/src/live/prompts/live-instructions.md +23 -0
  184. package/src/live/protocol.test.ts +140 -0
  185. package/src/live/protocol.ts +233 -0
  186. package/src/live/transport.ts +490 -0
  187. package/src/live/visualizer.ts +234 -0
  188. package/src/lsp/client.ts +52 -9
  189. package/src/lsp/clients/biome-client.ts +3 -4
  190. package/src/lsp/index.ts +44 -11
  191. package/src/main.ts +9 -1
  192. package/src/mcp/config-writer.test.ts +43 -0
  193. package/src/mcp/config-writer.ts +109 -82
  194. package/src/mcp/manager.ts +39 -8
  195. package/src/mcp/oauth-discovery.ts +10 -2
  196. package/src/mcp/render.ts +94 -35
  197. package/src/mcp/smithery-auth.test.ts +29 -0
  198. package/src/mcp/smithery-auth.ts +3 -2
  199. package/src/mcp/tool-bridge.ts +107 -11
  200. package/src/mcp/types.ts +7 -0
  201. package/src/memories/index.ts +40 -20
  202. package/src/memory-backend/tool-names.ts +2 -0
  203. package/src/mnemopi/backend.ts +24 -7
  204. package/src/modes/acp/acp-agent.ts +56 -10
  205. package/src/modes/acp/acp-event-mapper.ts +8 -1
  206. package/src/modes/components/agent-hub.ts +27 -10
  207. package/src/modes/components/login-dialog.ts +14 -4
  208. package/src/modes/components/oauth-selector.ts +24 -7
  209. package/src/modes/components/plan-review-overlay.ts +350 -35
  210. package/src/modes/components/settings-defs.ts +28 -3
  211. package/src/modes/components/settings-selector.ts +178 -6
  212. package/src/modes/components/status-line/component.jj-cache.test.ts +229 -0
  213. package/src/modes/components/status-line/component.ts +123 -2
  214. package/src/modes/components/tool-execution.test.ts +63 -2
  215. package/src/modes/components/tool-execution.ts +11 -2
  216. package/src/modes/controllers/command-controller.ts +70 -22
  217. package/src/modes/controllers/event-controller.ts +111 -16
  218. package/src/modes/controllers/extension-ui-controller.test.ts +1 -0
  219. package/src/modes/controllers/extension-ui-controller.ts +20 -4
  220. package/src/modes/controllers/live-command-controller.ts +178 -0
  221. package/src/modes/controllers/mcp-command-controller.ts +80 -41
  222. package/src/modes/controllers/omfg-controller.ts +44 -40
  223. package/src/modes/controllers/selector-controller.ts +18 -10
  224. package/src/modes/controllers/session-focus-controller.ts +6 -1
  225. package/src/modes/interactive-mode.ts +160 -38
  226. package/src/modes/rpc/rpc-client.ts +94 -3
  227. package/src/modes/rpc/rpc-frame.ts +164 -4
  228. package/src/modes/rpc/rpc-messages.ts +127 -0
  229. package/src/modes/rpc/rpc-mode.ts +79 -7
  230. package/src/modes/rpc/rpc-types.ts +34 -2
  231. package/src/modes/runtime-init.ts +2 -0
  232. package/src/modes/setup-wizard/scenes/model.ts +5 -7
  233. package/src/modes/setup-wizard/scenes/providers.ts +3 -2
  234. package/src/modes/setup-wizard/scenes/sign-in.ts +8 -2
  235. package/src/modes/setup-wizard/scenes/theme.ts +13 -3
  236. package/src/modes/setup-wizard/scenes/types.ts +9 -1
  237. package/src/modes/setup-wizard/scenes/web-search.ts +23 -10
  238. package/src/modes/setup-wizard/wizard-overlay.ts +1 -1
  239. package/src/modes/theme/theme.ts +1 -1
  240. package/src/modes/types.ts +3 -1
  241. package/src/modes/utils/context-usage.ts +23 -7
  242. package/src/modes/utils/ui-helpers.ts +10 -5
  243. package/src/plan-mode/plan-files.ts +40 -0
  244. package/src/prompts/bench/cache-prefix-chunk.md +1 -0
  245. package/src/prompts/bench/cache-prefix.md +3 -0
  246. package/src/prompts/bench/cache-suffix.md +1 -0
  247. package/src/prompts/goals/guided-goal-system.md +24 -3
  248. package/src/prompts/system/custom-system-prompt.md +1 -1
  249. package/src/prompts/system/project-prompt.md +8 -1
  250. package/src/prompts/system/subagent-async-pending.md +6 -0
  251. package/src/prompts/system/system-prompt.md +1 -1
  252. package/src/prompts/system/workflow-notice.md +48 -40
  253. package/src/prompts/system/xdev-mount-notice.md +4 -0
  254. package/src/prompts/tools/task.md +12 -2
  255. package/src/prompts/tools/todo.md +3 -1
  256. package/src/registry/agent-lifecycle.ts +116 -46
  257. package/src/registry/agent-registry.ts +35 -8
  258. package/src/registry/persisted-agents.ts +26 -2
  259. package/src/sdk.ts +255 -139
  260. package/src/secrets/index.ts +127 -4
  261. package/src/secrets/obfuscator.ts +2328 -105
  262. package/src/session/acp-permission-gate.ts +165 -0
  263. package/src/session/agent-session-error-log.test.ts +1 -1
  264. package/src/session/agent-session-events.ts +66 -0
  265. package/src/session/agent-session-types.ts +334 -0
  266. package/src/session/agent-session.ts +1910 -12213
  267. package/src/session/artifacts.ts +6 -5
  268. package/src/session/async-job-delivery.ts +74 -0
  269. package/src/session/bash-runner.ts +326 -0
  270. package/src/session/blob-store.test.ts +56 -0
  271. package/src/session/blob-store.ts +18 -2
  272. package/src/session/checkpoint-entries.ts +81 -0
  273. package/src/session/eval-runner.ts +212 -0
  274. package/src/session/indexed-session-storage.ts +9 -2
  275. package/src/session/irc-bridge.ts +203 -0
  276. package/src/session/messages.ts +263 -1
  277. package/src/session/model-controls.ts +714 -0
  278. package/src/session/prewalk.ts +252 -0
  279. package/src/session/queued-messages.ts +93 -0
  280. package/src/session/redis-session-storage.ts +51 -11
  281. package/src/session/retry-fallback-chains.ts +455 -0
  282. package/src/session/role-models.ts +85 -0
  283. package/src/session/session-advisors.ts +1679 -0
  284. package/src/session/session-context.test.ts +224 -1
  285. package/src/session/session-context.ts +47 -5
  286. package/src/session/session-entries.ts +8 -0
  287. package/src/session/session-handoff.ts +308 -0
  288. package/src/session/session-history-format.ts +20 -9
  289. package/src/session/session-loader.ts +10 -51
  290. package/src/session/session-maintenance.ts +2983 -0
  291. package/src/session/session-manager.ts +390 -30
  292. package/src/session/session-memory.ts +222 -0
  293. package/src/session/session-provider-boundary.ts +307 -0
  294. package/src/session/session-stats.ts +293 -0
  295. package/src/session/session-tools.ts +943 -0
  296. package/src/session/session-workspace.ts +53 -0
  297. package/src/session/stream-guards.ts +417 -0
  298. package/src/session/todo-tracker.ts +380 -0
  299. package/src/session/ttsr-coordinator.ts +496 -0
  300. package/src/session/turn-recovery.ts +1629 -0
  301. package/src/slash-commands/builtin-registry.ts +92 -11
  302. package/src/slash-commands/helpers/usage-report.ts +25 -4
  303. package/src/system-prompt.ts +41 -17
  304. package/src/task/executor.ts +288 -70
  305. package/src/task/index.ts +148 -111
  306. package/src/task/output-manager.ts +25 -3
  307. package/src/task/persisted-revive.ts +4 -3
  308. package/src/task/structured-subagent.ts +4 -1
  309. package/src/task/types.ts +16 -0
  310. package/src/tools/approval.ts +55 -10
  311. package/src/tools/bash-interactive.ts +26 -0
  312. package/src/tools/bash-skill-urls.ts +28 -2
  313. package/src/tools/bash.ts +339 -123
  314. package/src/tools/browser/launch.ts +9 -1
  315. package/src/tools/browser/tab-supervisor.ts +8 -5
  316. package/src/tools/browser.ts +94 -47
  317. package/src/tools/hub/index.ts +1 -1
  318. package/src/tools/hub/jobs.ts +67 -8
  319. package/src/tools/image-gen.ts +20 -28
  320. package/src/tools/image-providers.ts +50 -0
  321. package/src/tools/index.ts +12 -0
  322. package/src/tools/path-utils.ts +49 -2
  323. package/src/tools/read.ts +156 -88
  324. package/src/tools/renderers.ts +7 -1
  325. package/src/tools/report-tool-issue.ts +79 -28
  326. package/src/tools/todo.ts +101 -11
  327. package/src/tools/vibe.ts +1 -2
  328. package/src/tools/write.ts +51 -1
  329. package/src/tools/xdev.ts +67 -3
  330. package/src/tts/streaming-player.ts +62 -4
  331. package/src/tts/vocalizer.ts +18 -1
  332. package/src/utils/jj.ts +125 -4
  333. package/src/utils/lang-from-path.ts +7 -0
  334. package/src/utils/title-generator.ts +137 -1
  335. package/src/vibe/runtime.ts +852 -72
  336. package/src/web/search/index.ts +3 -6
  337. package/src/web/search/provider.ts +31 -16
  338. package/src/web/search/providers/firecrawl.ts +46 -13
  339. package/src/web/search/providers/xai.ts +7 -1
  340. package/src/web/search/types.ts +8 -1
package/src/sdk.ts CHANGED
@@ -14,6 +14,7 @@ import type {
14
14
  CredentialDisabledEvent,
15
15
  Message,
16
16
  Model,
17
+ ModelUsageHealth,
17
18
  ProviderSessionState,
18
19
  SimpleStreamOptions,
19
20
  } from "@oh-my-pi/pi-ai";
@@ -32,7 +33,7 @@ import {
32
33
  formatActiveRepoWatchdogPrompt,
33
34
  formatAdvisorContextPrompt,
34
35
  } from "./advisor";
35
- import { type AsyncJob, AsyncJobManager } from "./async";
36
+ import { AsyncJobManager } from "./async";
36
37
  import { AutoLearnController, buildAutoLearnInstructions } from "./autolearn/controller";
37
38
  import { createAutoresearchExtension } from "./autoresearch";
38
39
  import { loadCapability } from "./capability";
@@ -55,6 +56,7 @@ import {
55
56
  resolveModelRoleValue,
56
57
  } from "./config/model-resolver";
57
58
  import { loadPromptTemplates as loadPromptTemplatesInternal, type PromptTemplate } from "./config/prompt-templates";
59
+ import { applyProviderGlobalsFromSettings } from "./config/provider-globals";
58
60
  import { buildServiceTierByFamily } from "./config/service-tier";
59
61
  import { Settings, type SkillsSettings } from "./config/settings";
60
62
  import { CursorExecHandlers } from "./cursor";
@@ -105,19 +107,22 @@ import {
105
107
  } from "./mcp";
106
108
  import { MCP_CONNECTION_STATUS_EVENT_CHANNEL, type McpConnectionStatusEvent } from "./mcp/startup-events";
107
109
  import { createSessionMemoryRuntimeContext, resolveMemoryBackend } from "./memory-backend";
110
+ import { MEMORY_BACKEND_TOOL_NAMES } from "./memory-backend/tool-names";
108
111
  import type { MnemopiSessionState } from "./mnemopi/state";
109
- import asyncResultTemplate from "./prompts/tools/async-result.md" with { type: "text" };
110
112
  import lateDiagnosticTemplate from "./prompts/tools/lsp-late-diagnostic.md" with { type: "text" };
111
113
  import { AgentLifecycleManager } from "./registry/agent-lifecycle";
112
- import { AgentRegistry, MAIN_AGENT_ID } from "./registry/agent-registry";
114
+ import { type AgentRef, AgentRegistry, MAIN_AGENT_ID } from "./registry/agent-registry";
113
115
  import {
114
116
  collectEnvSecrets,
115
117
  deobfuscateSessionContext,
116
118
  deobfuscateToolArguments,
119
+ getExistingSecretPlaceholderKey,
120
+ getSecretPlaceholderKey,
117
121
  loadSecrets,
118
122
  obfuscateMessages,
119
123
  obfuscateProviderContext,
120
124
  SecretObfuscator,
125
+ secretEntriesNeedPlaceholderKey,
121
126
  } from "./secrets";
122
127
  import { AgentSession, type InitialRetryFallbackState, type PlanYolo, type Prewalk } from "./session/agent-session";
123
128
  import { discoverAuthStorage as discoverAuthStorageFromConfig } from "./session/auth-broker-config";
@@ -132,6 +137,12 @@ import {
132
137
  wrapSteeringForModel,
133
138
  } from "./session/messages";
134
139
  import { clampProviderContextImages } from "./session/provider-image-budget";
140
+ import {
141
+ expandDefaultRetryFallbackChains,
142
+ findRetryFallbackCandidates,
143
+ type RetryFallbackResolutionContext,
144
+ resolveRetryFallbackChainKey,
145
+ } from "./session/retry-fallback-chains";
135
146
  import { getRestorableSessionModels } from "./session/session-context";
136
147
  import { SessionManager } from "./session/session-manager";
137
148
  import { createSettingsAwareStreamFn } from "./session/settings-stream-fn";
@@ -173,15 +184,9 @@ import {
173
184
  GrepTool,
174
185
  getSearchTools,
175
186
  HIDDEN_TOOLS,
176
- isImageProviderPreference,
177
187
  isMountableUnderXdev,
178
- isSearchProviderId,
179
- isSearchProviderPreference,
180
188
  type LspStartupServerInfo,
181
189
  ReadTool,
182
- setExcludedSearchProviders,
183
- setPreferredImageProvider,
184
- setPreferredSearchProvider,
185
190
  type Tool,
186
191
  type ToolSession,
187
192
  WebSearchTool,
@@ -199,62 +204,14 @@ import { ttsTool } from "./tools/tts";
199
204
  import { resolveActiveRepoContext } from "./utils/active-repo-context";
200
205
  import { EventBus } from "./utils/event-bus";
201
206
  import { buildNamedToolChoice } from "./utils/tool-choice";
207
+ import { VibeSessionRegistry } from "./vibe/runtime";
202
208
  import { buildWorkspaceTree, type WorkspaceTree } from "./workspace-tree";
203
209
 
204
- type AsyncResultEntry = {
205
- jobId: string;
206
- result: string;
207
- job: AsyncJob | undefined;
208
- durationMs: number | undefined;
209
- };
210
-
211
- type AsyncResultJobDetails = {
212
- jobId: string;
213
- type?: "bash" | "task";
214
- label?: string;
215
- durationMs?: number;
216
- };
217
-
218
- type AsyncResultDetails = {
219
- jobs: AsyncResultJobDetails[];
220
- };
221
-
222
210
  type McpNotificationEntry = {
223
211
  serverName: string;
224
212
  uri: string;
225
213
  };
226
214
 
227
- function buildAsyncResultBatchMessage(entries: AsyncResultEntry[]): CustomMessage<AsyncResultDetails> | null {
228
- if (entries.length === 0) return null;
229
- const jobs = entries.map(entry => ({
230
- jobId: entry.jobId,
231
- result: entry.result,
232
- type: entry.job?.type,
233
- label: entry.job?.label,
234
- durationMs: entry.durationMs,
235
- }));
236
- const details: AsyncResultDetails = {
237
- jobs: jobs.map(job => ({
238
- jobId: job.jobId,
239
- type: job.type,
240
- label: job.label,
241
- durationMs: job.durationMs,
242
- })),
243
- };
244
- return {
245
- role: "custom",
246
- customType: "async-result",
247
- content: prompt.render(asyncResultTemplate, {
248
- multiple: jobs.length > 1,
249
- jobs,
250
- }),
251
- display: true,
252
- attribution: "agent",
253
- details,
254
- timestamp: Date.now(),
255
- };
256
- }
257
-
258
215
  type LateDiagnosticsDetails = {
259
216
  files: Array<{ path: string; summary: string; errored: boolean; messages: string[] }>;
260
217
  };
@@ -371,6 +328,8 @@ function applyMCPEnvironment(result: { exaApiKeys: string[] }): void {
371
328
  export interface CreateAgentSessionOptions {
372
329
  /** Working directory for project-local discovery. Default: getProjectDir() */
373
330
  cwd?: string;
331
+ /** Additional workspace directories beyond cwd (multi-root), absolute or cwd-relative. */
332
+ additionalDirectories?: string[];
374
333
  /** Global config directory. Default: ~/.omp/agent */
375
334
  agentDir?: string;
376
335
  /** Spawns to allow. Default: "*" */
@@ -525,6 +484,13 @@ export interface CreateAgentSessionOptions {
525
484
  agentDisplayName?: string;
526
485
  /** Optional shared agent registry for IRC routing. Default: AgentRegistry.global(). */
527
486
  agentRegistry?: AgentRegistry;
487
+ /**
488
+ * Registry generation authorized for this creation. `null` requires the id
489
+ * to be absent; an AgentRef allows a parked revival to reuse only that ref.
490
+ * Undefined preserves legacy unconditional registration for external SDK callers.
491
+ * @internal
492
+ */
493
+ expectedAgentRef?: AgentRef | null;
528
494
  /** Parent task ID prefix for nested artifact naming (e.g., "Extensions") */
529
495
  parentTaskPrefix?: string;
530
496
  /**
@@ -554,6 +520,11 @@ export interface CreateAgentSessionOptions {
554
520
 
555
521
  /** Whether UI is available (enables interactive tools like ask). Default: false */
556
522
  hasUI?: boolean;
523
+ /**
524
+ * Defer `confirm` reserve-policy fallback until AgentSession prompt-time UI is configured.
525
+ * ACP uses this while capabilities are negotiated without enabling UI-only tools.
526
+ */
527
+ deferUsageReserveConfirmation?: boolean;
557
528
 
558
529
  /**
559
530
  * Opt-in OpenTelemetry instrumentation forwarded to the underlying Agent.
@@ -1292,26 +1263,22 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1292
1263
  discoveredSkillsPromise?.catch(() => {});
1293
1264
 
1294
1265
  // Initialize provider preferences from settings
1295
- const excludedWebSearchProviders = settings.get("providers.webSearchExclude");
1296
- if (Array.isArray(excludedWebSearchProviders)) {
1297
- setExcludedSearchProviders(excludedWebSearchProviders.filter(isSearchProviderId));
1298
- }
1299
-
1300
- const webSearchProvider = settings.get("providers.webSearch");
1301
- if (typeof webSearchProvider === "string" && isSearchProviderPreference(webSearchProvider)) {
1302
- setPreferredSearchProvider(webSearchProvider);
1303
- }
1304
-
1305
- const imageProvider = settings.get("providers.image");
1306
- if (isImageProviderPreference(imageProvider)) {
1307
- setPreferredImageProvider(imageProvider);
1308
- }
1266
+ applyProviderGlobalsFromSettings(settings);
1309
1267
 
1310
1268
  const sessionManager =
1311
1269
  options.sessionManager ??
1312
1270
  logger.time("sessionManager", () =>
1313
1271
  SessionManager.create(cwd, SessionManager.getDefaultSessionDir(cwd, agentDir)),
1314
1272
  );
1273
+ const configuredDirs = options.additionalDirectories
1274
+ ? options.additionalDirectories
1275
+ : settings.get("workspace.additionalDirectories");
1276
+ if (configuredDirs.length > 0) {
1277
+ // Merge with any roots restored from the session header (resume/fork), not replace.
1278
+ const existing = sessionManager.getAdditionalDirectories();
1279
+ const merged = [...new Set([...existing, ...configuredDirs])];
1280
+ await sessionManager.setAdditionalDirectories(merged);
1281
+ }
1315
1282
  const providerSessionId = options.providerSessionId ?? sessionManager.getSessionId();
1316
1283
  const forkCacheShapeChanged =
1317
1284
  options.model !== undefined ||
@@ -1349,8 +1316,30 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1349
1316
  const fileEntries = await logger.time("loadSecrets", loadSecrets, cwd, agentDir);
1350
1317
  const envEntries = collectEnvSecrets();
1351
1318
  const allEntries = [...envEntries, ...fileEntries];
1319
+ const needsPlaceholderKey = secretEntriesNeedPlaceholderKey(allEntries);
1320
+ const placeholderKey = needsPlaceholderKey
1321
+ ? await getSecretPlaceholderKey(agentDir)
1322
+ : await getExistingSecretPlaceholderKey(agentDir);
1352
1323
  if (allEntries.length > 0) {
1353
- obfuscator = new SecretObfuscator(allEntries);
1324
+ // The persisted placeholder key — and creating its key file under the
1325
+ // configured agentDir — is only needed for reversible obfuscate-mode
1326
+ // placeholders, or for a default (no custom `replacement`) replace-mode
1327
+ // regex whose key-derived idempotent fallback marker needs a stable key
1328
+ // across restarts (see `secretEntryNeedsPlaceholderKey`). A replace-only
1329
+ // secrets set with no such regex must not require the key; otherwise a
1330
+ // headless run with an unwritable default config root fails startup for a
1331
+ // feature it does not use.
1332
+ obfuscator = new SecretObfuscator(allEntries, placeholderKey);
1333
+ }
1334
+ if (obfuscator?.hasSecrets() !== true && placeholderKey !== undefined) {
1335
+ // No configured entry produced an active secret (e.g. only ignored short
1336
+ // plain entries, or no entries at all), but a persisted key exists. Build a
1337
+ // redaction-only obfuscator so a tool read of the key file does not ship the
1338
+ // reusable HMAC key to the provider.
1339
+ obfuscator = new SecretObfuscator(
1340
+ [{ type: "plain", mode: "replace", content: placeholderKey }],
1341
+ placeholderKey,
1342
+ );
1354
1343
  }
1355
1344
  }
1356
1345
  const secretsEnabled = obfuscator?.hasSecrets() === true;
@@ -1566,28 +1555,6 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1566
1555
  const restrictToolNames = options.restrictToolNames === true;
1567
1556
  const enableLsp = !restrictToolNames && (options.enableLsp ?? true);
1568
1557
  const asyncMaxJobs = Math.min(100, Math.max(1, settings.get("async.maxJobs") ?? 100));
1569
- const ASYNC_INLINE_RESULT_MAX_CHARS = 12_000;
1570
- const ASYNC_PREVIEW_MAX_CHARS = 4_000;
1571
- const formatAsyncResultForFollowUp = async (result: string): Promise<string> => {
1572
- if (result.length <= ASYNC_INLINE_RESULT_MAX_CHARS) {
1573
- return result;
1574
- }
1575
-
1576
- const preview = `${result.slice(0, ASYNC_PREVIEW_MAX_CHARS)}\n\n[Output truncated. Showing first ${ASYNC_PREVIEW_MAX_CHARS.toLocaleString()} characters.]`;
1577
- try {
1578
- const { path: artifactPath, id: artifactId } = await sessionManager.allocateArtifactPath("async");
1579
- if (artifactPath && artifactId) {
1580
- await Bun.write(artifactPath, result);
1581
- return `${preview}\nFull output: artifact://${artifactId}`;
1582
- }
1583
- } catch (error) {
1584
- logger.warn("Failed to persist async follow-up artifact", {
1585
- error: error instanceof Error ? error.message : String(error),
1586
- });
1587
- }
1588
-
1589
- return preview;
1590
- };
1591
1558
  // Only the first top-level session in a process owns an AsyncJobManager.
1592
1559
  // Subagents inherit the parent's manager via `AsyncJobManager.instance()`
1593
1560
  // (set below), and any additional top-level session spun up in-process
@@ -1596,24 +1563,12 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1596
1563
  // owning session's manager and break the `task`/`bash` async paths
1597
1564
  // (issue #1923). The `instance()` guard means later sessions also skip
1598
1565
  // constructing an orphaned manager that nothing would ever route to.
1566
+ // Delivery is owner-routed: every AgentSession registers its own sink
1567
+ // (see session/async-job-delivery.ts), so the manager takes no default
1568
+ // onJobComplete here.
1599
1569
  const asyncJobManager =
1600
1570
  !options.parentTaskPrefix && !AsyncJobManager.instance()
1601
- ? new AsyncJobManager({
1602
- maxRunningJobs: asyncMaxJobs,
1603
- onJobComplete: async (jobId, result, job) => {
1604
- if (!session || asyncJobManager!.isDeliverySuppressed(jobId)) return;
1605
- const formattedResult = await formatAsyncResultForFollowUp(result);
1606
- if (asyncJobManager!.isDeliverySuppressed(jobId)) return;
1607
-
1608
- const durationMs = job ? Math.max(0, Date.now() - job.startTime) : undefined;
1609
- session.yieldQueue.enqueue<AsyncResultEntry>("async-result", {
1610
- jobId,
1611
- result: formattedResult,
1612
- job,
1613
- durationMs,
1614
- });
1615
- },
1616
- })
1571
+ ? new AsyncJobManager({ maxRunningJobs: asyncMaxJobs })
1617
1572
  : undefined;
1618
1573
 
1619
1574
  const scopedAsyncJobManager = asyncJobManager ?? (options.parentTaskPrefix ? AsyncJobManager.instance() : undefined);
@@ -1623,15 +1578,18 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1623
1578
  const resolvedAgentDisplayName =
1624
1579
  options.agentDisplayName ?? ((options.taskDepth ?? 0) > 0 || options.parentTaskPrefix ? "sub" : "main");
1625
1580
  const agentKind = (options.taskDepth ?? 0) > 0 || options.parentTaskPrefix ? ("sub" as const) : ("main" as const);
1581
+ let registeredAgentRef: AgentRef | undefined;
1626
1582
  /**
1627
1583
  * Forget the agent ref on teardown — unless the agent is being parked (or is
1628
1584
  * already parked). Parking disposes the session but keeps the ref addressable
1629
1585
  * (history://, revive); only process teardown / explicit kill unregisters.
1630
1586
  */
1631
1587
  const unregisterUnlessParked = (): void => {
1632
- if (agentRegistry.get(resolvedAgentId)?.status === "parked") return;
1633
- if (AgentLifecycleManager.global().isParking(resolvedAgentId)) return;
1634
- agentRegistry.unregister(resolvedAgentId);
1588
+ const ref = registeredAgentRef;
1589
+ if (!ref || agentRegistry.get(resolvedAgentId) !== ref) return;
1590
+ if (ref.status === "parked") return;
1591
+ if (AgentLifecycleManager.global().isParking(resolvedAgentId, ref)) return;
1592
+ agentRegistry.unregister(resolvedAgentId, ref);
1635
1593
  };
1636
1594
  const evalKernelOwnerId = `agent-session:${Snowflake.next()}`;
1637
1595
 
@@ -1660,6 +1618,9 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1660
1618
  isToolActive: name => activeToolNames.has(name),
1661
1619
  setActiveToolNames,
1662
1620
  hasUI: options.hasUI ?? false,
1621
+ get additionalDirectories() {
1622
+ return sessionManager.getAdditionalDirectories();
1623
+ },
1663
1624
  enableLsp,
1664
1625
  enableIrc: restrictToolNames ? false : options.enableIrc,
1665
1626
  restrictToolNames,
@@ -1684,6 +1645,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1684
1645
  prewalkArmed: options.prewalk !== undefined,
1685
1646
  taskDepth: options.taskDepth ?? 0,
1686
1647
  getSessionFile: () => sessionManager.getSessionFile() ?? null,
1648
+ sessionManager,
1687
1649
  getEvalKernelOwnerId: () => evalKernelOwnerId,
1688
1650
  getEvalSessionId: () =>
1689
1651
  session?.getEvalSessionId() ?? options.parentEvalSessionId ?? defaultEvalSessionId(toolSession),
@@ -1696,6 +1658,11 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1696
1658
  getAgentId: () => resolvedAgentId,
1697
1659
  getToolByName: name => session?.getToolByName(name),
1698
1660
  agentRegistry,
1661
+ // The global lifecycle releases through AgentRegistry.global(); wiring it
1662
+ // onto a caller-supplied registry would report a cancel while releasing an
1663
+ // unrelated global ref. With no lifecycle, hub cancel falls back to
1664
+ // dispose + unregister on the session's own registry.
1665
+ agentLifecycle: options.agentRegistry ? undefined : () => AgentLifecycleManager.global(),
1699
1666
  getSessionSpawns: () => options.spawns ?? "*",
1700
1667
  getModelString: () => (hasExplicitModel && model ? formatModelString(model) : undefined),
1701
1668
  getActiveModelString,
@@ -2026,8 +1993,11 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
2026
1993
  // the background.
2027
1994
  await modelRegistry.refreshRuntimeProviders("offline");
2028
1995
  // Continue runtime discovery in the background (cache-aware) so startup is
2029
- // only blocked on local cache reads, not provider network fetches.
2030
- void modelRegistry.refreshRuntimeProviders().catch(error => {
1996
+ // only blocked on local cache reads, not provider network fetches. Stash
1997
+ // the promise so the deferred `--model` retry below can await it instead
1998
+ // of starting a second concurrent discovery pass (the unfiltered
1999
+ // `refresh()` also covers runtime model managers).
2000
+ const runtimeDiscoveryPromise = modelRegistry.refreshRuntimeProviders().catch(error => {
2031
2001
  logger.warn("runtime provider discovery failed", {
2032
2002
  error: error instanceof Error ? error.message : String(error),
2033
2003
  });
@@ -2076,8 +2046,42 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
2076
2046
  // registered. Use the same CLI resolver as the immediate path so bare role
2077
2047
  // names, exact model names, and provider selectors keep one precedence rule.
2078
2048
  if (!model && deferredModelPatterns.length > 0) {
2079
- const availableModels = modelRegistry.getAll();
2049
+ // Deferred `--model` patterns almost always failed at the immediate
2050
+ // path (main.ts:881) precisely because discovery-backed providers
2051
+ // hadn't populated yet. Await the in-flight runtime discovery
2052
+ // already kicked off above (stash + reuse avoids a second concurrent
2053
+ // `#refreshRuntimeDiscoveries` pass for the same runtime model
2054
+ // managers; it resolves instantly when no runtime managers are
2055
+ // registered). `refreshRuntimeProviders()` only covers runtime model
2056
+ // managers, not config-discovery providers (e.g. user-configured
2057
+ // ollama); fall back to a full cache-aware refresh only when the
2058
+ // runtime pass didn't surface a match AND config-discovery providers
2059
+ // exist to fetch from. By then runtime managers short-circuit on the
2060
+ // fresh cache written by the awaited pass, closing the double-fetch
2061
+ // window.
2062
+ await logger.time("resolveModelDiscoveryDeferredRetry", () => runtimeDiscoveryPromise);
2080
2063
  const matchPreferences = getModelMatchPreferences(settings);
2064
+ const runtimeResolved = deferredModelPatterns.some(pattern =>
2065
+ pattern.split(",").some(selector => {
2066
+ const trimmedSelector = selector.trim();
2067
+ if (!trimmedSelector) return false;
2068
+ const resolved = resolveCliModel({
2069
+ cliModel: trimmedSelector,
2070
+ modelRegistry,
2071
+ settings,
2072
+ preferences: matchPreferences,
2073
+ });
2074
+ return Boolean(
2075
+ resolved.model || (resolved.configuredPatterns && resolved.configuredPatterns.length > 0),
2076
+ );
2077
+ }),
2078
+ );
2079
+ if (!runtimeResolved && modelRegistry.getDiscoverableProviders().length > 0) {
2080
+ await logger.time("resolveModelDiscoveryFallbackNonRuntime", () =>
2081
+ modelRegistry.refresh("online-if-uncached"),
2082
+ );
2083
+ }
2084
+ const availableModels = modelRegistry.getAll();
2081
2085
  const expandedModelPatterns = deferredModelPatterns.flatMap(pattern =>
2082
2086
  pattern.split(",").flatMap(selector => {
2083
2087
  const trimmedSelector = selector.trim();
@@ -2099,27 +2103,38 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
2099
2103
  if (!resolved.configuredRole || !settings.get("retry.modelFallback")) {
2100
2104
  return primaryPatterns;
2101
2105
  }
2102
- const fallbackChains = settings.get("retry.fallbackChains");
2103
- const roleFallbacks =
2104
- fallbackChains[resolved.configuredRole] ??
2105
- (resolved.configuredRole === "default" ? undefined : fallbackChains.default);
2106
- if (!Array.isArray(roleFallbacks)) return primaryPatterns;
2106
+ const fallbackContext: RetryFallbackResolutionContext = {
2107
+ chains: expandDefaultRetryFallbackChains(settings.get("retry.fallbackChains"), [
2108
+ ...Object.keys(settings.getModelRoles()),
2109
+ resolved.configuredRole,
2110
+ ]),
2111
+ getModelRole: role => settings.getModelRole(role),
2112
+ modelLookup: modelRegistry,
2113
+ };
2107
2114
  const originalSelector = resolved.configuredPatterns[0];
2115
+ const originalModel = parseModelPattern(originalSelector, availableModels, matchPreferences).model;
2116
+ const chainKey = resolveRetryFallbackChainKey(
2117
+ fallbackContext,
2118
+ originalSelector,
2119
+ originalModel,
2120
+ resolved.configuredRole,
2121
+ );
2122
+ if (!chainKey) return primaryPatterns;
2108
2123
  const parsedOriginal = parseModelString(originalSelector, {
2109
2124
  allowMaxSuffix: true,
2110
2125
  allowAutoAlias: true,
2111
2126
  isLiteralModelId: (provider, id) => modelRegistry.find(provider, id) !== undefined,
2112
2127
  });
2113
2128
  const retryFallback: InitialRetryFallbackState = {
2114
- role: resolved.configuredRole,
2129
+ role: chainKey,
2115
2130
  originalSelector,
2116
2131
  originalThinkingLevel: parsedOriginal?.thinkingLevel,
2117
2132
  };
2118
2133
  return [
2119
2134
  ...primaryPatterns,
2120
- ...roleFallbacks
2121
- .filter((pattern): pattern is string => typeof pattern === "string")
2122
- .map(pattern => ({ pattern, retryFallback })),
2135
+ ...findRetryFallbackCandidates(fallbackContext, chainKey, originalSelector, originalModel, {
2136
+ allowMissingPrimary: true,
2137
+ }).map(candidate => ({ pattern: candidate.raw, retryFallback })),
2123
2138
  ];
2124
2139
  }
2125
2140
  if (resolved.model) {
@@ -2139,10 +2154,68 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
2139
2154
  }));
2140
2155
  }),
2141
2156
  );
2157
+ let usageFallbackTriggered = false;
2142
2158
  for (let patternIndex = 0; patternIndex < expandedModelPatterns.length; patternIndex += 1) {
2143
2159
  const { pattern, retryFallback } = expandedModelPatterns[patternIndex];
2144
2160
  const primary = parseModelPattern(pattern, availableModels, matchPreferences);
2145
2161
  if (!primary.model || (retryFallback && !hasModelAuth(primary.model))) continue;
2162
+ let hasUsageFallbackCandidate = false;
2163
+ for (
2164
+ let candidateIndex = patternIndex + 1;
2165
+ candidateIndex < expandedModelPatterns.length;
2166
+ candidateIndex += 1
2167
+ ) {
2168
+ const candidate = parseModelPattern(
2169
+ expandedModelPatterns[candidateIndex].pattern,
2170
+ availableModels,
2171
+ matchPreferences,
2172
+ );
2173
+ if (candidate.model && hasModelAuth(candidate.model)) {
2174
+ hasUsageFallbackCandidate = true;
2175
+ break;
2176
+ }
2177
+ }
2178
+ const usageReservePolicy = settings.get("retry.usageReservePolicy");
2179
+ if (
2180
+ (hasUsageFallbackCandidate || usageReservePolicy === "fail-closed") &&
2181
+ settings.get("retry.modelFallback") &&
2182
+ settings.get("retry.usageAwareFallback")
2183
+ ) {
2184
+ let usageHealth: ModelUsageHealth | undefined;
2185
+ try {
2186
+ usageHealth = await modelRegistry.authStorage.getModelUsageHealth(primary.model.provider, {
2187
+ modelId: primary.model.id,
2188
+ baseUrl: primary.model.baseUrl,
2189
+ reserveFraction: settings.get("retry.usageReservePct") / 100,
2190
+ });
2191
+ } catch (error) {
2192
+ logger.debug("Usage-aware model preflight failed open", {
2193
+ provider: primary.model.provider,
2194
+ model: primary.model.id,
2195
+ error: String(error),
2196
+ });
2197
+ }
2198
+ if (usageHealth?.state === "depleted") {
2199
+ if (usageReservePolicy === "fail-closed") {
2200
+ throw new Error(
2201
+ `Usage depleted for ${primary.model.provider}/${primary.model.id}; reserve policy is fail-closed.`,
2202
+ );
2203
+ }
2204
+ usageFallbackTriggered = true;
2205
+ continue;
2206
+ }
2207
+ if (usageHealth?.state === "reserve") {
2208
+ if (usageReservePolicy === "fail-closed") {
2209
+ throw new Error(
2210
+ `Usage reserve reached for ${primary.model.provider}/${primary.model.id}; reserve policy is fail-closed.`,
2211
+ );
2212
+ }
2213
+ if (usageReservePolicy === "auto" || (!options.hasUI && !options.deferUsageReserveConfirmation)) {
2214
+ usageFallbackTriggered = true;
2215
+ continue;
2216
+ }
2217
+ }
2218
+ }
2146
2219
  let selectedModel = primary.model;
2147
2220
  let selectedThinkingLevel = primary.thinkingLevel;
2148
2221
  let selectedExplicitThinkingLevel = primary.explicitThinkingLevel;
@@ -2222,7 +2295,8 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
2222
2295
  }
2223
2296
  }
2224
2297
  model = selectedModel;
2225
- initialRetryFallback = retryFallback;
2298
+ initialRetryFallback =
2299
+ retryFallback && usageFallbackTriggered ? { ...retryFallback, pinned: true } : retryFallback;
2226
2300
  modelFallbackMessage = undefined;
2227
2301
  if (selectedExplicitThinkingLevel) {
2228
2302
  restoredSessionThinkingLevel = selectedThinkingLevel;
@@ -2602,8 +2676,13 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
2602
2676
  }
2603
2677
  const defaultPrompt = await buildSystemPromptInternal({
2604
2678
  cwd,
2679
+ additionalWorkspaceRoots: sessionManager.getAdditionalDirectories(),
2605
2680
  xdevTools: toolSession.xdevRegistry?.entries() ?? [],
2606
- xdevDocs: toolSession.xdevRegistry?.docsAll() ?? "",
2681
+ xdevDocs:
2682
+ toolSession.xdevRegistry?.docsAll(
2683
+ settings.get("tools.xdevDocs"),
2684
+ settings.get("tools.xdevInlineDevices"),
2685
+ ) ?? "",
2607
2686
  autoQaEnabled: !restrictToolNames && isAutoQaEnabled(settings),
2608
2687
  resolvedCustomPrompt: options.customSystemPrompt,
2609
2688
  skills: session?.skills ?? skills,
@@ -2708,16 +2787,25 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
2708
2787
  // so that subagents launched in the same parallel batch can see each other in
2709
2788
  // their initial `# IRC Peers` block (rendered inside `rebuildSystemPrompt`).
2710
2789
  // The session reference is attached after construction below.
2711
- agentRegistry.register({
2790
+ const registrationInput = {
2712
2791
  id: resolvedAgentId,
2713
2792
  displayName: resolvedAgentDisplayName,
2714
2793
  kind: agentKind,
2715
2794
  parentId: options.parentAgentId,
2716
2795
  session: null,
2717
2796
  sessionFile: sessionManager.getSessionFile() ?? null,
2718
- status: "running",
2719
- });
2720
- hasRegistered = true;
2797
+ status: "running" as const,
2798
+ };
2799
+ registeredAgentRef =
2800
+ options.expectedAgentRef === undefined
2801
+ ? agentRegistry.register(registrationInput)
2802
+ : agentRegistry.registerIfAvailable(registrationInput, options.expectedAgentRef);
2803
+ if (!registeredAgentRef) {
2804
+ throw new Error(`Agent "${resolvedAgentId}" is already owned by another session generation.`);
2805
+ }
2806
+ // A reused parked ref remains parked until the new AgentSession is fully
2807
+ // constructed and attached. Startup failure therefore leaves it revivable.
2808
+ hasRegistered = options.expectedAgentRef === undefined || options.expectedAgentRef === null;
2721
2809
 
2722
2810
  // Partition the initial enabled set for the xd:// transport: ambient
2723
2811
  // discoverable tools become mounted devices, while explicitly requested
@@ -3028,6 +3116,16 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
3028
3116
  skillsSettings: settings.getGroup("skills"),
3029
3117
  modelRegistry,
3030
3118
  toolRegistry,
3119
+ memoryAgentDir: agentDir,
3120
+ memoryTaskDepth: taskDepth,
3121
+ createMemoryTools: restrictToolNames
3122
+ ? undefined
3123
+ : async () => {
3124
+ const tools = await Promise.all(
3125
+ MEMORY_BACKEND_TOOL_NAMES.map(name => BUILTIN_TOOLS[name](toolSession)),
3126
+ );
3127
+ return tools.filter((tool): tool is AgentTool => tool !== null);
3128
+ },
3031
3129
  createVibeTools:
3032
3130
  (options.taskDepth ?? 0) === 0 && !options.parentTaskPrefix
3033
3131
  ? () => createVibeTools(toolSession)
@@ -3074,12 +3172,6 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
3074
3172
  titleSystemPrompt: options.titleSystemPrompt,
3075
3173
  });
3076
3174
  hasSession = true;
3077
- if (asyncJobManager) {
3078
- session.yieldQueue.register<AsyncResultEntry>("async-result", {
3079
- isStale: entry => asyncJobManager.isDeliverySuppressed(entry.jobId),
3080
- build: buildAsyncResultBatchMessage,
3081
- });
3082
- }
3083
3175
  session.yieldQueue.register<McpNotificationEntry>("mcp-notification", {
3084
3176
  build: buildMcpNotificationBatchMessage,
3085
3177
  });
@@ -3091,7 +3183,19 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
3091
3183
  // Attach the live session to the pre-registered ref so peers can route IRC
3092
3184
  // messages here. Refresh sessionFile in case it was unavailable at pre-register
3093
3185
  // time. The dispose wrapper below unregisters on teardown (unless parked).
3094
- agentRegistry.attachSession(resolvedAgentId, session, sessionManager.getSessionFile() ?? null);
3186
+ if (
3187
+ !registeredAgentRef ||
3188
+ !agentRegistry.attachSession(
3189
+ resolvedAgentId,
3190
+ session,
3191
+ sessionManager.getSessionFile() ?? null,
3192
+ registeredAgentRef,
3193
+ ) ||
3194
+ !agentRegistry.setStatus(resolvedAgentId, "running", registeredAgentRef)
3195
+ ) {
3196
+ throw new Error(`Agent "${resolvedAgentId}" was replaced during session initialization.`);
3197
+ }
3198
+ hasRegistered = true;
3095
3199
  {
3096
3200
  const originalDispose = session.dispose.bind(session);
3097
3201
  session.dispose = async () => {
@@ -3105,6 +3209,17 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
3105
3209
  // adopted subagent sessions, revivers. Tear it down while shared
3106
3210
  // resources (kernels, MCP, LSP) are still live. Subagent disposal
3107
3211
  // must NOT touch the global lifecycle.
3212
+ const vibeRegistry = VibeSessionRegistry.global();
3213
+ const vibeParentSession = {
3214
+ getAgentId: () => resolvedAgentId,
3215
+ getSessionId: () => sessionManager.getSessionId(),
3216
+ getSessionFile: () => sessionManager.getSessionFile() ?? null,
3217
+ sessionManager,
3218
+ asyncJobManager: scopedAsyncJobManager,
3219
+ settings,
3220
+ getActiveModelString,
3221
+ };
3222
+ await vibeRegistry.suspendScope(vibeRegistry.ownerScope(vibeParentSession), scopedAsyncJobManager);
3108
3223
  await AgentLifecycleManager.global().dispose();
3109
3224
  }
3110
3225
  await originalDispose();
@@ -3346,6 +3461,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
3346
3461
  try {
3347
3462
  if (hasSession) {
3348
3463
  await session.dispose();
3464
+ if (hasRegistered) unregisterUnlessParked();
3349
3465
  } else {
3350
3466
  if (hasRegistered) unregisterUnlessParked();
3351
3467
  if (asyncJobManager) {