open-multi-agent-kit 0.96.0 → 0.96.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (351) hide show
  1. package/CHANGELOG.md +81 -0
  2. package/README.md +34 -6
  3. package/dist/cli/args.d.ts.map +1 -1
  4. package/dist/cli/args.js +1 -0
  5. package/dist/cli/args.js.map +1 -1
  6. package/dist/commands/doctor-provider.d.ts.map +1 -1
  7. package/dist/commands/doctor-provider.js +15 -20
  8. package/dist/commands/doctor-provider.js.map +1 -1
  9. package/dist/commands/resource-doctor-cli.d.ts +22 -0
  10. package/dist/commands/resource-doctor-cli.d.ts.map +1 -0
  11. package/dist/commands/resource-doctor-cli.js +98 -0
  12. package/dist/commands/resource-doctor-cli.js.map +1 -0
  13. package/dist/commands/sdk-session-cli.d.ts +24 -0
  14. package/dist/commands/sdk-session-cli.d.ts.map +1 -0
  15. package/dist/commands/sdk-session-cli.js +225 -0
  16. package/dist/commands/sdk-session-cli.js.map +1 -0
  17. package/dist/core/advisory-judge-model.d.ts +18 -0
  18. package/dist/core/advisory-judge-model.d.ts.map +1 -0
  19. package/dist/core/advisory-judge-model.js +168 -0
  20. package/dist/core/advisory-judge-model.js.map +1 -0
  21. package/dist/core/advisory-judge-response.d.ts +8 -0
  22. package/dist/core/advisory-judge-response.d.ts.map +1 -0
  23. package/dist/core/advisory-judge-response.js +61 -0
  24. package/dist/core/advisory-judge-response.js.map +1 -0
  25. package/dist/core/advisory-judge-types.d.ts +53 -0
  26. package/dist/core/advisory-judge-types.d.ts.map +1 -0
  27. package/dist/core/advisory-judge-types.js +2 -0
  28. package/dist/core/advisory-judge-types.js.map +1 -0
  29. package/dist/core/advisory-judge.d.ts +8 -0
  30. package/dist/core/advisory-judge.d.ts.map +1 -0
  31. package/dist/core/advisory-judge.js +178 -0
  32. package/dist/core/advisory-judge.js.map +1 -0
  33. package/dist/core/agent-session-services.d.ts +1 -0
  34. package/dist/core/agent-session-services.d.ts.map +1 -1
  35. package/dist/core/agent-session-services.js +1 -0
  36. package/dist/core/agent-session-services.js.map +1 -1
  37. package/dist/core/agent-session.d.ts +41 -2
  38. package/dist/core/agent-session.d.ts.map +1 -1
  39. package/dist/core/agent-session.js +396 -179
  40. package/dist/core/agent-session.js.map +1 -1
  41. package/dist/core/auth-storage.d.ts.map +1 -1
  42. package/dist/core/auth-storage.js +28 -4
  43. package/dist/core/auth-storage.js.map +1 -1
  44. package/dist/core/compaction-gate.d.ts +28 -0
  45. package/dist/core/compaction-gate.d.ts.map +1 -0
  46. package/dist/core/compaction-gate.js +34 -0
  47. package/dist/core/compaction-gate.js.map +1 -0
  48. package/dist/core/completion-sound.d.ts +109 -0
  49. package/dist/core/completion-sound.d.ts.map +1 -0
  50. package/dist/core/completion-sound.js +165 -0
  51. package/dist/core/completion-sound.js.map +1 -0
  52. package/dist/core/context-budget-system-prompt-items.d.ts.map +1 -1
  53. package/dist/core/context-budget-system-prompt-items.js +5 -3
  54. package/dist/core/context-budget-system-prompt-items.js.map +1 -1
  55. package/dist/core/domain-loadouts.d.ts.map +1 -1
  56. package/dist/core/domain-loadouts.js +2 -2
  57. package/dist/core/domain-loadouts.js.map +1 -1
  58. package/dist/core/durable-goal-checkpoint.d.ts +6 -0
  59. package/dist/core/durable-goal-checkpoint.d.ts.map +1 -0
  60. package/dist/core/durable-goal-checkpoint.js +195 -0
  61. package/dist/core/durable-goal-checkpoint.js.map +1 -0
  62. package/dist/core/durable-goal-parse.d.ts +3 -0
  63. package/dist/core/durable-goal-parse.d.ts.map +1 -0
  64. package/dist/core/durable-goal-parse.js +86 -0
  65. package/dist/core/durable-goal-parse.js.map +1 -0
  66. package/dist/core/durable-goal-reducer.d.ts +3 -0
  67. package/dist/core/durable-goal-reducer.d.ts.map +1 -0
  68. package/dist/core/durable-goal-reducer.js +175 -0
  69. package/dist/core/durable-goal-reducer.js.map +1 -0
  70. package/dist/core/durable-goal-store.d.ts +19 -0
  71. package/dist/core/durable-goal-store.d.ts.map +1 -0
  72. package/dist/core/durable-goal-store.js +126 -0
  73. package/dist/core/durable-goal-store.js.map +1 -0
  74. package/dist/core/durable-goal.d.ts +92 -0
  75. package/dist/core/durable-goal.d.ts.map +1 -0
  76. package/dist/core/durable-goal.js +62 -0
  77. package/dist/core/durable-goal.js.map +1 -0
  78. package/dist/core/extensions/builtin/goal-controller.d.ts +3 -0
  79. package/dist/core/extensions/builtin/goal-controller.d.ts.map +1 -0
  80. package/dist/core/extensions/builtin/goal-controller.js +125 -0
  81. package/dist/core/extensions/builtin/goal-controller.js.map +1 -0
  82. package/dist/core/extensions/builtin/identical-loop.d.ts +3 -0
  83. package/dist/core/extensions/builtin/identical-loop.d.ts.map +1 -0
  84. package/dist/core/extensions/builtin/identical-loop.js +31 -0
  85. package/dist/core/extensions/builtin/identical-loop.js.map +1 -0
  86. package/dist/core/extensions/builtin/prompt-preset.d.ts +3 -0
  87. package/dist/core/extensions/builtin/prompt-preset.d.ts.map +1 -0
  88. package/dist/core/extensions/builtin/prompt-preset.js +13 -0
  89. package/dist/core/extensions/builtin/prompt-preset.js.map +1 -0
  90. package/dist/core/extensions/builtin/tool-pair-repair.d.ts +3 -0
  91. package/dist/core/extensions/builtin/tool-pair-repair.d.ts.map +1 -0
  92. package/dist/core/extensions/builtin/tool-pair-repair.js +12 -0
  93. package/dist/core/extensions/builtin/tool-pair-repair.js.map +1 -0
  94. package/dist/core/extensions/types.d.ts +6 -2
  95. package/dist/core/extensions/types.d.ts.map +1 -1
  96. package/dist/core/extensions/types.js.map +1 -1
  97. package/dist/core/goal-continuation.d.ts +13 -0
  98. package/dist/core/goal-continuation.d.ts.map +1 -0
  99. package/dist/core/goal-continuation.js +10 -0
  100. package/dist/core/goal-continuation.js.map +1 -0
  101. package/dist/core/grok-harness.d.ts +1 -1
  102. package/dist/core/grok-harness.d.ts.map +1 -1
  103. package/dist/core/grok-harness.js +1 -1
  104. package/dist/core/grok-harness.js.map +1 -1
  105. package/dist/core/grok-playbook.d.ts +1 -1
  106. package/dist/core/grok-playbook.d.ts.map +1 -1
  107. package/dist/core/grok-playbook.js +1 -1
  108. package/dist/core/grok-playbook.js.map +1 -1
  109. package/dist/core/host-resource-probe.d.ts +63 -0
  110. package/dist/core/host-resource-probe.d.ts.map +1 -0
  111. package/dist/core/host-resource-probe.js +126 -0
  112. package/dist/core/host-resource-probe.js.map +1 -0
  113. package/dist/core/host-resource-snapshot.d.ts +54 -0
  114. package/dist/core/host-resource-snapshot.d.ts.map +1 -0
  115. package/dist/core/host-resource-snapshot.js +139 -0
  116. package/dist/core/host-resource-snapshot.js.map +1 -0
  117. package/dist/core/identical-loop.d.ts +15 -0
  118. package/dist/core/identical-loop.d.ts.map +1 -0
  119. package/dist/core/identical-loop.js +34 -0
  120. package/dist/core/identical-loop.js.map +1 -0
  121. package/dist/core/index.d.ts +1 -1
  122. package/dist/core/index.d.ts.map +1 -1
  123. package/dist/core/index.js +1 -1
  124. package/dist/core/index.js.map +1 -1
  125. package/dist/core/model-prompt-policy.d.ts +11 -0
  126. package/dist/core/model-prompt-policy.d.ts.map +1 -0
  127. package/dist/core/model-prompt-policy.js +17 -0
  128. package/dist/core/model-prompt-policy.js.map +1 -0
  129. package/dist/core/model-registry.d.ts.map +1 -1
  130. package/dist/core/model-registry.js +16 -4
  131. package/dist/core/model-registry.js.map +1 -1
  132. package/dist/core/model-resolver.d.ts.map +1 -1
  133. package/dist/core/model-resolver.js +2 -2
  134. package/dist/core/model-resolver.js.map +1 -1
  135. package/dist/core/prompt-budget.d.ts +21 -0
  136. package/dist/core/prompt-budget.d.ts.map +1 -0
  137. package/dist/core/prompt-budget.js +51 -0
  138. package/dist/core/prompt-budget.js.map +1 -0
  139. package/dist/core/prompt-cache.d.ts +14 -0
  140. package/dist/core/prompt-cache.d.ts.map +1 -0
  141. package/dist/core/prompt-cache.js +16 -0
  142. package/dist/core/prompt-cache.js.map +1 -0
  143. package/dist/core/prompt-preset.d.ts +8 -0
  144. package/dist/core/prompt-preset.d.ts.map +1 -0
  145. package/dist/core/prompt-preset.js +56 -0
  146. package/dist/core/prompt-preset.js.map +1 -0
  147. package/dist/core/prompt-settlement.d.ts +81 -0
  148. package/dist/core/prompt-settlement.d.ts.map +1 -0
  149. package/dist/core/prompt-settlement.js +84 -0
  150. package/dist/core/prompt-settlement.js.map +1 -0
  151. package/dist/core/provider-resilience.d.ts +16 -0
  152. package/dist/core/provider-resilience.d.ts.map +1 -1
  153. package/dist/core/provider-resilience.js +24 -2
  154. package/dist/core/provider-resilience.js.map +1 -1
  155. package/dist/core/provider-retry.d.ts +36 -0
  156. package/dist/core/provider-retry.d.ts.map +1 -0
  157. package/dist/core/provider-retry.js +73 -0
  158. package/dist/core/provider-retry.js.map +1 -0
  159. package/dist/core/provider-usage.d.ts +16 -2
  160. package/dist/core/provider-usage.d.ts.map +1 -1
  161. package/dist/core/provider-usage.js +150 -8
  162. package/dist/core/provider-usage.js.map +1 -1
  163. package/dist/core/reasoning-router-resolver.d.ts.map +1 -1
  164. package/dist/core/reasoning-router-resolver.js +1 -1
  165. package/dist/core/reasoning-router-resolver.js.map +1 -1
  166. package/dist/core/resource-admission-config.d.ts +55 -0
  167. package/dist/core/resource-admission-config.d.ts.map +1 -0
  168. package/dist/core/resource-admission-config.js +97 -0
  169. package/dist/core/resource-admission-config.js.map +1 -0
  170. package/dist/core/resource-admission.d.ts +81 -0
  171. package/dist/core/resource-admission.d.ts.map +1 -0
  172. package/dist/core/resource-admission.js +128 -0
  173. package/dist/core/resource-admission.js.map +1 -0
  174. package/dist/core/resource-governor-format.d.ts +50 -0
  175. package/dist/core/resource-governor-format.d.ts.map +1 -0
  176. package/dist/core/resource-governor-format.js +113 -0
  177. package/dist/core/resource-governor-format.js.map +1 -0
  178. package/dist/core/resource-governor-settings.d.ts +56 -0
  179. package/dist/core/resource-governor-settings.d.ts.map +1 -0
  180. package/dist/core/resource-governor-settings.js +89 -0
  181. package/dist/core/resource-governor-settings.js.map +1 -0
  182. package/dist/core/resource-loader.d.ts.map +1 -1
  183. package/dist/core/resource-loader.js +32 -0
  184. package/dist/core/resource-loader.js.map +1 -1
  185. package/dist/core/resource-observation-journal.d.ts +74 -0
  186. package/dist/core/resource-observation-journal.d.ts.map +1 -0
  187. package/dist/core/resource-observation-journal.js +198 -0
  188. package/dist/core/resource-observation-journal.js.map +1 -0
  189. package/dist/core/resource-safety-gate.d.ts +54 -0
  190. package/dist/core/resource-safety-gate.d.ts.map +1 -0
  191. package/dist/core/resource-safety-gate.js +51 -0
  192. package/dist/core/resource-safety-gate.js.map +1 -0
  193. package/dist/core/run-journal.d.ts.map +1 -1
  194. package/dist/core/run-journal.js +13 -1
  195. package/dist/core/run-journal.js.map +1 -1
  196. package/dist/core/run-resource-lease.d.ts +73 -0
  197. package/dist/core/run-resource-lease.d.ts.map +1 -0
  198. package/dist/core/run-resource-lease.js +78 -0
  199. package/dist/core/run-resource-lease.js.map +1 -0
  200. package/dist/core/sandbox/backend.d.ts +12 -0
  201. package/dist/core/sandbox/backend.d.ts.map +1 -1
  202. package/dist/core/sandbox/backend.js +52 -42
  203. package/dist/core/sandbox/backend.js.map +1 -1
  204. package/dist/core/sandbox/policy.d.ts +2 -0
  205. package/dist/core/sandbox/policy.d.ts.map +1 -1
  206. package/dist/core/sandbox/policy.js +2 -1
  207. package/dist/core/sandbox/policy.js.map +1 -1
  208. package/dist/core/sdk.d.ts +2 -0
  209. package/dist/core/sdk.d.ts.map +1 -1
  210. package/dist/core/sdk.js +1 -0
  211. package/dist/core/sdk.js.map +1 -1
  212. package/dist/core/session-bash-service.d.ts +12 -0
  213. package/dist/core/session-bash-service.d.ts.map +1 -1
  214. package/dist/core/session-bash-service.js +14 -0
  215. package/dist/core/session-bash-service.js.map +1 -1
  216. package/dist/core/session-system-prompt.d.ts +30 -0
  217. package/dist/core/session-system-prompt.d.ts.map +1 -0
  218. package/dist/core/session-system-prompt.js +39 -0
  219. package/dist/core/session-system-prompt.js.map +1 -0
  220. package/dist/core/session-termination.d.ts +7 -2
  221. package/dist/core/session-termination.d.ts.map +1 -1
  222. package/dist/core/session-termination.js +29 -1
  223. package/dist/core/session-termination.js.map +1 -1
  224. package/dist/core/settings-manager.d.ts +12 -0
  225. package/dist/core/settings-manager.d.ts.map +1 -1
  226. package/dist/core/settings-manager.js +36 -0
  227. package/dist/core/settings-manager.js.map +1 -1
  228. package/dist/core/slash-commands.d.ts.map +1 -1
  229. package/dist/core/slash-commands.js +1 -0
  230. package/dist/core/slash-commands.js.map +1 -1
  231. package/dist/core/strict-record.d.ts +2 -0
  232. package/dist/core/strict-record.d.ts.map +1 -0
  233. package/dist/core/strict-record.js +9 -0
  234. package/dist/core/strict-record.js.map +1 -0
  235. package/dist/core/subagent-lane-launcher.d.ts +64 -0
  236. package/dist/core/subagent-lane-launcher.d.ts.map +1 -0
  237. package/dist/core/subagent-lane-launcher.js +107 -0
  238. package/dist/core/subagent-lane-launcher.js.map +1 -0
  239. package/dist/core/system-cpu-sampler.d.ts +45 -0
  240. package/dist/core/system-cpu-sampler.d.ts.map +1 -0
  241. package/dist/core/system-cpu-sampler.js +95 -0
  242. package/dist/core/system-cpu-sampler.js.map +1 -0
  243. package/dist/core/system-prompt.d.ts.map +1 -1
  244. package/dist/core/system-prompt.js +5 -8
  245. package/dist/core/system-prompt.js.map +1 -1
  246. package/dist/core/tool-pair-repair.d.ts +11 -0
  247. package/dist/core/tool-pair-repair.d.ts.map +1 -0
  248. package/dist/core/tool-pair-repair.js +38 -0
  249. package/dist/core/tool-pair-repair.js.map +1 -0
  250. package/dist/core/tools/artifact-spill.d.ts +13 -0
  251. package/dist/core/tools/artifact-spill.d.ts.map +1 -0
  252. package/dist/core/tools/artifact-spill.js +26 -0
  253. package/dist/core/tools/artifact-spill.js.map +1 -0
  254. package/dist/core/tools/bash.d.ts +7 -4
  255. package/dist/core/tools/bash.d.ts.map +1 -1
  256. package/dist/core/tools/bash.js +8 -7
  257. package/dist/core/tools/bash.js.map +1 -1
  258. package/dist/core/tools/index.d.ts +1 -1
  259. package/dist/core/tools/index.d.ts.map +1 -1
  260. package/dist/core/tools/index.js.map +1 -1
  261. package/dist/core/tools/read.d.ts +1 -0
  262. package/dist/core/tools/read.d.ts.map +1 -1
  263. package/dist/core/tools/read.js +10 -1
  264. package/dist/core/tools/read.js.map +1 -1
  265. package/dist/core/tools/tool-definition-wrapper.d.ts.map +1 -1
  266. package/dist/core/tools/tool-definition-wrapper.js +2 -0
  267. package/dist/core/tools/tool-definition-wrapper.js.map +1 -1
  268. package/dist/core/workload-classifier.d.ts +32 -0
  269. package/dist/core/workload-classifier.d.ts.map +1 -0
  270. package/dist/core/workload-classifier.js +66 -0
  271. package/dist/core/workload-classifier.js.map +1 -0
  272. package/dist/core/workload-family-matcher.d.ts +3 -0
  273. package/dist/core/workload-family-matcher.d.ts.map +1 -0
  274. package/dist/core/workload-family-matcher.js +126 -0
  275. package/dist/core/workload-family-matcher.js.map +1 -0
  276. package/dist/core/workload-permit-pool.d.ts +78 -0
  277. package/dist/core/workload-permit-pool.d.ts.map +1 -0
  278. package/dist/core/workload-permit-pool.js +138 -0
  279. package/dist/core/workload-permit-pool.js.map +1 -0
  280. package/dist/core/workload-shard-executor.d.ts +68 -0
  281. package/dist/core/workload-shard-executor.d.ts.map +1 -0
  282. package/dist/core/workload-shard-executor.js +156 -0
  283. package/dist/core/workload-shard-executor.js.map +1 -0
  284. package/dist/core/workload-shard-plan.d.ts +81 -0
  285. package/dist/core/workload-shard-plan.d.ts.map +1 -0
  286. package/dist/core/workload-shard-plan.js +183 -0
  287. package/dist/core/workload-shard-plan.js.map +1 -0
  288. package/dist/core/workload-shard-runner.d.ts +11 -0
  289. package/dist/core/workload-shard-runner.d.ts.map +1 -0
  290. package/dist/core/workload-shard-runner.js +98 -0
  291. package/dist/core/workload-shard-runner.js.map +1 -0
  292. package/dist/core/workload-shard-store.d.ts +44 -0
  293. package/dist/core/workload-shard-store.d.ts.map +1 -0
  294. package/dist/core/workload-shard-store.js +189 -0
  295. package/dist/core/workload-shard-store.js.map +1 -0
  296. package/dist/core/workload-sharders.d.ts +47 -0
  297. package/dist/core/workload-sharders.d.ts.map +1 -0
  298. package/dist/core/workload-sharders.js +162 -0
  299. package/dist/core/workload-sharders.js.map +1 -0
  300. package/dist/core/workload-shell-scan.d.ts +12 -0
  301. package/dist/core/workload-shell-scan.d.ts.map +1 -0
  302. package/dist/core/workload-shell-scan.js +79 -0
  303. package/dist/core/workload-shell-scan.js.map +1 -0
  304. package/dist/index.d.ts +18 -1
  305. package/dist/index.d.ts.map +1 -1
  306. package/dist/index.js +18 -0
  307. package/dist/index.js.map +1 -1
  308. package/dist/main.d.ts.map +1 -1
  309. package/dist/main.js +14 -0
  310. package/dist/main.js.map +1 -1
  311. package/dist/modes/interactive/interactive-mode.d.ts +4 -0
  312. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  313. package/dist/modes/interactive/interactive-mode.js +81 -0
  314. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  315. package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
  316. package/dist/modes/interactive/theme/theme.js +14 -3
  317. package/dist/modes/interactive/theme/theme.js.map +1 -1
  318. package/docs/adaptorch-preview-spec.md +1 -1
  319. package/docs/adaptorch-preview.md +2 -2
  320. package/docs/book-to-skill.md +1 -1
  321. package/docs/containerization.md +9 -0
  322. package/docs/docs.json +11 -3
  323. package/docs/environment-variables.md +11 -4
  324. package/docs/extensions.md +14 -1
  325. package/docs/grok-harness.md +35 -12
  326. package/docs/index.md +7 -6
  327. package/docs/loadout-domains/grok-harness.md +2 -2
  328. package/docs/models.md +10 -9
  329. package/docs/provider-resilience.md +9 -3
  330. package/docs/providers.md +9 -4
  331. package/docs/run-protocol.md +32 -0
  332. package/docs/sdk.md +100 -3
  333. package/docs/security.md +22 -32
  334. package/docs/sessions.md +14 -0
  335. package/docs/settings.md +64 -2
  336. package/docs/skills.md +16 -0
  337. package/docs/usage.md +37 -15
  338. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  339. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  340. package/examples/extensions/custom-provider-gitlab-duo/index.ts +6 -1
  341. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  342. package/examples/extensions/gondolin/package-lock.json +2 -2
  343. package/examples/extensions/gondolin/package.json +1 -1
  344. package/examples/extensions/sandbox/package-lock.json +2 -2
  345. package/examples/extensions/sandbox/package.json +1 -1
  346. package/examples/extensions/subagent/agent-capability-router.test.ts +7 -0
  347. package/examples/extensions/subagent/domain-profiles.ts +0 -1
  348. package/examples/extensions/with-deps/package-lock.json +2 -2
  349. package/examples/extensions/with-deps/package.json +1 -1
  350. package/npm-shrinkwrap.json +18 -18
  351. package/package.json +7 -9
@@ -16,7 +16,7 @@ import { createHash, randomUUID } from "node:crypto";
16
16
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
17
17
  import { basename, dirname, join as joinPath } from "node:path";
18
18
  import { isDeepStrictEqual } from "node:util";
19
- import { getVisionRouteModel, isVisionRouteModel } from "omk-agent-core";
19
+ import { getVisionRouteModel } from "omk-agent-core";
20
20
  import { clampThinkingLevel, cleanupSessionResources, deriveContextPromptCacheKey, getSupportedThinkingLevels, isContextOverflow, modelsAreEqual, resetApiProviders, streamSimple, } from "omk-ai";
21
21
  import { APP_NAME, VERSION } from "../config.js";
22
22
  import { theme } from "../modes/interactive/theme/theme.js";
@@ -32,6 +32,7 @@ import { getCompactionHeadroomThreshold } from "./compaction/compaction.js";
32
32
  import { createCompactionHysteresisConfig, createCompactionHysteresisState, stepCompactionHysteresis, } from "./compaction/hysteresis.js";
33
33
  import { calculateContextTokens, collectEntriesForBranchSummary, compact, estimateContextTokens, estimateProjectedContextTokens, generateBranchSummary, prepareCompaction, resolveCompactionModel, } from "./compaction/index.js";
34
34
  import { compactionEmitWillRetry } from "./compaction/resume-policy.js";
35
+ import { isSessionModelOverflow, shouldSkipCompactionCheck } from "./compaction-gate.js";
35
36
  import { estimateToolResultReserve, } from "./context-budget-reserved-tokens.js";
36
37
  import { createDiskContextBudgetCacheProviderV2, DiskContextBudgetCacheProviderV2, } from "./context-budget-v2-cache-disk.js";
37
38
  import { applyContextCacheInvalidation, createContextCacheInvalidationSnapshot, } from "./context-budget-v2-cache-invalidation.js";
@@ -43,6 +44,7 @@ import { ExtensionRunner, wrapRegisteredTools, } from "./extensions/index.js";
43
44
  import { emitSessionShutdownEvent } from "./extensions/runner.js";
44
45
  import { assertTextChatModelForCompletion } from "./grok-harness.js";
45
46
  import { grokPlaybookAppendForProvider } from "./grok-playbook.js";
47
+ import { captureHostResourceSnapshot } from "./host-resource-snapshot.js";
46
48
  import { decideLoadoutAccess } from "./loadout-access-policy.js";
47
49
  import { loadMcpServerConfigs } from "./mcp/config.js";
48
50
  import { McpManager } from "./mcp/manager.js";
@@ -52,6 +54,7 @@ import { TurnMetricsSink } from "./turn-metrics.js";
52
54
  const DISABLED_ENV_VALUES = new Set(["0", "false", "off", "disable", "disabled"]);
53
55
  const MAX_OVERFLOW_RECOVERY_ATTEMPTS = 2;
54
56
  const OVERFLOW_RECOVERY_EMERGENCY_TOKENS = 4_096;
57
+ const MAX_RESOURCE_OBSERVATION_JOURNALS = 32;
55
58
  function isDisabledEnvValue(value) {
56
59
  return value !== undefined && DISABLED_ENV_VALUES.has(value.trim().toLowerCase());
57
60
  }
@@ -70,20 +73,31 @@ function firstTextContent(result) {
70
73
  return undefined;
71
74
  }
72
75
  import { redactCredentialShapedContent } from "./compaction/transaction.js";
76
+ import { selectContextFilesForModel } from "./model-prompt-policy.js";
73
77
  import { findExactModelReferenceMatch } from "./model-resolver.js";
78
+ import { computePromptTokenBudget } from "./prompt-budget.js";
79
+ import { classifyPromptCacheTransition } from "./prompt-cache.js";
80
+ import { createPromptSettlementState, reducePromptSettlement, settlePromptIfReady, } from "./prompt-settlement.js";
74
81
  import { expandPromptTemplate } from "./prompt-templates.js";
75
- import { isContentSafetyStopMessage, isQuotaExhaustionMessage, isStickySafetyModel, isTransientProviderErrorMessage, pickFailoverCandidate, resolveProviderResilience, stickySafetyBlockMessage, } from "./provider-resilience.js";
82
+ import { isQuotaExhaustionMessage, isStickySafetyModel, pickFailoverCandidate, resolveProviderResilience, shouldEjectStickySafetyModel, shouldHonorSafetyFailover, stickySafetyBlockMessage, } from "./provider-resilience.js";
83
+ import { computeRetryDelayMs, failoverModelKey, isFailoverTriggerError, isRetryableAssistantError, nextRetryAttempt, retryBudgetForAssistantError, } from "./provider-retry.js";
76
84
  import { getBiasStepsForCell, parseRouterBiasSnapshot } from "./reasoning-router-bias.js";
77
85
  import { classifyTaskV4, deriveRouterFeedbackFeaturesV4, resolveThinkingLevelV4WithUncertainty, TASK_CLASS_THINKING_LEVELS_V4, } from "./reasoning-router-v4.js";
78
86
  import { redactSensitiveText, redactSensitiveTextForced } from "./redaction.js";
79
87
  import { getRepositoryRouterLearningPaths } from "./repository-learning-scope.js";
88
+ import { decideResourceAdmission } from "./resource-admission.js";
89
+ import { resolveResourceGovernorSettings } from "./resource-governor-settings.js";
90
+ import { admissionObservationFacts, classificationObservationFacts, ResourceObservationJournal, settledObservationFacts, snapshotObservationFacts, soundObservationFacts, } from "./resource-observation-journal.js";
91
+ import { decideResourceSafetyGate, RESOURCE_PRESSURE_REQUIRED_ACTION } from "./resource-safety-gate.js";
80
92
  import { appendRouterFeedbackRecord, ROUTER_FEEDBACK_LEVELS, } from "./router-feedback-collector.js";
81
93
  import { RunJournalStore } from "./run-journal-store.js";
94
+ import { RunResourceLeaseController } from "./run-resource-lease.js";
82
95
  import { SessionBashRuntime } from "./session-bash-runtime.js";
83
96
  import { SessionBashService } from "./session-bash-service.js";
84
97
  import { SessionCompactionService } from "./session-compaction-service.js";
85
98
  import { CURRENT_SESSION_VERSION, getLatestCompactionEntry } from "./session-manager.js";
86
99
  import { acquireSessionOwnerLeaseSync } from "./session-owner-lease.js";
100
+ import { assembleSessionSystemPrompt } from "./session-system-prompt.js";
87
101
  import { classifySessionTermination, } from "./session-termination.js";
88
102
  import { createSyntheticSourceInfo } from "./source-info.js";
89
103
  import { buildSystemPromptPlan } from "./system-prompt.js";
@@ -92,6 +106,8 @@ import { createAllToolDefinitions } from "./tools/index.js";
92
106
  import { createToolDefinitionFromAgentTool } from "./tools/tool-definition-wrapper.js";
93
107
  import { createVerifiedBashOperations } from "./verified-bash-adapter.js";
94
108
  import { resolveSessionWorkspaceScope } from "./verified-bash-runtime.js";
109
+ import { classifyWorkloadCommand } from "./workload-classifier.js";
110
+ import { WorkloadPermitError, WorkloadPermitPool } from "./workload-permit-pool.js";
95
111
  /**
96
112
  * Parse a skill block from message text.
97
113
  * Returns null if the text doesn't contain a skill block.
@@ -315,6 +331,8 @@ export class AgentSession {
315
331
  // v10.3-Ω: models that already refused/failed this turn — failover must advance,
316
332
  // not re-pick the same candidate (claude-opus-5 → grok → grok → grok loop fix).
317
333
  _refusedModels = new Set();
334
+ /** True when `--model` / `--provider` pinned this process to one model. */
335
+ _modelPinned;
318
336
  // Bash execution state
319
337
  _bashService;
320
338
  _compactionService;
@@ -348,6 +366,14 @@ export class AgentSession {
348
366
  _runJournalStore;
349
367
  _ownedSessionOwnerLease;
350
368
  _activeRunId = null;
369
+ // Resource governor (roadmap M2): lease controller owns the temporary tool
370
+ // cap for governed runs; the last decision backs the §19.4 read-only API.
371
+ _resourceLeaseController;
372
+ _resourceObservations = null;
373
+ _resourceObservationJournals = new Map();
374
+ _latestResourcePromptRunId = null;
375
+ _lastResourceAdmission = null;
376
+ _workloadPermitPool;
351
377
  _pendingRuntimeTerminationCause;
352
378
  _activeRunToolTermination;
353
379
  _lastTermination;
@@ -383,6 +409,7 @@ export class AgentSession {
383
409
  this.sessionManager = config.sessionManager;
384
410
  this.settingsManager = config.settingsManager;
385
411
  this._scopedModels = config.scopedModels ?? [];
412
+ this._modelPinned = config.modelPinned === true;
386
413
  this._resourceLoader = config.resourceLoader;
387
414
  this._customTools = config.customTools ?? [];
388
415
  this._cwd = config.cwd;
@@ -427,6 +454,7 @@ export class AgentSession {
427
454
  isStreaming: () => this.isStreaming,
428
455
  pushMessage: (message) => this.agent.state.messages.push(message),
429
456
  appendMessage: (message) => this.sessionManager.appendMessage(message),
457
+ acquireResourcePermit: (command, signal) => this._acquireBashResourcePermit(command, signal),
430
458
  });
431
459
  this._compactionService = new SessionCompactionService({
432
460
  sessionManager: this.sessionManager,
@@ -1043,13 +1071,14 @@ export class AgentSession {
1043
1071
  };
1044
1072
  _willRetryAfterAgentEnd(event) {
1045
1073
  const settings = this.settingsManager.getRetrySettings();
1046
- if (!settings.enabled || this._retryAttempt >= settings.maxRetries) {
1074
+ if (!settings.enabled)
1047
1075
  return false;
1048
- }
1049
1076
  for (let i = event.messages.length - 1; i >= 0; i--) {
1050
1077
  const message = event.messages[i];
1051
1078
  if (message.role === "assistant") {
1052
- return this._isRetryableError(message);
1079
+ const assistant = message;
1080
+ const maxRetries = retryBudgetForAssistantError(assistant, settings.maxRetries);
1081
+ return this._retryAttempt < maxRetries && this._isRetryableError(assistant);
1053
1082
  }
1054
1083
  }
1055
1084
  return false;
@@ -1554,7 +1583,7 @@ export class AgentSession {
1554
1583
  }
1555
1584
  return undefined;
1556
1585
  }
1557
- _getContextBudgetOptions(queryContext = this._extractCurrentQuery()) {
1586
+ _getContextBudgetOptions(queryContext = this._extractCurrentQuery(), model = this.model) {
1558
1587
  const contextGovernorOverride = process.env.OMK_CONTEXT_GOVERNOR;
1559
1588
  if (contextGovernorOverride === "0") {
1560
1589
  return undefined;
@@ -1562,56 +1591,24 @@ export class AgentSession {
1562
1591
  if (contextGovernorOverride !== "1" && !this.settingsManager.getContextBudgetEnabled()) {
1563
1592
  return undefined;
1564
1593
  }
1565
- const contextWindow = this.model?.contextWindow ?? 0;
1566
- // --- maxPromptTokens ---
1567
- // Priority 1: explicit env override (user knows best)
1568
- const envMaxPrompt = parsePositiveIntegerEnv("OMK_CONTEXT_GOVERNOR_MAX_PROMPT_TOKENS");
1569
- let maxPromptTokens;
1570
- let responseReserveTokens;
1571
- if (envMaxPrompt !== undefined) {
1572
- // User override — use as-is for prompt, derive response reserve normally
1573
- maxPromptTokens = envMaxPrompt;
1574
- responseReserveTokens =
1575
- parsePositiveIntegerEnv("OMK_CONTEXT_GOVERNOR_RESPONSE_RESERVE_TOKENS") ??
1576
- this._computeResponseReserve(contextWindow);
1577
- }
1578
- else if (contextWindow > 0) {
1579
- // Dynamic: derive from model contextWindow
1580
- const envPromptRatio = parsePositiveFloatEnv("OMK_CONTEXT_GOVERNOR_PROMPT_RATIO");
1581
- const envResponseRatio = parsePositiveFloatEnv("OMK_CONTEXT_GOVERNOR_RESPONSE_RATIO");
1582
- responseReserveTokens =
1583
- parsePositiveIntegerEnv("OMK_CONTEXT_GOVERNOR_RESPONSE_RESERVE_TOKENS") ??
1584
- this._computeResponseReserve(contextWindow, envResponseRatio);
1585
- const safetyMargin = Math.floor(contextWindow * SAFETY_MARGIN_RATIO);
1586
- if (envPromptRatio !== undefined && envPromptRatio > 0 && envPromptRatio < 1) {
1587
- maxPromptTokens = Math.floor(contextWindow * envPromptRatio);
1588
- }
1589
- else {
1590
- // Default: contextWindow minus reserves and safety margin
1591
- maxPromptTokens = contextWindow - responseReserveTokens - safetyMargin;
1592
- }
1593
- }
1594
- else {
1595
- // No model info — legacy fallback
1596
- maxPromptTokens = LEGACY_MAX_PROMPT_TOKENS;
1597
- responseReserveTokens =
1598
- parsePositiveIntegerEnv("OMK_CONTEXT_GOVERNOR_RESPONSE_RESERVE_TOKENS") ?? LEGACY_RESPONSE_RESERVE_TOKENS;
1599
- }
1594
+ const contextWindow = model?.contextWindow ?? 0;
1595
+ const budget = computePromptTokenBudget({
1596
+ contextWindow,
1597
+ modelMaxTokens: model?.maxTokens,
1598
+ envMaxPromptTokens: parsePositiveIntegerEnv("OMK_CONTEXT_GOVERNOR_MAX_PROMPT_TOKENS"),
1599
+ envResponseReserveTokens: parsePositiveIntegerEnv("OMK_CONTEXT_GOVERNOR_RESPONSE_RESERVE_TOKENS"),
1600
+ envPromptRatio: parsePositiveFloatEnv("OMK_CONTEXT_GOVERNOR_PROMPT_RATIO"),
1601
+ envResponseRatio: parsePositiveFloatEnv("OMK_CONTEXT_GOVERNOR_RESPONSE_RATIO"),
1602
+ });
1603
+ const maxPromptTokens = budget.maxPromptTokens;
1604
+ const responseReserveTokens = budget.responseReserveTokens;
1600
1605
  const cacheProvider = this._contextBudgetCacheProvider ?? this._createContextBudgetCacheProvider();
1601
1606
  cacheProvider.setInvalidationSnapshot?.(this._contextCacheInvalidationSnapshot);
1602
1607
  this._contextBudgetCacheProvider = cacheProvider;
1603
- // Enforce floor
1604
- if (maxPromptTokens < MIN_PROMPT_TOKENS) {
1605
- maxPromptTokens = MIN_PROMPT_TOKENS;
1606
- }
1607
- // Ensure responseReserve does not exceed maxPromptTokens
1608
- if (responseReserveTokens >= maxPromptTokens) {
1609
- responseReserveTokens = Math.max(Math.floor(maxPromptTokens / 4), LEGACY_RESPONSE_RESERVE_TOKENS);
1610
- }
1611
1608
  return {
1612
1609
  maxPromptTokens,
1613
1610
  responseReserveTokens,
1614
- modelId: this.model?.id ?? "unknown",
1611
+ modelId: model?.id ?? "unknown",
1615
1612
  tokenizerMode: parseTokenizerModeEnv(process.env.OMK_CONTEXT_GOVERNOR_TOKENIZER),
1616
1613
  activeSkillNames: parseCommaSeparatedEnv(process.env.OMK_CONTEXT_GOVERNOR_ACTIVE_SKILLS),
1617
1614
  queryContext,
@@ -1622,15 +1619,6 @@ export class AgentSession {
1622
1619
  * Compute responseReserveTokens from contextWindow.
1623
1620
  * Prefers the model's own maxTokens when available, otherwise uses a ratio.
1624
1621
  */
1625
- _computeResponseReserve(contextWindow, overrideRatio) {
1626
- // Prefer model's maxTokens (actual output limit) if available and reasonable
1627
- const modelMaxTokens = this.model?.maxTokens;
1628
- if (modelMaxTokens !== undefined && modelMaxTokens > 0 && modelMaxTokens < contextWindow) {
1629
- return modelMaxTokens;
1630
- }
1631
- const ratio = overrideRatio ?? RESPONSE_RESERVE_RATIO;
1632
- return Math.max(Math.floor(contextWindow * ratio), LEGACY_RESPONSE_RESERVE_TOKENS);
1633
- }
1634
1622
  _recordPromptCachePlan(reason) {
1635
1623
  const boundary = this.agent.state.systemPromptCacheBoundary;
1636
1624
  const scope = this.model ? `${this.model.provider}/${this.model.id}` : "unknown";
@@ -1641,65 +1629,64 @@ export class AgentSession {
1641
1629
  messages: [],
1642
1630
  tools: this.agent.state.tools,
1643
1631
  }, scope);
1644
- if (!nextKey) {
1632
+ const transition = classifyPromptCacheTransition(this._promptCacheKey, nextKey);
1633
+ if (transition.kind === "bypass") {
1645
1634
  this._promptCacheBoundaryBypasses += 1;
1646
1635
  this._promptCacheStablePrefixCharacters = 0;
1647
- if (this._promptCacheKey !== undefined) {
1636
+ if (transition.recordBreak) {
1648
1637
  this._promptCacheLastBreakReason = reason;
1649
1638
  }
1650
1639
  this._promptCacheKey = undefined;
1651
1640
  return;
1652
1641
  }
1653
- if (this._promptCacheKey !== undefined && this._promptCacheKey !== nextKey) {
1642
+ if (transition.kind === "changed") {
1654
1643
  this._promptCacheKeyChanges += 1;
1655
1644
  this._promptCacheLastBreakReason = reason;
1656
1645
  }
1657
1646
  this._promptCacheKey = nextKey;
1658
1647
  this._promptCacheStablePrefixCharacters = boundary ?? 0;
1659
1648
  }
1660
- _rebuildSystemPrompt(toolNames) {
1661
- const validToolNames = toolNames.filter((name) => this._toolRegistry.has(name));
1662
- const toolSnippets = {};
1663
- const promptGuidelines = [];
1664
- for (const name of validToolNames) {
1665
- const snippet = this._toolPromptSnippets.get(name);
1666
- if (snippet) {
1667
- toolSnippets[name] = snippet;
1668
- }
1669
- const toolGuidelines = this._toolPromptGuidelines.get(name);
1670
- if (toolGuidelines) {
1671
- promptGuidelines.push(...toolGuidelines);
1672
- }
1673
- }
1674
- const loaderSystemPrompt = this._resourceLoader.getSystemPrompt();
1675
- const loaderAppendSystemPrompt = this._resourceLoader.getAppendSystemPrompt();
1676
- const grokAppend = grokPlaybookAppendForProvider(this.model?.provider);
1677
- const appendParts = [...loaderAppendSystemPrompt];
1678
- if (grokAppend) {
1679
- appendParts.push(grokAppend);
1680
- }
1681
- const appendSystemPrompt = appendParts.length > 0 ? appendParts.join("\n\n") : undefined;
1682
- const loadedSkills = this._resourceLoader.getSkills().skills;
1683
- const loadedContextFiles = this._resourceLoader.getAgentsFiles().agentsFiles;
1684
- this._baseSystemPromptOptions = {
1649
+ _getDefaultActiveSkills() {
1650
+ const trustedSkillNames = new Set(this._resourceLoader
1651
+ .getSkills()
1652
+ .skills.filter((skill) => skill.sourceInfo.scope === "user")
1653
+ .map((skill) => skill.name));
1654
+ return this.settingsManager.getDefaultActiveSkills().filter((name) => trustedSkillNames.has(name));
1655
+ }
1656
+ _rebuildSystemPrompt(toolNames, model = this.model) {
1657
+ const defaultActiveSkills = this._getDefaultActiveSkills();
1658
+ const assembled = assembleSessionSystemPrompt({
1685
1659
  cwd: this._cwd,
1686
- skills: loadedSkills,
1687
- contextFiles: loadedContextFiles,
1688
- customPrompt: loaderSystemPrompt,
1689
- appendSystemPrompt,
1690
- selectedTools: validToolNames,
1691
- toolSnippets,
1692
- promptGuidelines,
1693
- contextBudget: this._getContextBudgetOptions(),
1694
- };
1695
- const built = buildSystemPromptPlan(this._baseSystemPromptOptions);
1696
- this._baseSystemPromptCacheBoundary = built.cacheBoundary;
1697
- return built.prompt;
1660
+ toolNames,
1661
+ hasTool: (name) => this._toolRegistry.has(name),
1662
+ toolPromptSnippets: this._toolPromptSnippets,
1663
+ toolPromptGuidelines: this._toolPromptGuidelines,
1664
+ customPrompt: this._resourceLoader.getSystemPrompt(),
1665
+ appendSystemPrompt: this._resourceLoader.getAppendSystemPrompt(),
1666
+ providerAppend: grokPlaybookAppendForProvider(model?.provider),
1667
+ skills: this._resourceLoader.getSkills().skills,
1668
+ activeSkillNames: defaultActiveSkills,
1669
+ activeSkillSource: defaultActiveSkills.length > 0 ? "settings" : undefined,
1670
+ contextFiles: selectContextFilesForModel(this._resourceLoader.getAgentsFiles().agentsFiles, model),
1671
+ contextBudget: this._getContextBudgetOptions(undefined, model),
1672
+ });
1673
+ this._baseSystemPromptOptions = assembled.options;
1674
+ this._baseSystemPromptCacheBoundary = assembled.cacheBoundary;
1675
+ return assembled.prompt;
1698
1676
  }
1699
1677
  // =========================================================================
1700
1678
  // Prompting
1701
1679
  // =========================================================================
1702
1680
  async _runAgentPrompt(messages) {
1681
+ // One identity per top-level run (§16.2): internal retries and
1682
+ // continuations inside this call share it, as does the resource lease.
1683
+ const promptRunId = `prompt-run-${randomUUID()}`;
1684
+ const startedAtEpochMs = Date.now();
1685
+ // Roadmap M2: the lease spans the whole run including internal retries
1686
+ // and continuations, so they share one admission decision (§8.3).
1687
+ const resourceLease = await this._beginResourceGovernedRun(promptRunId);
1688
+ const resourceObservations = this._resourceObservationJournals.get(promptRunId) ?? null;
1689
+ let outcome = "completed";
1703
1690
  try {
1704
1691
  await this.agent.prompt(messages);
1705
1692
  while (await this._handlePostAgentRun()) {
@@ -1707,11 +1694,251 @@ export class AgentSession {
1707
1694
  }
1708
1695
  }
1709
1696
  catch (error) {
1697
+ outcome =
1698
+ this._userAbortRequested || (error instanceof Error && /abort/i.test(error.message)) ? "aborted" : "failed";
1710
1699
  this._publishRuntimeFailure(error);
1711
1700
  throw error;
1712
1701
  }
1713
1702
  finally {
1703
+ if (resourceLease !== null) {
1704
+ // Generation-safe: a stale release after another run acquired the
1705
+ // lease is a no-op instead of clobbering the newer cap (§8.1).
1706
+ this._resourceLeaseController?.release(resourceLease);
1707
+ resourceObservations?.record("resource_lease_released_v1", { promptRunId });
1708
+ }
1714
1709
  this._flushPendingBashMessages();
1710
+ // §16.4 (M4): settle only after every in-run continuation drained.
1711
+ this._emitPromptSettledIfReady({ promptRunId, startedAtEpochMs, outcome, resourceObservations });
1712
+ }
1713
+ }
1714
+ /**
1715
+ * Emit `prompt_settled` when the §16.4 conditions hold. Called exactly
1716
+ * once per promptRunId (from `_runAgentPrompt`'s finally), so the same
1717
+ * run can never emit twice; late-queued messages block emission
1718
+ * conservatively — the continuation run settles under its own id.
1719
+ * Settlement is a UX signal only and must never affect the run (§16.1).
1720
+ */
1721
+ _emitPromptSettledIfReady(input) {
1722
+ try {
1723
+ let state = createPromptSettlementState(input.promptRunId, input.startedAtEpochMs);
1724
+ state = reducePromptSettlement(state, { kind: "terminal", outcome: input.outcome });
1725
+ if (this.isStreaming) {
1726
+ state = reducePromptSettlement(state, { kind: "tool", delta: 1 });
1727
+ }
1728
+ if (this.agent.hasQueuedMessages()) {
1729
+ state = reducePromptSettlement(state, { kind: "continuation", delta: 1 });
1730
+ }
1731
+ const settled = settlePromptIfReady(state, Date.now());
1732
+ if (settled.event !== null) {
1733
+ input.resourceObservations?.record("prompt_settled_v1", settledObservationFacts(settled.event));
1734
+ this._emit(settled.event);
1735
+ }
1736
+ }
1737
+ catch {
1738
+ // Never let settlement bookkeeping break a run.
1739
+ }
1740
+ }
1741
+ /**
1742
+ * Resource governor preflight for one top-level run (roadmap §8, §25.3 M2).
1743
+ *
1744
+ * - `off`: nothing; v0.96.1 observable behavior is preserved.
1745
+ * - `observe` (default): fire-and-forget probe records a decision for the
1746
+ * §19.4 read-only API; the cap never changes and the prompt is never
1747
+ * delayed. The pending probe is bounded (~cpuSampleMs) and never rejects.
1748
+ * - `adaptive`/`strict`: bounded blocking probe (default 300 ms deadline),
1749
+ * then a generation-safe lease applies the effective tool cap for this
1750
+ * run. The caller's `finally` releases it exactly once.
1751
+ *
1752
+ * Never throws: any probe or policy failure leaves this run ungoverned
1753
+ * (§2.1: probe failure must not crash or block the prompt).
1754
+ */
1755
+ async _beginResourceGovernedRun(promptRunId) {
1756
+ try {
1757
+ const resolved = resolveResourceGovernorSettings(this.settingsManager.getResourceGovernorSettings());
1758
+ if (resolved.mode === "off") {
1759
+ return null;
1760
+ }
1761
+ const resourceObservations = this._openResourceObservations(promptRunId);
1762
+ const probeOptions = {
1763
+ cwd: this._cwd,
1764
+ maxProbeMs: resolved.maxProbeMs,
1765
+ cpuSampleMs: resolved.cpuSampleMs,
1766
+ };
1767
+ // §7.3: Agent.maxToolConcurrency uses undefined for "unlimited"; the
1768
+ // admission contract uses 0 for the same meaning.
1769
+ const configuredCaps = { maxToolConcurrency: this.agent.maxToolConcurrency ?? 0 };
1770
+ if (resolved.mode === "observe") {
1771
+ void captureHostResourceSnapshot(probeOptions)
1772
+ .then((snapshot) => {
1773
+ const decision = decideResourceAdmission({
1774
+ snapshot,
1775
+ config: resolved.admission,
1776
+ configuredCaps,
1777
+ });
1778
+ if (this._latestResourcePromptRunId === promptRunId) {
1779
+ this._lastResourceAdmission = decision;
1780
+ }
1781
+ resourceObservations?.record("resource_snapshot_v1", snapshotObservationFacts(snapshot));
1782
+ resourceObservations?.record("resource_admission_v1", admissionObservationFacts(decision));
1783
+ })
1784
+ .catch(() => { });
1785
+ return null;
1786
+ }
1787
+ const snapshot = await captureHostResourceSnapshot(probeOptions);
1788
+ const decision = decideResourceAdmission({ snapshot, config: resolved.admission, configuredCaps });
1789
+ if (this._latestResourcePromptRunId === promptRunId) {
1790
+ this._lastResourceAdmission = decision;
1791
+ }
1792
+ resourceObservations?.record("resource_snapshot_v1", snapshotObservationFacts(snapshot));
1793
+ resourceObservations?.record("resource_admission_v1", admissionObservationFacts(decision));
1794
+ this._resourceLeaseController ??= new RunResourceLeaseController({
1795
+ getCap: () => this.agent.maxToolConcurrency,
1796
+ setCap: (cap) => {
1797
+ this.agent.maxToolConcurrency = cap;
1798
+ },
1799
+ });
1800
+ const lease = this._resourceLeaseController.acquire({ promptRunId, decision });
1801
+ resourceObservations?.record("resource_lease_acquired_v1", {
1802
+ decisionId: decision.decisionId,
1803
+ appliedToolCap: decision.maxToolConcurrency,
1804
+ });
1805
+ return lease;
1806
+ }
1807
+ catch {
1808
+ return null;
1809
+ }
1810
+ }
1811
+ _openResourceObservations(promptRunId) {
1812
+ this._latestResourcePromptRunId = promptRunId;
1813
+ try {
1814
+ const journal = ResourceObservationJournal.open(this._cwd, promptRunId);
1815
+ this._resourceObservations = journal;
1816
+ this._resourceObservationJournals.set(promptRunId, journal);
1817
+ while (this._resourceObservationJournals.size > MAX_RESOURCE_OBSERVATION_JOURNALS) {
1818
+ const oldestPromptRunId = this._resourceObservationJournals.keys().next().value;
1819
+ if (oldestPromptRunId === undefined)
1820
+ break;
1821
+ this._resourceObservationJournals.delete(oldestPromptRunId);
1822
+ }
1823
+ return journal;
1824
+ }
1825
+ catch {
1826
+ this._resourceObservations = null;
1827
+ this._resourceObservationJournals.delete(promptRunId);
1828
+ return null;
1829
+ }
1830
+ }
1831
+ /** §20.2: record the TUI-side completion sound result in its originating run journal. */
1832
+ recordCompletionSoundResult(promptRunId, result) {
1833
+ const journal = this._resourceObservationJournals.get(promptRunId);
1834
+ journal?.record("completion_sound_result_v1", soundObservationFacts(result));
1835
+ this._resourceObservationJournals.delete(promptRunId);
1836
+ if (this._latestResourcePromptRunId === promptRunId) {
1837
+ this._latestResourcePromptRunId = null;
1838
+ if (this._resourceObservations === journal)
1839
+ this._resourceObservations = null;
1840
+ }
1841
+ }
1842
+ /** §19.4 read-only SDK surface: the most recent resource admission decision, if any. */
1843
+ getCurrentResourceAdmission() {
1844
+ return this._lastResourceAdmission;
1845
+ }
1846
+ /** §19.4 read-only SDK surface: capture a fresh host resource snapshot. */
1847
+ async getHostResourceSnapshot() {
1848
+ const resolved = resolveResourceGovernorSettings(this.settingsManager.getResourceGovernorSettings());
1849
+ return captureHostResourceSnapshot({
1850
+ cwd: this._cwd,
1851
+ maxProbeMs: resolved.maxProbeMs,
1852
+ cpuSampleMs: resolved.cpuSampleMs,
1853
+ });
1854
+ }
1855
+ /** §19.4 read-only SDK surface: shared workload permit pool state, if any. */
1856
+ getWorkloadPermitSnapshot() {
1857
+ return this._workloadPermitPool?.snapshot() ?? null;
1858
+ }
1859
+ /**
1860
+ * §14.1 shared-budget seam: child subagents must reuse this instance
1861
+ * instead of creating a private pool (host oversubscription). The live
1862
+ * child launcher wiring lands in M6.
1863
+ */
1864
+ get workloadPermitPool() {
1865
+ this._workloadPermitPool ??= new WorkloadPermitPool();
1866
+ return this._workloadPermitPool;
1867
+ }
1868
+ /**
1869
+ * Resource safety gate for the bash boundary (roadmap §9.4/§11, M3).
1870
+ * Runs only in adaptive/strict mode with a recorded admission decision;
1871
+ * observe/off keep v0.96.1 behavior. Never throws: gate machinery
1872
+ * failures leave the command ungated (§2.1), while computed block
1873
+ * verdicts and permit rejections return a bounded §11.3 payload.
1874
+ */
1875
+ async _acquireBashResourcePermit(command, signal) {
1876
+ try {
1877
+ const resolved = resolveResourceGovernorSettings(this.settingsManager.getResourceGovernorSettings());
1878
+ if (resolved.mode !== "adaptive" && resolved.mode !== "strict") {
1879
+ return {};
1880
+ }
1881
+ const decision = this._lastResourceAdmission;
1882
+ if (decision === null) {
1883
+ return {};
1884
+ }
1885
+ const classification = classifyWorkloadCommand(command);
1886
+ this._resourceObservations?.record("workload_classification_v1", classificationObservationFacts(classification));
1887
+ const verdict = decideResourceSafetyGate({ commandSafety: "allowed", classification, decision });
1888
+ if (verdict.kind === "allow") {
1889
+ return {};
1890
+ }
1891
+ if (verdict.kind === "block") {
1892
+ return { blocked: JSON.stringify(verdict.block) };
1893
+ }
1894
+ this._workloadPermitPool ??= new WorkloadPermitPool({ capacity: decision.maxHeavyProcesses });
1895
+ const pool = this._workloadPermitPool;
1896
+ pool.setCapacity(decision.maxHeavyProcesses);
1897
+ const waitStartedMs = Date.now();
1898
+ this._resourceObservations?.record("workload_permit_wait_v1", {
1899
+ workloadClass: classification.workloadClass,
1900
+ weight: verdict.weight,
1901
+ });
1902
+ try {
1903
+ const permit = await pool.acquire({
1904
+ requestId: `bash-${randomUUID()}`,
1905
+ promptRunId: this._resourceLeaseController?.activeLease?.promptRunId ?? "unleased",
1906
+ workloadClass: classification.workloadClass,
1907
+ weight: verdict.weight,
1908
+ signal,
1909
+ // Bounded §10.3 timeout-aware wait; a settings knob can follow later.
1910
+ timeoutMs: 60_000,
1911
+ });
1912
+ this._resourceObservations?.record("workload_permit_acquired_v1", {
1913
+ workloadClass: classification.workloadClass,
1914
+ weight: verdict.weight,
1915
+ waitMs: Date.now() - waitStartedMs,
1916
+ });
1917
+ return {
1918
+ release: () => {
1919
+ permit.release();
1920
+ this._resourceObservations?.record("workload_permit_released_v1", {
1921
+ workloadClass: classification.workloadClass,
1922
+ weight: verdict.weight,
1923
+ });
1924
+ },
1925
+ };
1926
+ }
1927
+ catch (error) {
1928
+ const code = error instanceof WorkloadPermitError ? error.code : "queue_overflow";
1929
+ return {
1930
+ blocked: JSON.stringify({
1931
+ kind: "resource_pressure",
1932
+ pressure: decision.pressure,
1933
+ action: "defer-heavy",
1934
+ reasonCodes: [...decision.reasons, `resource.permit.${code}`],
1935
+ requiredAction: RESOURCE_PRESSURE_REQUIRED_ACTION,
1936
+ }),
1937
+ };
1938
+ }
1939
+ }
1940
+ catch {
1941
+ return {};
1715
1942
  }
1716
1943
  }
1717
1944
  async _handlePostAgentRun() {
@@ -1753,8 +1980,13 @@ export class AgentSession {
1753
1980
  const expandPromptTemplates = options?.expandPromptTemplates ?? true;
1754
1981
  const preflightResult = options?.preflightResult;
1755
1982
  let currentText = redactSensitiveText(text);
1756
- let promptActiveSkillNames = [...(options?.activeSkillNames ?? [])];
1757
- let promptActiveSkillSource = options?.activeSkillSource;
1983
+ const defaultActiveSkills = this._getDefaultActiveSkills();
1984
+ let promptActiveSkillNames = mergePromptActiveSkillNames(defaultActiveSkills, options?.activeSkillNames ?? []);
1985
+ let promptActiveSkillSource = defaultActiveSkills.length > 0
1986
+ ? options?.activeSkillSource
1987
+ ? `settings+${options.activeSkillSource}`
1988
+ : "settings"
1989
+ : options?.activeSkillSource;
1758
1990
  let isBangSkillInvocation = false;
1759
1991
  if (expandPromptTemplates) {
1760
1992
  const bangInvocation = parseBangInvocation(text, {
@@ -1766,7 +1998,8 @@ export class AgentSession {
1766
1998
  ? `/skill:${bangInvocation.skillName} ${bangInvocation.prompt}`
1767
1999
  : `/skill:${bangInvocation.skillName}`;
1768
2000
  promptActiveSkillNames = mergePromptActiveSkillNames(promptActiveSkillNames, bangInvocation.activeSkillNames);
1769
- promptActiveSkillSource = bangInvocation.source;
2001
+ promptActiveSkillSource =
2002
+ defaultActiveSkills.length > 0 ? `settings+${bangInvocation.source}` : bangInvocation.source;
1770
2003
  }
1771
2004
  }
1772
2005
  let messages;
@@ -1870,8 +2103,8 @@ export class AgentSession {
1870
2103
  const turnSystemPromptOptions = {
1871
2104
  ...this._baseSystemPromptOptions,
1872
2105
  contextBudget: this._getContextBudgetOptions(expandedText),
1873
- ...(promptActiveSkillNames.length > 0 ? { activeSkillNames: promptActiveSkillNames } : {}),
1874
- ...(promptActiveSkillSource ? { activeSkillSource: promptActiveSkillSource } : {}),
2106
+ activeSkillNames: promptActiveSkillNames,
2107
+ activeSkillSource: promptActiveSkillSource,
1875
2108
  };
1876
2109
  const turnSystemPrompt = buildSystemPromptPlan(turnSystemPromptOptions);
1877
2110
  // Emit before_agent_start extension event
@@ -2234,16 +2467,25 @@ export class AgentSession {
2234
2467
  if (!this._modelRegistry.hasConfiguredAuth(model)) {
2235
2468
  throw new Error(`No API key for ${model.provider}/${model.id}`);
2236
2469
  }
2237
- // Root-level: block sticky safety models (Fable) unless explicitly allowed in settings.
2470
+ // Root-level: block sticky safety models (Fable) unless the user pinned `--model`.
2238
2471
  const resilience = resolveProviderResilience(this.settingsManager.getProviderResilienceSettings());
2239
- if (resilience.blockStickySafetyModels && isStickySafetyModel(model.id, model.provider)) {
2472
+ if (shouldEjectStickySafetyModel({
2473
+ blockStickySafetyModels: resilience.blockStickySafetyModels,
2474
+ modelPinned: this._modelPinned,
2475
+ }) &&
2476
+ isStickySafetyModel(model.id, model.provider)) {
2240
2477
  throw new Error(stickySafetyBlockMessage(model.id, model.provider));
2241
2478
  }
2242
2479
  // Grok OAuth: block selecting Imagine models as the session chat model.
2243
2480
  assertTextChatModelForCompletion(model.id, model.provider);
2244
2481
  const previousModel = this.model;
2245
2482
  const thinkingLevel = this._getThinkingLevelForModelSwitch();
2483
+ const nextSystemPrompt = this._rebuildSystemPrompt(this.getActiveToolNames(), model);
2246
2484
  this.agent.state.model = model;
2485
+ this._baseSystemPrompt = nextSystemPrompt;
2486
+ this.agent.state.systemPrompt = this._baseSystemPrompt;
2487
+ this.agent.state.systemPromptCacheBoundary = this._baseSystemPromptCacheBoundary;
2488
+ this.agent.state.systemPromptCacheBoundaryBypass = false;
2247
2489
  this.sessionManager.appendModelChange(model.provider, model.id);
2248
2490
  this.settingsManager.setDefaultModelAndProvider(model.provider, model.id);
2249
2491
  // Re-clamp thinking level for new model's capabilities
@@ -2957,34 +3199,29 @@ export class AgentSession {
2957
3199
  */
2958
3200
  async _checkCompaction(assistantMessage, skipAbortedCheck = true) {
2959
3201
  const settings = this.settingsManager.getCompactionSettings();
2960
- if (!settings.enabled)
2961
- return false;
2962
- // Skip if message was aborted (user cancelled) - unless skipAbortedCheck is false
2963
- if (skipAbortedCheck && assistantMessage.stopReason === "aborted")
2964
- return false;
2965
- const contextWindow = this.model?.contextWindow ?? 0;
2966
- // Skip overflow check if the message came from a different model.
2967
- // This handles the case where user switched from a smaller-context model (e.g. opus)
2968
- // to a larger-context model (e.g. codex) - the overflow error from the old model
2969
- // shouldn't trigger compaction for the new model.
2970
- const sameModel = this.model && assistantMessage.provider === this.model.provider && assistantMessage.model === this.model.id;
2971
- // Image-bearing turns are auto-routed to a vision-capable model (gpt-5.6-luna)
2972
- // while the session model stays text-only (deepseek). An overflow from that
2973
- // routed model must still count as the session's own overflow: compaction is
2974
- // the fix, and the raw context_length_exceeded error must never reach the user.
2975
- const visionRouteOverflow = this.model != null &&
2976
- !(this.model.input ?? []).includes("image") &&
2977
- isVisionRouteModel({ provider: assistantMessage.provider, id: assistantMessage.model });
2978
- // Skip compaction checks if this assistant message is older than the latest
2979
- // compaction boundary. This prevents a stale pre-compaction usage/error
2980
- // from retriggering compaction on the first prompt after compaction.
3202
+ // Stale pre-compaction usage/errors must not retrigger compaction on the
3203
+ // first prompt after one finished.
2981
3204
  const compactionEntry = getLatestCompactionEntry(this.sessionManager.getBranch());
2982
- const assistantIsFromBeforeCompaction = compactionEntry !== null && assistantMessage.timestamp <= new Date(compactionEntry.timestamp).getTime();
2983
- if (assistantIsFromBeforeCompaction) {
3205
+ if (shouldSkipCompactionCheck({
3206
+ enabled: settings.enabled,
3207
+ skipAbortedCheck,
3208
+ stopReason: assistantMessage.stopReason,
3209
+ messageTimestamp: assistantMessage.timestamp,
3210
+ latestCompactionTimestamp: compactionEntry ? new Date(compactionEntry.timestamp).getTime() : undefined,
3211
+ })) {
2984
3212
  return false;
2985
3213
  }
2986
- // Case 1: Overflow - LLM returned context overflow error
2987
- if ((sameModel || visionRouteOverflow) && isContextOverflow(assistantMessage, contextWindow)) {
3214
+ const contextWindow = this.model?.contextWindow ?? 0;
3215
+ // Case 1: Overflow - LLM returned context overflow error. Only counts when
3216
+ // the failing message belongs to the session model — or to the auto-routed
3217
+ // vision model while the session model is text-only.
3218
+ if (isSessionModelOverflow({
3219
+ message: assistantMessage,
3220
+ contextWindow,
3221
+ sessionProvider: this.model?.provider,
3222
+ sessionModelId: this.model?.id,
3223
+ sessionInputs: this.model?.input,
3224
+ })) {
2988
3225
  if (this._overflowRecoveryAttempts >= MAX_OVERFLOW_RECOVERY_ATTEMPTS) {
2989
3226
  this._emit({
2990
3227
  type: "compaction_end",
@@ -3692,20 +3929,7 @@ export class AgentSession {
3692
3929
  * Context overflow errors are NOT retryable (handled by compaction instead).
3693
3930
  */
3694
3931
  _isRetryableError(message) {
3695
- if (message.stopReason !== "error" || !message.errorMessage)
3696
- return false;
3697
- // Context overflow is handled by compaction, not retry
3698
- const contextWindow = this.model?.contextWindow ?? 0;
3699
- if (isContextOverflow(message, contextWindow))
3700
- return false;
3701
- const err = message.errorMessage;
3702
- // Billing/quota exhaustion: useless to retry the same model until the
3703
- // cycle resets, but the turn may still be saved by failing over to the
3704
- // next candidate, so let _prepareRetry run its failover path.
3705
- if (isQuotaExhaustionMessage(err))
3706
- return true;
3707
- // Shared contract with provider-resilience.ts (Fable safety stop, K3 orphan tool_call_id, terminated).
3708
- return isTransientProviderErrorMessage(err);
3932
+ return isRetryableAssistantError(message, this.model?.contextWindow ?? 0);
3709
3933
  }
3710
3934
  /**
3711
3935
  * Retry policy + callbacks shared by compaction and branch-summary summarization calls.
@@ -3742,8 +3966,12 @@ export class AgentSession {
3742
3966
  /** Eject sticky safety model at prompt boundary (session resume / leftover default). */
3743
3967
  async _ejectStickySafetyModelIfNeeded() {
3744
3968
  const resilience = resolveProviderResilience(this.settingsManager.getProviderResilienceSettings());
3745
- if (!resilience.blockStickySafetyModels)
3969
+ if (!shouldEjectStickySafetyModel({
3970
+ blockStickySafetyModels: resilience.blockStickySafetyModels,
3971
+ modelPinned: this._modelPinned,
3972
+ })) {
3746
3973
  return;
3974
+ }
3747
3975
  const current = this.model;
3748
3976
  if (!current || !isStickySafetyModel(current.id, current.provider))
3749
3977
  return;
@@ -3771,11 +3999,17 @@ export class AgentSession {
3771
3999
  */
3772
4000
  async _maybeFailoverFromSafetyStop(message) {
3773
4001
  const resilience = resolveProviderResilience(this.settingsManager.getProviderResilienceSettings());
3774
- if (!resilience.autoFailoverOnSafetyStop)
4002
+ if (!shouldHonorSafetyFailover({
4003
+ autoFailoverOnSafetyStop: resilience.autoFailoverOnSafetyStop,
4004
+ modelPinned: this._modelPinned,
4005
+ modelId: this.model?.id,
4006
+ provider: this.model?.provider,
4007
+ })) {
3775
4008
  return undefined;
4009
+ }
3776
4010
  // Fire on safety-stop FPs AND on billing/quota exhaustion: both mean the
3777
4011
  // current model cannot finish this turn and same-model retry is useless.
3778
- if (!isContentSafetyStopMessage(message.errorMessage) && !isQuotaExhaustionMessage(message.errorMessage)) {
4012
+ if (!isFailoverTriggerError(message.errorMessage)) {
3779
4013
  return undefined;
3780
4014
  }
3781
4015
  const current = this.model;
@@ -3783,10 +4017,10 @@ export class AgentSession {
3783
4017
  // claude-opus-5 emits the same stop_reason=refusal FP; same-model retry is useless.
3784
4018
  // v10.3-Ω: mark the model that just refused so we never re-pick it this turn.
3785
4019
  if (current)
3786
- this._refusedModels.add(`${current.provider}/${current.id}`);
4020
+ this._refusedModels.add(failoverModelKey(current.provider, current.id));
3787
4021
  const pick = pickFailoverCandidate(resilience.failoverCandidates, current ? { provider: current.provider, id: current.id } : undefined, (c) => {
3788
4022
  // Skip any model that already refused/failed this turn → advance the chain.
3789
- if (this._refusedModels.has(`${c.provider}/${c.id}`))
4023
+ if (this._refusedModels.has(failoverModelKey(c.provider, c.id)))
3790
4024
  return false;
3791
4025
  const next = this._modelRegistry.find(c.provider, c.id);
3792
4026
  if (!next)
@@ -3804,34 +4038,32 @@ export class AgentSession {
3804
4038
  }
3805
4039
  catch {
3806
4040
  // setModel failed (auth/guard) — blacklist so next retry advances further.
3807
- this._refusedModels.add(`${pick.provider}/${pick.id}`);
4041
+ this._refusedModels.add(failoverModelKey(pick.provider, pick.id));
3808
4042
  return undefined;
3809
4043
  }
3810
4044
  }
3811
4045
  async _prepareRetry(message) {
3812
4046
  const settings = this.settingsManager.getRetrySettings();
3813
- if (!settings.enabled) {
3814
- return false;
3815
- }
3816
- this._retryAttempt++;
3817
- if (this._retryAttempt > settings.maxRetries) {
3818
- // Preserve the completed attempt count so post-run handling can emit the final failure.
3819
- this._retryAttempt--;
4047
+ const maxRetries = retryBudgetForAssistantError(message, settings.maxRetries);
4048
+ const attempt = nextRetryAttempt({
4049
+ enabled: settings.enabled,
4050
+ completedAttempts: this._retryAttempt,
4051
+ maxRetries,
4052
+ });
4053
+ if (attempt === undefined)
3820
4054
  return false;
3821
- }
4055
+ this._retryAttempt = attempt;
3822
4056
  // Content/safety stop (Fable/Opus/Sonnet): switch model BEFORE delay so retry is not same-model refusal.
3823
4057
  const failoverTo = await this._maybeFailoverFromSafetyStop(message);
3824
4058
  // Safety stops are usually immediate false positives — short delay after failover, full backoff otherwise.
3825
- const delayMs = failoverTo
3826
- ? Math.min(400, settings.baseDelayMs)
3827
- : settings.baseDelayMs * 2 ** (this._retryAttempt - 1);
4059
+ const delayMs = computeRetryDelayMs(settings.baseDelayMs, attempt, failoverTo !== undefined);
3828
4060
  const errorMessage = failoverTo
3829
4061
  ? `${message.errorMessage || "content/safety stop"} → failover ${failoverTo}`
3830
4062
  : message.errorMessage || "Unknown error";
3831
4063
  this._emit({
3832
4064
  type: "auto_retry_start",
3833
- attempt: this._retryAttempt,
3834
- maxAttempts: settings.maxRetries,
4065
+ attempt,
4066
+ maxAttempts: maxRetries,
3835
4067
  delayMs,
3836
4068
  errorMessage,
3837
4069
  });
@@ -4343,21 +4575,6 @@ export class AgentSession {
4343
4575
  return this._extensionRunner;
4344
4576
  }
4345
4577
  }
4346
- // ---------------------------------------------------------------------------
4347
- // Context budget constants
4348
- // ---------------------------------------------------------------------------
4349
- /** Fraction of contextWindow reserved for model response generation. */
4350
- const RESPONSE_RESERVE_RATIO = 0.2;
4351
- /** Fraction of contextWindow held back as safety margin for token-count imprecision. */
4352
- const SAFETY_MARGIN_RATIO = 0.1;
4353
- // Default prompt budget = contextWindow - responseReserve - safetyMargin.
4354
- // With the defaults above (0.2 + 0.1) this yields ~0.70 of contextWindow, but it
4355
- // stays correct when responseReserve is overridden by model.maxTokens.
4356
- /** Absolute floor for maxPromptTokens — below this the budget is meaningless. */
4357
- const MIN_PROMPT_TOKENS = 4000;
4358
- /** Legacy defaults when no model contextWindow is known. */
4359
- const LEGACY_MAX_PROMPT_TOKENS = 60_000;
4360
- const LEGACY_RESPONSE_RESERVE_TOKENS = 8_192;
4361
4578
  function parsePositiveIntegerEnv(name) {
4362
4579
  const raw = process.env[name];
4363
4580
  if (raw === undefined || raw.trim() === "") {