open-multi-agent-kit 0.96.1 → 0.97.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 (267) hide show
  1. package/CHANGELOG.md +57 -0
  2. package/README.md +14 -2
  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/resource-doctor-cli.d.ts +22 -0
  7. package/dist/commands/resource-doctor-cli.d.ts.map +1 -0
  8. package/dist/commands/resource-doctor-cli.js +98 -0
  9. package/dist/commands/resource-doctor-cli.js.map +1 -0
  10. package/dist/core/agent-session.d.ts +46 -2
  11. package/dist/core/agent-session.d.ts.map +1 -1
  12. package/dist/core/agent-session.js +373 -12
  13. package/dist/core/agent-session.js.map +1 -1
  14. package/dist/core/attachment-store.d.ts +39 -0
  15. package/dist/core/attachment-store.d.ts.map +1 -0
  16. package/dist/core/attachment-store.js +81 -0
  17. package/dist/core/attachment-store.js.map +1 -0
  18. package/dist/core/command-safety.d.ts +8 -0
  19. package/dist/core/command-safety.d.ts.map +1 -1
  20. package/dist/core/command-safety.js +140 -14
  21. package/dist/core/command-safety.js.map +1 -1
  22. package/dist/core/compaction/compaction.d.ts +6 -1
  23. package/dist/core/compaction/compaction.d.ts.map +1 -1
  24. package/dist/core/compaction/compaction.js +35 -1
  25. package/dist/core/compaction/compaction.js.map +1 -1
  26. package/dist/core/completion-sound.d.ts +109 -0
  27. package/dist/core/completion-sound.d.ts.map +1 -0
  28. package/dist/core/completion-sound.js +165 -0
  29. package/dist/core/completion-sound.js.map +1 -0
  30. package/dist/core/context-budget-governor-v2.d.ts +1 -1
  31. package/dist/core/context-budget-governor-v2.d.ts.map +1 -1
  32. package/dist/core/context-budget-governor-v2.js +1 -1
  33. package/dist/core/context-budget-governor-v2.js.map +1 -1
  34. package/dist/core/context-budget-system-prompt-items.d.ts.map +1 -1
  35. package/dist/core/context-budget-system-prompt-items.js +2 -2
  36. package/dist/core/context-budget-system-prompt-items.js.map +1 -1
  37. package/dist/core/context-budget-v2-cache-keys.d.ts +3 -0
  38. package/dist/core/context-budget-v2-cache-keys.d.ts.map +1 -1
  39. package/dist/core/context-budget-v2-cache-keys.js +2 -0
  40. package/dist/core/context-budget-v2-cache-keys.js.map +1 -1
  41. package/dist/core/context-budget-v2-plan-cache-keys.d.ts.map +1 -1
  42. package/dist/core/context-budget-v2-plan-cache-keys.js +3 -0
  43. package/dist/core/context-budget-v2-plan-cache-keys.js.map +1 -1
  44. package/dist/core/context-budget-v2-planner.d.ts.map +1 -1
  45. package/dist/core/context-budget-v2-planner.js +2 -1
  46. package/dist/core/context-budget-v2-planner.js.map +1 -1
  47. package/dist/core/context-budget-v2-scoring.d.ts +17 -0
  48. package/dist/core/context-budget-v2-scoring.d.ts.map +1 -1
  49. package/dist/core/context-budget-v2-scoring.js +25 -8
  50. package/dist/core/context-budget-v2-scoring.js.map +1 -1
  51. package/dist/core/context-budget-v2-selection.d.ts +2 -2
  52. package/dist/core/context-budget-v2-selection.d.ts.map +1 -1
  53. package/dist/core/context-budget-v2-selection.js +18 -1
  54. package/dist/core/context-budget-v2-selection.js.map +1 -1
  55. package/dist/core/context-budget-v2-types.d.ts +17 -0
  56. package/dist/core/context-budget-v2-types.d.ts.map +1 -1
  57. package/dist/core/context-budget-v2-types.js +15 -0
  58. package/dist/core/context-budget-v2-types.js.map +1 -1
  59. package/dist/core/extensions/builtin/command-safety-gate.d.ts +3 -1
  60. package/dist/core/extensions/builtin/command-safety-gate.d.ts.map +1 -1
  61. package/dist/core/extensions/builtin/command-safety-gate.js +7 -0
  62. package/dist/core/extensions/builtin/command-safety-gate.js.map +1 -1
  63. package/dist/core/extensions/types.d.ts +12 -1
  64. package/dist/core/extensions/types.d.ts.map +1 -1
  65. package/dist/core/extensions/types.js.map +1 -1
  66. package/dist/core/host-resource-probe.d.ts +63 -0
  67. package/dist/core/host-resource-probe.d.ts.map +1 -0
  68. package/dist/core/host-resource-probe.js +126 -0
  69. package/dist/core/host-resource-probe.js.map +1 -0
  70. package/dist/core/host-resource-snapshot.d.ts +54 -0
  71. package/dist/core/host-resource-snapshot.d.ts.map +1 -0
  72. package/dist/core/host-resource-snapshot.js +139 -0
  73. package/dist/core/host-resource-snapshot.js.map +1 -0
  74. package/dist/core/model-registry.d.ts +8 -3
  75. package/dist/core/model-registry.d.ts.map +1 -1
  76. package/dist/core/model-registry.js +71 -1
  77. package/dist/core/model-registry.js.map +1 -1
  78. package/dist/core/package-doctor-source-scan.d.ts +9 -0
  79. package/dist/core/package-doctor-source-scan.d.ts.map +1 -1
  80. package/dist/core/package-doctor-source-scan.js +77 -4
  81. package/dist/core/package-doctor-source-scan.js.map +1 -1
  82. package/dist/core/prompt-attachment.d.ts +51 -0
  83. package/dist/core/prompt-attachment.d.ts.map +1 -0
  84. package/dist/core/prompt-attachment.js +186 -0
  85. package/dist/core/prompt-attachment.js.map +1 -0
  86. package/dist/core/prompt-settlement.d.ts +81 -0
  87. package/dist/core/prompt-settlement.d.ts.map +1 -0
  88. package/dist/core/prompt-settlement.js +84 -0
  89. package/dist/core/prompt-settlement.js.map +1 -0
  90. package/dist/core/provider-resilience.d.ts +28 -0
  91. package/dist/core/provider-resilience.d.ts.map +1 -1
  92. package/dist/core/provider-resilience.js +51 -1
  93. package/dist/core/provider-resilience.js.map +1 -1
  94. package/dist/core/provider-retry.d.ts +7 -0
  95. package/dist/core/provider-retry.d.ts.map +1 -1
  96. package/dist/core/provider-retry.js +24 -0
  97. package/dist/core/provider-retry.js.map +1 -1
  98. package/dist/core/provider-usage.d.ts +15 -2
  99. package/dist/core/provider-usage.d.ts.map +1 -1
  100. package/dist/core/provider-usage.js +176 -2
  101. package/dist/core/provider-usage.js.map +1 -1
  102. package/dist/core/reasoning-router-promotion.d.ts +84 -0
  103. package/dist/core/reasoning-router-promotion.d.ts.map +1 -0
  104. package/dist/core/reasoning-router-promotion.js +92 -0
  105. package/dist/core/reasoning-router-promotion.js.map +1 -0
  106. package/dist/core/reasoning-router-resolver.d.ts.map +1 -1
  107. package/dist/core/reasoning-router-resolver.js +1 -1
  108. package/dist/core/reasoning-router-resolver.js.map +1 -1
  109. package/dist/core/resource-admission-config.d.ts +55 -0
  110. package/dist/core/resource-admission-config.d.ts.map +1 -0
  111. package/dist/core/resource-admission-config.js +97 -0
  112. package/dist/core/resource-admission-config.js.map +1 -0
  113. package/dist/core/resource-admission.d.ts +81 -0
  114. package/dist/core/resource-admission.d.ts.map +1 -0
  115. package/dist/core/resource-admission.js +128 -0
  116. package/dist/core/resource-admission.js.map +1 -0
  117. package/dist/core/resource-governor-format.d.ts +50 -0
  118. package/dist/core/resource-governor-format.d.ts.map +1 -0
  119. package/dist/core/resource-governor-format.js +113 -0
  120. package/dist/core/resource-governor-format.js.map +1 -0
  121. package/dist/core/resource-governor-settings.d.ts +56 -0
  122. package/dist/core/resource-governor-settings.d.ts.map +1 -0
  123. package/dist/core/resource-governor-settings.js +89 -0
  124. package/dist/core/resource-governor-settings.js.map +1 -0
  125. package/dist/core/resource-observation-journal.d.ts +74 -0
  126. package/dist/core/resource-observation-journal.d.ts.map +1 -0
  127. package/dist/core/resource-observation-journal.js +198 -0
  128. package/dist/core/resource-observation-journal.js.map +1 -0
  129. package/dist/core/resource-safety-gate.d.ts +54 -0
  130. package/dist/core/resource-safety-gate.d.ts.map +1 -0
  131. package/dist/core/resource-safety-gate.js +51 -0
  132. package/dist/core/resource-safety-gate.js.map +1 -0
  133. package/dist/core/run-journal.d.ts.map +1 -1
  134. package/dist/core/run-journal.js +13 -1
  135. package/dist/core/run-journal.js.map +1 -1
  136. package/dist/core/run-resource-lease.d.ts +73 -0
  137. package/dist/core/run-resource-lease.d.ts.map +1 -0
  138. package/dist/core/run-resource-lease.js +78 -0
  139. package/dist/core/run-resource-lease.js.map +1 -0
  140. package/dist/core/session-bash-service.d.ts +12 -0
  141. package/dist/core/session-bash-service.d.ts.map +1 -1
  142. package/dist/core/session-bash-service.js +18 -3
  143. package/dist/core/session-bash-service.js.map +1 -1
  144. package/dist/core/session-system-prompt.d.ts +2 -0
  145. package/dist/core/session-system-prompt.d.ts.map +1 -1
  146. package/dist/core/session-system-prompt.js +2 -0
  147. package/dist/core/session-system-prompt.js.map +1 -1
  148. package/dist/core/session-termination.d.ts +8 -3
  149. package/dist/core/session-termination.d.ts.map +1 -1
  150. package/dist/core/session-termination.js +40 -3
  151. package/dist/core/session-termination.js.map +1 -1
  152. package/dist/core/settings-manager.d.ts +12 -0
  153. package/dist/core/settings-manager.d.ts.map +1 -1
  154. package/dist/core/settings-manager.js +36 -0
  155. package/dist/core/settings-manager.js.map +1 -1
  156. package/dist/core/slash-commands.d.ts.map +1 -1
  157. package/dist/core/slash-commands.js +1 -0
  158. package/dist/core/slash-commands.js.map +1 -1
  159. package/dist/core/subagent-lane-launcher.d.ts +64 -0
  160. package/dist/core/subagent-lane-launcher.d.ts.map +1 -0
  161. package/dist/core/subagent-lane-launcher.js +107 -0
  162. package/dist/core/subagent-lane-launcher.js.map +1 -0
  163. package/dist/core/system-cpu-sampler.d.ts +45 -0
  164. package/dist/core/system-cpu-sampler.d.ts.map +1 -0
  165. package/dist/core/system-cpu-sampler.js +95 -0
  166. package/dist/core/system-cpu-sampler.js.map +1 -0
  167. package/dist/core/system-prompt.d.ts.map +1 -1
  168. package/dist/core/system-prompt.js +5 -8
  169. package/dist/core/system-prompt.js.map +1 -1
  170. package/dist/core/tools/bash.d.ts.map +1 -1
  171. package/dist/core/tools/bash.js +3 -7
  172. package/dist/core/tools/bash.js.map +1 -1
  173. package/dist/core/tools/grep.d.ts.map +1 -1
  174. package/dist/core/tools/grep.js +13 -48
  175. package/dist/core/tools/grep.js.map +1 -1
  176. package/dist/core/tools/read.d.ts.map +1 -1
  177. package/dist/core/tools/read.js +1 -34
  178. package/dist/core/tools/read.js.map +1 -1
  179. package/dist/core/workload-classifier.d.ts +32 -0
  180. package/dist/core/workload-classifier.d.ts.map +1 -0
  181. package/dist/core/workload-classifier.js +66 -0
  182. package/dist/core/workload-classifier.js.map +1 -0
  183. package/dist/core/workload-family-matcher.d.ts +3 -0
  184. package/dist/core/workload-family-matcher.d.ts.map +1 -0
  185. package/dist/core/workload-family-matcher.js +126 -0
  186. package/dist/core/workload-family-matcher.js.map +1 -0
  187. package/dist/core/workload-permit-pool.d.ts +78 -0
  188. package/dist/core/workload-permit-pool.d.ts.map +1 -0
  189. package/dist/core/workload-permit-pool.js +138 -0
  190. package/dist/core/workload-permit-pool.js.map +1 -0
  191. package/dist/core/workload-shard-executor.d.ts +68 -0
  192. package/dist/core/workload-shard-executor.d.ts.map +1 -0
  193. package/dist/core/workload-shard-executor.js +156 -0
  194. package/dist/core/workload-shard-executor.js.map +1 -0
  195. package/dist/core/workload-shard-plan.d.ts +81 -0
  196. package/dist/core/workload-shard-plan.d.ts.map +1 -0
  197. package/dist/core/workload-shard-plan.js +183 -0
  198. package/dist/core/workload-shard-plan.js.map +1 -0
  199. package/dist/core/workload-shard-runner.d.ts +11 -0
  200. package/dist/core/workload-shard-runner.d.ts.map +1 -0
  201. package/dist/core/workload-shard-runner.js +98 -0
  202. package/dist/core/workload-shard-runner.js.map +1 -0
  203. package/dist/core/workload-shard-store.d.ts +44 -0
  204. package/dist/core/workload-shard-store.d.ts.map +1 -0
  205. package/dist/core/workload-shard-store.js +189 -0
  206. package/dist/core/workload-shard-store.js.map +1 -0
  207. package/dist/core/workload-sharders.d.ts +47 -0
  208. package/dist/core/workload-sharders.d.ts.map +1 -0
  209. package/dist/core/workload-sharders.js +162 -0
  210. package/dist/core/workload-sharders.js.map +1 -0
  211. package/dist/core/workload-shell-scan.d.ts +12 -0
  212. package/dist/core/workload-shell-scan.d.ts.map +1 -0
  213. package/dist/core/workload-shell-scan.js +79 -0
  214. package/dist/core/workload-shell-scan.js.map +1 -0
  215. package/dist/index.d.ts +1 -1
  216. package/dist/index.d.ts.map +1 -1
  217. package/dist/index.js +1 -1
  218. package/dist/index.js.map +1 -1
  219. package/dist/main.d.ts.map +1 -1
  220. package/dist/main.js +6 -0
  221. package/dist/main.js.map +1 -1
  222. package/dist/modes/interactive/components/attachment-strip.d.ts +21 -0
  223. package/dist/modes/interactive/components/attachment-strip.d.ts.map +1 -0
  224. package/dist/modes/interactive/components/attachment-strip.js +136 -0
  225. package/dist/modes/interactive/components/attachment-strip.js.map +1 -0
  226. package/dist/modes/interactive/components/footer.d.ts +6 -0
  227. package/dist/modes/interactive/components/footer.d.ts.map +1 -1
  228. package/dist/modes/interactive/components/footer.js +26 -1
  229. package/dist/modes/interactive/components/footer.js.map +1 -1
  230. package/dist/modes/interactive/components/status-sidebar.d.ts.map +1 -1
  231. package/dist/modes/interactive/components/status-sidebar.js +12 -1
  232. package/dist/modes/interactive/components/status-sidebar.js.map +1 -1
  233. package/dist/modes/interactive/interactive-mode.d.ts +27 -4
  234. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  235. package/dist/modes/interactive/interactive-mode.js +255 -53
  236. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  237. package/docs/compaction.md +1 -1
  238. package/docs/extensions.md +12 -3
  239. package/docs/index.md +4 -1
  240. package/docs/metrics.md +74 -2
  241. package/docs/provider-resilience.md +11 -0
  242. package/docs/providers.md +1 -1
  243. package/docs/sdk.md +4 -0
  244. package/docs/settings.md +64 -2
  245. package/docs/skills.md +16 -0
  246. package/docs/usage.md +1 -1
  247. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  248. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  249. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  250. package/examples/extensions/gondolin/package-lock.json +2 -2
  251. package/examples/extensions/gondolin/package.json +1 -1
  252. package/examples/extensions/sandbox/package-lock.json +2 -2
  253. package/examples/extensions/sandbox/package.json +1 -1
  254. package/examples/extensions/subagent/agent-capability-router.test.ts +7 -0
  255. package/examples/extensions/subagent/domain-profiles.ts +0 -1
  256. package/examples/extensions/with-deps/package-lock.json +2 -2
  257. package/examples/extensions/with-deps/package.json +1 -1
  258. package/npm-shrinkwrap.json +18 -18
  259. package/package.json +8 -10
  260. package/dist/core/tools/omp-pure-seams.d.ts +0 -41
  261. package/dist/core/tools/omp-pure-seams.d.ts.map +0 -1
  262. package/dist/core/tools/omp-pure-seams.js +0 -142
  263. package/dist/core/tools/omp-pure-seams.js.map +0 -1
  264. package/dist/core/tools/omp-seam-runtime.d.ts +0 -100
  265. package/dist/core/tools/omp-seam-runtime.d.ts.map +0 -1
  266. package/dist/core/tools/omp-seam-runtime.js +0 -40
  267. package/dist/core/tools/omp-seam-runtime.js.map +0 -1
@@ -0,0 +1,128 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { computeHostResourceSnapshotDigest, effectiveAvailableMemoryBytes, } from "./host-resource-snapshot.js";
3
+ import { DEFAULT_RESOURCE_ADMISSION_CONFIG, DEFAULT_RESOURCE_ADMISSION_THRESHOLDS, } from "./resource-admission-config.js";
4
+ export { DEFAULT_RESOURCE_ADMISSION_CAP_TABLE, DEFAULT_RESOURCE_ADMISSION_CONFIG, DEFAULT_RESOURCE_ADMISSION_THRESHOLDS, resolveResourceAdmissionConfig, validateResourceAdmissionConfig, } from "./resource-admission-config.js";
5
+ /**
6
+ * Pure resource admission policy (OMK v0.97.x roadmap §6.6-§7, M1/PR1).
7
+ *
8
+ * This module maps an immutable {@link HostResourceSnapshot} to an immutable
9
+ * {@link ResourceAdmissionDecision}. It is deterministic (clock and id are
10
+ * injectable), side-effect free, and performs no scheduler wiring — the
11
+ * decision only becomes scheduler authority in later milestones (M2+).
12
+ *
13
+ * Policy invariants (verified by property tests, roadmap §23.2):
14
+ * - Monotonic degradation (§4.4): worse memory/disk/CPU never improves
15
+ * pressure and never raises a cap.
16
+ * - Conservative unknown handling (§4.3): a known critical fact wins; a
17
+ * missing key probe (memory, disk, heap) degrades to `constrained`; only a
18
+ * fully healthy snapshot is `normal`.
19
+ * - CPU alone never produces `critical` (§6.7): CPU pressure throttles,
20
+ * while memory/disk/heap may block heavy admission.
21
+ * - User cap precedence (§7.3): effective caps never exceed configured caps,
22
+ * and `0 = unlimited` settings are limited to the admission cap.
23
+ */
24
+ export const RESOURCE_ADMISSION_VERSION = 1;
25
+ const MIB = 1024 * 1024;
26
+ const MIN_CAP = 1;
27
+ const PRESSURE_RANK = { normal: 0, constrained: 1, critical: 2 };
28
+ const RANK_TO_PRESSURE = ["normal", "constrained", "critical"];
29
+ /** §7 action mapping: allow at normal, throttle at constrained, defer heavy work at critical. */
30
+ const ACTION_BY_PRESSURE = {
31
+ normal: "allow",
32
+ constrained: "throttle",
33
+ critical: "defer-heavy",
34
+ };
35
+ /** Total order for pressure levels: `normal` (0) < `constrained` (1) < `critical` (2). */
36
+ export function resourcePressureRank(pressure) {
37
+ return PRESSURE_RANK[pressure];
38
+ }
39
+ /**
40
+ * Derive pressure and reason codes from a snapshot (§4.3, §6.6, §6.7).
41
+ * Pure and monotonic: strictly worse inputs never yield a better pressure.
42
+ */
43
+ export function evaluateResourcePressure(snapshot, thresholds = DEFAULT_RESOURCE_ADMISSION_THRESHOLDS) {
44
+ const reasons = [];
45
+ let rank = PRESSURE_RANK.normal;
46
+ const escalate = (target, reason) => {
47
+ reasons.push(reason);
48
+ rank = Math.max(rank, PRESSURE_RANK[target]);
49
+ };
50
+ const memory = effectiveAvailableMemoryBytes(snapshot);
51
+ if (memory !== null) {
52
+ if (memory < thresholds.criticalAvailableMemoryMiB * MIB) {
53
+ escalate("critical", "resource.memory.critical");
54
+ }
55
+ else if (memory < thresholds.constrainedAvailableMemoryMiB * MIB) {
56
+ escalate("constrained", "resource.memory.low");
57
+ }
58
+ }
59
+ const disk = snapshot.workspaceAvailableBytes;
60
+ if (disk !== null) {
61
+ if (disk < thresholds.criticalDiskFreeMiB * MIB) {
62
+ escalate("critical", "resource.disk.critical");
63
+ }
64
+ else if (disk < thresholds.constrainedDiskFreeMiB * MIB) {
65
+ escalate("constrained", "resource.disk.low");
66
+ }
67
+ }
68
+ const heapRatio = snapshot.heapLimitBytes > 0 ? snapshot.heapUsedBytes / snapshot.heapLimitBytes : null;
69
+ if (heapRatio !== null) {
70
+ if (heapRatio >= thresholds.criticalHeapRatio) {
71
+ escalate("critical", "resource.heap.critical");
72
+ }
73
+ else if (heapRatio >= thresholds.constrainedHeapRatio) {
74
+ escalate("constrained", "resource.heap.high");
75
+ }
76
+ }
77
+ // §6.7: CPU pressure only throttles; it can never be critical on its own.
78
+ if (snapshot.systemCpuPercent !== null && snapshot.systemCpuPercent >= thresholds.busyCpuPercent) {
79
+ escalate("constrained", "resource.cpu.busy");
80
+ }
81
+ // §4.3 conservative unknown handling. Key probes are the heavy-admission
82
+ // authorities (memory, disk, heap); a missing one degrades to constrained.
83
+ if (memory === null || disk === null || heapRatio === null) {
84
+ escalate("constrained", "resource.probe.partial");
85
+ }
86
+ // §21: any probe timeout keeps the prompt going but never reports normal.
87
+ if (snapshot.diagnostics.some((code) => code.endsWith(".timeout"))) {
88
+ escalate("constrained", "resource.probe.timeout");
89
+ }
90
+ return { pressure: RANK_TO_PRESSURE[rank] ?? "critical", reasons };
91
+ }
92
+ /** Map a snapshot to an immutable admission decision (§7.1-§7.3). */
93
+ export function decideResourceAdmission(input) {
94
+ const config = input.config ?? DEFAULT_RESOURCE_ADMISSION_CONFIG;
95
+ const { pressure, reasons } = evaluateResourcePressure(input.snapshot, config.thresholds);
96
+ const tier = config.caps[pressure];
97
+ return {
98
+ schemaVersion: RESOURCE_ADMISSION_VERSION,
99
+ decisionId: input.decisionId ?? `res-adm-${randomUUID()}`,
100
+ snapshotDigest: computeHostResourceSnapshotDigest(input.snapshot),
101
+ pressure,
102
+ action: ACTION_BY_PRESSURE[pressure],
103
+ maxToolConcurrency: effectiveCap(input.configuredCaps?.maxToolConcurrency, tier.maxToolConcurrency),
104
+ maxParallelLanes: effectiveCap(input.configuredCaps?.maxParallelLanes, tier.maxParallelLanes),
105
+ maxHeavyProcesses: effectiveCap(input.configuredCaps?.maxHeavyProcesses, tier.maxHeavyProcesses),
106
+ reasons,
107
+ decidedAt: input.decidedAt ?? new Date().toISOString(),
108
+ };
109
+ }
110
+ /** Bounded hint for the model (§6.2). Carries no raw host values. */
111
+ export function toModelResourceBudgetHint(decision) {
112
+ return {
113
+ pressure: decision.pressure,
114
+ maxToolConcurrency: decision.maxToolConcurrency,
115
+ maxParallelLanes: decision.maxParallelLanes,
116
+ maxHeavyProcesses: decision.maxHeavyProcesses,
117
+ reasons: decision.reasons,
118
+ };
119
+ }
120
+ /** §7.3: `min(configured, admission)`, where configured `0`/absent means unlimited. */
121
+ function effectiveCap(configured, admission) {
122
+ const admitted = Math.max(MIN_CAP, Math.floor(admission));
123
+ if (typeof configured !== "number" || !Number.isFinite(configured) || configured <= 0) {
124
+ return admitted;
125
+ }
126
+ return Math.max(MIN_CAP, Math.min(Math.floor(configured), admitted));
127
+ }
128
+ //# sourceMappingURL=resource-admission.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resource-admission.js","sourceRoot":"","sources":["../../src/core/resource-admission.ts"],"names":[],"mappings":"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","sourcesContent":["import { randomUUID } from \"node:crypto\";\nimport {\n\tcomputeHostResourceSnapshotDigest,\n\teffectiveAvailableMemoryBytes,\n\ttype HostResourceSnapshot,\n} from \"./host-resource-snapshot.ts\";\nimport {\n\tDEFAULT_RESOURCE_ADMISSION_CONFIG,\n\tDEFAULT_RESOURCE_ADMISSION_THRESHOLDS,\n\ttype ResourceAdmissionConfig,\n\ttype ResourceAdmissionThresholds,\n} from \"./resource-admission-config.ts\";\n\nexport type {\n\tResolvedResourceAdmissionConfig,\n\tResourceAdmissionCapTable,\n\tResourceAdmissionConfig,\n\tResourceAdmissionConfigOverrides,\n\tResourceAdmissionThresholds,\n\tResourceGovernorMode,\n\tResourcePressureCaps,\n} from \"./resource-admission-config.ts\";\nexport {\n\tDEFAULT_RESOURCE_ADMISSION_CAP_TABLE,\n\tDEFAULT_RESOURCE_ADMISSION_CONFIG,\n\tDEFAULT_RESOURCE_ADMISSION_THRESHOLDS,\n\tresolveResourceAdmissionConfig,\n\tvalidateResourceAdmissionConfig,\n} from \"./resource-admission-config.ts\";\n\n/**\n * Pure resource admission policy (OMK v0.97.x roadmap §6.6-§7, M1/PR1).\n *\n * This module maps an immutable {@link HostResourceSnapshot} to an immutable\n * {@link ResourceAdmissionDecision}. It is deterministic (clock and id are\n * injectable), side-effect free, and performs no scheduler wiring — the\n * decision only becomes scheduler authority in later milestones (M2+).\n *\n * Policy invariants (verified by property tests, roadmap §23.2):\n * - Monotonic degradation (§4.4): worse memory/disk/CPU never improves\n * pressure and never raises a cap.\n * - Conservative unknown handling (§4.3): a known critical fact wins; a\n * missing key probe (memory, disk, heap) degrades to `constrained`; only a\n * fully healthy snapshot is `normal`.\n * - CPU alone never produces `critical` (§6.7): CPU pressure throttles,\n * while memory/disk/heap may block heavy admission.\n * - User cap precedence (§7.3): effective caps never exceed configured caps,\n * and `0 = unlimited` settings are limited to the admission cap.\n */\n\nexport const RESOURCE_ADMISSION_VERSION = 1 as const;\n\nexport type ResourcePressure = \"normal\" | \"constrained\" | \"critical\";\n\nexport type ResourceAdmissionAction = \"allow\" | \"throttle\" | \"defer-heavy\";\n\nexport type ResourceReasonCode =\n\t| \"resource.memory.low\"\n\t| \"resource.memory.critical\"\n\t| \"resource.disk.low\"\n\t| \"resource.disk.critical\"\n\t| \"resource.cpu.busy\"\n\t| \"resource.heap.high\"\n\t| \"resource.heap.critical\"\n\t| \"resource.probe.partial\"\n\t| \"resource.probe.timeout\";\n\nexport interface ResourceAdmissionDecision {\n\treadonly schemaVersion: typeof RESOURCE_ADMISSION_VERSION;\n\treadonly decisionId: string;\n\treadonly snapshotDigest: string;\n\treadonly pressure: ResourcePressure;\n\treadonly action: ResourceAdmissionAction;\n\n\treadonly maxToolConcurrency: number;\n\treadonly maxParallelLanes: number;\n\treadonly maxHeavyProcesses: number;\n\n\treadonly reasons: readonly ResourceReasonCode[];\n\treadonly decidedAt: string;\n}\n\n/** Bounded model-facing hint (§6.2). Never expose the raw snapshot to the model. */\nexport interface ModelResourceBudgetHint {\n\treadonly pressure: ResourcePressure;\n\treadonly maxToolConcurrency: number;\n\treadonly maxParallelLanes: number;\n\treadonly maxHeavyProcesses: number;\n\treadonly reasons: readonly ResourceReasonCode[];\n}\n\nconst MIB = 1024 * 1024;\nconst MIN_CAP = 1;\nconst PRESSURE_RANK: Record<ResourcePressure, number> = { normal: 0, constrained: 1, critical: 2 };\nconst RANK_TO_PRESSURE: readonly ResourcePressure[] = [\"normal\", \"constrained\", \"critical\"];\n/** §7 action mapping: allow at normal, throttle at constrained, defer heavy work at critical. */\nconst ACTION_BY_PRESSURE: Record<ResourcePressure, ResourceAdmissionAction> = {\n\tnormal: \"allow\",\n\tconstrained: \"throttle\",\n\tcritical: \"defer-heavy\",\n};\n\n/** Total order for pressure levels: `normal` (0) < `constrained` (1) < `critical` (2). */\nexport function resourcePressureRank(pressure: ResourcePressure): number {\n\treturn PRESSURE_RANK[pressure];\n}\n\nexport interface ResourcePressureEvaluation {\n\treadonly pressure: ResourcePressure;\n\treadonly reasons: readonly ResourceReasonCode[];\n}\n\n/**\n * Derive pressure and reason codes from a snapshot (§4.3, §6.6, §6.7).\n * Pure and monotonic: strictly worse inputs never yield a better pressure.\n */\nexport function evaluateResourcePressure(\n\tsnapshot: HostResourceSnapshot,\n\tthresholds: ResourceAdmissionThresholds = DEFAULT_RESOURCE_ADMISSION_THRESHOLDS,\n): ResourcePressureEvaluation {\n\tconst reasons: ResourceReasonCode[] = [];\n\tlet rank = PRESSURE_RANK.normal;\n\tconst escalate = (target: ResourcePressure, reason: ResourceReasonCode): void => {\n\t\treasons.push(reason);\n\t\trank = Math.max(rank, PRESSURE_RANK[target]);\n\t};\n\n\tconst memory = effectiveAvailableMemoryBytes(snapshot);\n\tif (memory !== null) {\n\t\tif (memory < thresholds.criticalAvailableMemoryMiB * MIB) {\n\t\t\tescalate(\"critical\", \"resource.memory.critical\");\n\t\t} else if (memory < thresholds.constrainedAvailableMemoryMiB * MIB) {\n\t\t\tescalate(\"constrained\", \"resource.memory.low\");\n\t\t}\n\t}\n\n\tconst disk = snapshot.workspaceAvailableBytes;\n\tif (disk !== null) {\n\t\tif (disk < thresholds.criticalDiskFreeMiB * MIB) {\n\t\t\tescalate(\"critical\", \"resource.disk.critical\");\n\t\t} else if (disk < thresholds.constrainedDiskFreeMiB * MIB) {\n\t\t\tescalate(\"constrained\", \"resource.disk.low\");\n\t\t}\n\t}\n\n\tconst heapRatio = snapshot.heapLimitBytes > 0 ? snapshot.heapUsedBytes / snapshot.heapLimitBytes : null;\n\tif (heapRatio !== null) {\n\t\tif (heapRatio >= thresholds.criticalHeapRatio) {\n\t\t\tescalate(\"critical\", \"resource.heap.critical\");\n\t\t} else if (heapRatio >= thresholds.constrainedHeapRatio) {\n\t\t\tescalate(\"constrained\", \"resource.heap.high\");\n\t\t}\n\t}\n\n\t// §6.7: CPU pressure only throttles; it can never be critical on its own.\n\tif (snapshot.systemCpuPercent !== null && snapshot.systemCpuPercent >= thresholds.busyCpuPercent) {\n\t\tescalate(\"constrained\", \"resource.cpu.busy\");\n\t}\n\n\t// §4.3 conservative unknown handling. Key probes are the heavy-admission\n\t// authorities (memory, disk, heap); a missing one degrades to constrained.\n\tif (memory === null || disk === null || heapRatio === null) {\n\t\tescalate(\"constrained\", \"resource.probe.partial\");\n\t}\n\t// §21: any probe timeout keeps the prompt going but never reports normal.\n\tif (snapshot.diagnostics.some((code) => code.endsWith(\".timeout\"))) {\n\t\tescalate(\"constrained\", \"resource.probe.timeout\");\n\t}\n\n\treturn { pressure: RANK_TO_PRESSURE[rank] ?? \"critical\", reasons };\n}\n\n/**\n * Existing user settings that admission must never exceed (§7.3).\n * `0` or `undefined` means \"unlimited\" and is limited to the admission cap.\n */\nexport interface ConfiguredResourceCaps {\n\treadonly maxToolConcurrency?: number;\n\treadonly maxParallelLanes?: number;\n\treadonly maxHeavyProcesses?: number;\n}\n\nexport interface ResourceAdmissionInput {\n\treadonly snapshot: HostResourceSnapshot;\n\treadonly config?: ResourceAdmissionConfig;\n\treadonly configuredCaps?: ConfiguredResourceCaps;\n\t/** Injectable for deterministic tests; defaults to a UUID-based id. */\n\treadonly decisionId?: string;\n\t/** Injectable ISO timestamp; defaults to the current time. */\n\treadonly decidedAt?: string;\n}\n\n/** Map a snapshot to an immutable admission decision (§7.1-§7.3). */\nexport function decideResourceAdmission(input: ResourceAdmissionInput): ResourceAdmissionDecision {\n\tconst config = input.config ?? DEFAULT_RESOURCE_ADMISSION_CONFIG;\n\tconst { pressure, reasons } = evaluateResourcePressure(input.snapshot, config.thresholds);\n\tconst tier = config.caps[pressure];\n\treturn {\n\t\tschemaVersion: RESOURCE_ADMISSION_VERSION,\n\t\tdecisionId: input.decisionId ?? `res-adm-${randomUUID()}`,\n\t\tsnapshotDigest: computeHostResourceSnapshotDigest(input.snapshot),\n\t\tpressure,\n\t\taction: ACTION_BY_PRESSURE[pressure],\n\t\tmaxToolConcurrency: effectiveCap(input.configuredCaps?.maxToolConcurrency, tier.maxToolConcurrency),\n\t\tmaxParallelLanes: effectiveCap(input.configuredCaps?.maxParallelLanes, tier.maxParallelLanes),\n\t\tmaxHeavyProcesses: effectiveCap(input.configuredCaps?.maxHeavyProcesses, tier.maxHeavyProcesses),\n\t\treasons,\n\t\tdecidedAt: input.decidedAt ?? new Date().toISOString(),\n\t};\n}\n\n/** Bounded hint for the model (§6.2). Carries no raw host values. */\nexport function toModelResourceBudgetHint(decision: ResourceAdmissionDecision): ModelResourceBudgetHint {\n\treturn {\n\t\tpressure: decision.pressure,\n\t\tmaxToolConcurrency: decision.maxToolConcurrency,\n\t\tmaxParallelLanes: decision.maxParallelLanes,\n\t\tmaxHeavyProcesses: decision.maxHeavyProcesses,\n\t\treasons: decision.reasons,\n\t};\n}\n\n/** §7.3: `min(configured, admission)`, where configured `0`/absent means unlimited. */\nfunction effectiveCap(configured: number | undefined, admission: number): number {\n\tconst admitted = Math.max(MIN_CAP, Math.floor(admission));\n\tif (typeof configured !== \"number\" || !Number.isFinite(configured) || configured <= 0) {\n\t\treturn admitted;\n\t}\n\treturn Math.max(MIN_CAP, Math.min(Math.floor(configured), admitted));\n}\n"]}
@@ -0,0 +1,50 @@
1
+ import type { HostResourceSnapshot } from "./host-resource-snapshot.ts";
2
+ import type { ResourceAdmissionDecision, ResourceGovernorMode, ResourceReasonCode } from "./resource-admission.ts";
3
+ import type { ResolvedResourceGovernorSettings } from "./resource-governor-settings.ts";
4
+ /** Humanize reason codes for the §19.1 summary line ("memory low, CPU busy"). */
5
+ export declare function describeResourceReasons(reasons: readonly ResourceReasonCode[]): string;
6
+ /** §19.1 summary block shared by `/resource` and the doctor's human output. */
7
+ export declare function formatResourceSummaryLines(mode: ResourceGovernorMode, decision: ResourceAdmissionDecision): string[];
8
+ /**
9
+ * Verbose probe details for explicit local diagnostics only (§19.1 verbose,
10
+ * §11.3: raw values never enter tool results or model prompts).
11
+ */
12
+ export declare function formatResourceProbeLines(snapshot: HostResourceSnapshot): string[];
13
+ /** Policy view: mode, §6.7 thresholds, §7.2 caps, and §18.1 validation errors. */
14
+ export declare function formatResourcePolicyLines(resolved: ResolvedResourceGovernorSettings): string[];
15
+ /** Bounded JSON schema for `omk doctor resources --json` (§19.2). */
16
+ export interface ResourceDoctorReport {
17
+ readonly schemaVersion: 1;
18
+ readonly command: "resource_doctor";
19
+ readonly mode: ResourceGovernorMode;
20
+ readonly pressure: ResourceAdmissionDecision["pressure"];
21
+ readonly action: ResourceAdmissionDecision["action"];
22
+ readonly caps: {
23
+ readonly maxToolConcurrency: number;
24
+ readonly maxParallelLanes: number;
25
+ readonly maxHeavyProcesses: number;
26
+ };
27
+ readonly reasons: readonly ResourceReasonCode[];
28
+ readonly probeDurationMs: number;
29
+ readonly snapshot: {
30
+ readonly environment: HostResourceSnapshot["environment"];
31
+ readonly logicalCpuCount: number;
32
+ readonly effectiveAvailableMemoryMiB: number | null;
33
+ readonly processAvailableMemoryMiB: number | null;
34
+ readonly constrainedMemoryMiB: number | null;
35
+ readonly hostFreeMemoryMiB: number | null;
36
+ readonly hostTotalMemoryMiB: number | null;
37
+ readonly workspaceAvailableMiB: number | null;
38
+ readonly heapUsedRatio: number | null;
39
+ readonly systemCpuPercent: number | null;
40
+ readonly diagnostics: readonly string[];
41
+ };
42
+ readonly settingsErrors: readonly string[];
43
+ }
44
+ export declare function buildResourceDoctorReport(input: {
45
+ readonly snapshot: HostResourceSnapshot;
46
+ readonly decision: ResourceAdmissionDecision;
47
+ readonly resolved: ResolvedResourceGovernorSettings;
48
+ readonly probeDurationMs: number;
49
+ }): ResourceDoctorReport;
50
+ //# sourceMappingURL=resource-governor-format.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resource-governor-format.d.ts","sourceRoot":"","sources":["../../src/core/resource-governor-format.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,6BAA6B,CAAC;AAExE,OAAO,KAAK,EAAE,yBAAyB,EAAE,oBAAoB,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AACnH,OAAO,KAAK,EAAE,gCAAgC,EAAE,MAAM,iCAAiC,CAAC;AAcxF,kFAAiF;AACjF,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,SAAS,kBAAkB,EAAE,GAAG,MAAM,CAKtF;AAED,gFAA+E;AAC/E,wBAAgB,0BAA0B,CAAC,IAAI,EAAE,oBAAoB,EAAE,QAAQ,EAAE,yBAAyB,GAAG,MAAM,EAAE,CAUpH;AAED;;;GAGG;AACH,wBAAgB,wBAAwB,CAAC,QAAQ,EAAE,oBAAoB,GAAG,MAAM,EAAE,CAiBjF;AAED,qFAAkF;AAClF,wBAAgB,yBAAyB,CAAC,QAAQ,EAAE,gCAAgC,GAAG,MAAM,EAAE,CAiB9F;AAED,sEAAqE;AACrE,MAAM,WAAW,oBAAoB;IACpC,QAAQ,CAAC,aAAa,EAAE,CAAC,CAAC;IAC1B,QAAQ,CAAC,OAAO,EAAE,iBAAiB,CAAC;IACpC,QAAQ,CAAC,IAAI,EAAE,oBAAoB,CAAC;IACpC,QAAQ,CAAC,QAAQ,EAAE,yBAAyB,CAAC,UAAU,CAAC,CAAC;IACzD,QAAQ,CAAC,MAAM,EAAE,yBAAyB,CAAC,QAAQ,CAAC,CAAC;IACrD,QAAQ,CAAC,IAAI,EAAE;QACd,QAAQ,CAAC,kBAAkB,EAAE,MAAM,CAAC;QACpC,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;QAClC,QAAQ,CAAC,iBAAiB,EAAE,MAAM,CAAC;KACnC,CAAC;IACF,QAAQ,CAAC,OAAO,EAAE,SAAS,kBAAkB,EAAE,CAAC;IAChD,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,QAAQ,EAAE;QAClB,QAAQ,CAAC,WAAW,EAAE,oBAAoB,CAAC,aAAa,CAAC,CAAC;QAC1D,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;QACjC,QAAQ,CAAC,2BAA2B,EAAE,MAAM,GAAG,IAAI,CAAC;QACpD,QAAQ,CAAC,yBAAyB,EAAE,MAAM,GAAG,IAAI,CAAC;QAClD,QAAQ,CAAC,oBAAoB,EAAE,MAAM,GAAG,IAAI,CAAC;QAC7C,QAAQ,CAAC,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;QAC1C,QAAQ,CAAC,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAC;QAC3C,QAAQ,CAAC,qBAAqB,EAAE,MAAM,GAAG,IAAI,CAAC;QAC9C,QAAQ,CAAC,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;QACtC,QAAQ,CAAC,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;QACzC,QAAQ,CAAC,WAAW,EAAE,SAAS,MAAM,EAAE,CAAC;KACxC,CAAC;IACF,QAAQ,CAAC,cAAc,EAAE,SAAS,MAAM,EAAE,CAAC;CAC3C;AAED,wBAAgB,yBAAyB,CAAC,KAAK,EAAE;IAChD,QAAQ,CAAC,QAAQ,EAAE,oBAAoB,CAAC;IACxC,QAAQ,CAAC,QAAQ,EAAE,yBAAyB,CAAC;IAC7C,QAAQ,CAAC,QAAQ,EAAE,gCAAgC,CAAC;IACpD,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;CACjC,GAAG,oBAAoB,CA+BvB","sourcesContent":["import type { HostResourceSnapshot } from \"./host-resource-snapshot.ts\";\nimport { effectiveAvailableMemoryBytes } from \"./host-resource-snapshot.ts\";\nimport type { ResourceAdmissionDecision, ResourceGovernorMode, ResourceReasonCode } from \"./resource-admission.ts\";\nimport type { ResolvedResourceGovernorSettings } from \"./resource-governor-settings.ts\";\n\nconst REASON_LABELS: Record<ResourceReasonCode, string> = {\n\t\"resource.memory.low\": \"memory low\",\n\t\"resource.memory.critical\": \"memory critical\",\n\t\"resource.disk.low\": \"disk low\",\n\t\"resource.disk.critical\": \"disk critical\",\n\t\"resource.cpu.busy\": \"CPU busy\",\n\t\"resource.heap.high\": \"heap high\",\n\t\"resource.heap.critical\": \"heap critical\",\n\t\"resource.probe.partial\": \"partial probe\",\n\t\"resource.probe.timeout\": \"probe timeout\",\n};\n\n/** Humanize reason codes for the §19.1 summary line (\"memory low, CPU busy\"). */\nexport function describeResourceReasons(reasons: readonly ResourceReasonCode[]): string {\n\tif (reasons.length === 0) {\n\t\treturn \"none\";\n\t}\n\treturn reasons.map((reason) => REASON_LABELS[reason] ?? reason).join(\", \");\n}\n\n/** §19.1 summary block shared by `/resource` and the doctor's human output. */\nexport function formatResourceSummaryLines(mode: ResourceGovernorMode, decision: ResourceAdmissionDecision): string[] {\n\treturn [\n\t\t`mode: ${mode}`,\n\t\t`pressure: ${decision.pressure}`,\n\t\t`action: ${decision.action}`,\n\t\t`tool concurrency: ${decision.maxToolConcurrency}`,\n\t\t`subagent lanes: ${decision.maxParallelLanes}`,\n\t\t`heavy processes: ${decision.maxHeavyProcesses}`,\n\t\t`reasons: ${describeResourceReasons(decision.reasons)}`,\n\t];\n}\n\n/**\n * Verbose probe details for explicit local diagnostics only (§19.1 verbose,\n * §11.3: raw values never enter tool results or model prompts).\n */\nexport function formatResourceProbeLines(snapshot: HostResourceSnapshot): string[] {\n\tconst heapRatio = snapshot.heapLimitBytes > 0 ? snapshot.heapUsedBytes / snapshot.heapLimitBytes : null;\n\tconst lines = [\n\t\t`observed at: ${snapshot.observedAt}`,\n\t\t`environment: ${snapshot.environment} (${snapshot.platform}/${snapshot.arch}, ${snapshot.logicalCpuCount} CPUs)`,\n\t\t`effective available memory: ${formatMiB(effectiveAvailableMemoryBytes(snapshot))}`,\n\t\t` process available: ${formatMiB(snapshot.processAvailableMemoryBytes)}`,\n\t\t` constrained limit: ${formatMiB(snapshot.constrainedMemoryBytes)}`,\n\t\t` host free: ${formatMiB(snapshot.hostFreeMemoryBytes)} of ${formatMiB(snapshot.hostTotalMemoryBytes)}`,\n\t\t`workspace disk available: ${formatMiB(snapshot.workspaceAvailableBytes)}`,\n\t\t`heap: ${formatMiB(snapshot.heapUsedBytes)} of ${formatMiB(snapshot.heapLimitBytes)}${heapRatio === null ? \"\" : ` (${(heapRatio * 100).toFixed(1)}%)`}`,\n\t\t`system CPU: ${snapshot.systemCpuPercent === null ? \"unknown\" : `${snapshot.systemCpuPercent.toFixed(1)}%`}`,\n\t];\n\tif (snapshot.diagnostics.length > 0) {\n\t\tlines.push(`diagnostics: ${snapshot.diagnostics.join(\", \")}`);\n\t}\n\treturn lines;\n}\n\n/** Policy view: mode, §6.7 thresholds, §7.2 caps, and §18.1 validation errors. */\nexport function formatResourcePolicyLines(resolved: ResolvedResourceGovernorSettings): string[] {\n\tconst { thresholds, caps } = resolved.admission;\n\tconst lines = [\n\t\t`mode: ${resolved.mode}`,\n\t\t`probe deadline: ${resolved.maxProbeMs ?? 300} ms, CPU sample: ${resolved.cpuSampleMs ?? 180} ms`,\n\t\t`memory MiB (constrained/critical): ${thresholds.constrainedAvailableMemoryMiB}/${thresholds.criticalAvailableMemoryMiB}`,\n\t\t`disk MiB (constrained/critical): ${thresholds.constrainedDiskFreeMiB}/${thresholds.criticalDiskFreeMiB}`,\n\t\t`heap ratio (constrained/critical): ${thresholds.constrainedHeapRatio}/${thresholds.criticalHeapRatio}`,\n\t\t`busy CPU percent: ${thresholds.busyCpuPercent}`,\n\t\t`caps normal: tools ${caps.normal.maxToolConcurrency}, lanes ${caps.normal.maxParallelLanes}, heavy ${caps.normal.maxHeavyProcesses}`,\n\t\t`caps constrained: tools ${caps.constrained.maxToolConcurrency}, lanes ${caps.constrained.maxParallelLanes}, heavy ${caps.constrained.maxHeavyProcesses}`,\n\t\t`caps critical: tools ${caps.critical.maxToolConcurrency}, lanes ${caps.critical.maxParallelLanes}, heavy ${caps.critical.maxHeavyProcesses}`,\n\t];\n\tfor (const error of resolved.errors) {\n\t\tlines.push(`error: ${error}`);\n\t}\n\treturn lines;\n}\n\n/** Bounded JSON schema for `omk doctor resources --json` (§19.2). */\nexport interface ResourceDoctorReport {\n\treadonly schemaVersion: 1;\n\treadonly command: \"resource_doctor\";\n\treadonly mode: ResourceGovernorMode;\n\treadonly pressure: ResourceAdmissionDecision[\"pressure\"];\n\treadonly action: ResourceAdmissionDecision[\"action\"];\n\treadonly caps: {\n\t\treadonly maxToolConcurrency: number;\n\t\treadonly maxParallelLanes: number;\n\t\treadonly maxHeavyProcesses: number;\n\t};\n\treadonly reasons: readonly ResourceReasonCode[];\n\treadonly probeDurationMs: number;\n\treadonly snapshot: {\n\t\treadonly environment: HostResourceSnapshot[\"environment\"];\n\t\treadonly logicalCpuCount: number;\n\t\treadonly effectiveAvailableMemoryMiB: number | null;\n\t\treadonly processAvailableMemoryMiB: number | null;\n\t\treadonly constrainedMemoryMiB: number | null;\n\t\treadonly hostFreeMemoryMiB: number | null;\n\t\treadonly hostTotalMemoryMiB: number | null;\n\t\treadonly workspaceAvailableMiB: number | null;\n\t\treadonly heapUsedRatio: number | null;\n\t\treadonly systemCpuPercent: number | null;\n\t\treadonly diagnostics: readonly string[];\n\t};\n\treadonly settingsErrors: readonly string[];\n}\n\nexport function buildResourceDoctorReport(input: {\n\treadonly snapshot: HostResourceSnapshot;\n\treadonly decision: ResourceAdmissionDecision;\n\treadonly resolved: ResolvedResourceGovernorSettings;\n\treadonly probeDurationMs: number;\n}): ResourceDoctorReport {\n\tconst { snapshot, decision, resolved } = input;\n\tconst heapRatio = snapshot.heapLimitBytes > 0 ? snapshot.heapUsedBytes / snapshot.heapLimitBytes : null;\n\treturn {\n\t\tschemaVersion: 1,\n\t\tcommand: \"resource_doctor\",\n\t\tmode: resolved.mode,\n\t\tpressure: decision.pressure,\n\t\taction: decision.action,\n\t\tcaps: {\n\t\t\tmaxToolConcurrency: decision.maxToolConcurrency,\n\t\t\tmaxParallelLanes: decision.maxParallelLanes,\n\t\t\tmaxHeavyProcesses: decision.maxHeavyProcesses,\n\t\t},\n\t\treasons: decision.reasons,\n\t\tprobeDurationMs: Math.round(input.probeDurationMs),\n\t\tsnapshot: {\n\t\t\tenvironment: snapshot.environment,\n\t\t\tlogicalCpuCount: snapshot.logicalCpuCount,\n\t\t\teffectiveAvailableMemoryMiB: toMiB(effectiveAvailableMemoryBytes(snapshot)),\n\t\t\tprocessAvailableMemoryMiB: toMiB(snapshot.processAvailableMemoryBytes),\n\t\t\tconstrainedMemoryMiB: toMiB(snapshot.constrainedMemoryBytes),\n\t\t\thostFreeMemoryMiB: toMiB(snapshot.hostFreeMemoryBytes),\n\t\t\thostTotalMemoryMiB: toMiB(snapshot.hostTotalMemoryBytes),\n\t\t\tworkspaceAvailableMiB: toMiB(snapshot.workspaceAvailableBytes),\n\t\t\theapUsedRatio: heapRatio === null ? null : Number(heapRatio.toFixed(4)),\n\t\t\tsystemCpuPercent: snapshot.systemCpuPercent,\n\t\t\tdiagnostics: snapshot.diagnostics,\n\t\t},\n\t\tsettingsErrors: resolved.errors,\n\t};\n}\n\nconst MIB = 1024 * 1024;\n\nfunction toMiB(bytes: number | null): number | null {\n\treturn bytes === null ? null : Math.round(bytes / MIB);\n}\n\nfunction formatMiB(bytes: number | null): string {\n\tconst mib = toMiB(bytes);\n\treturn mib === null ? \"unknown\" : `${mib.toLocaleString()} MiB`;\n}\n"]}
@@ -0,0 +1,113 @@
1
+ import { effectiveAvailableMemoryBytes } from "./host-resource-snapshot.js";
2
+ const REASON_LABELS = {
3
+ "resource.memory.low": "memory low",
4
+ "resource.memory.critical": "memory critical",
5
+ "resource.disk.low": "disk low",
6
+ "resource.disk.critical": "disk critical",
7
+ "resource.cpu.busy": "CPU busy",
8
+ "resource.heap.high": "heap high",
9
+ "resource.heap.critical": "heap critical",
10
+ "resource.probe.partial": "partial probe",
11
+ "resource.probe.timeout": "probe timeout",
12
+ };
13
+ /** Humanize reason codes for the §19.1 summary line ("memory low, CPU busy"). */
14
+ export function describeResourceReasons(reasons) {
15
+ if (reasons.length === 0) {
16
+ return "none";
17
+ }
18
+ return reasons.map((reason) => REASON_LABELS[reason] ?? reason).join(", ");
19
+ }
20
+ /** §19.1 summary block shared by `/resource` and the doctor's human output. */
21
+ export function formatResourceSummaryLines(mode, decision) {
22
+ return [
23
+ `mode: ${mode}`,
24
+ `pressure: ${decision.pressure}`,
25
+ `action: ${decision.action}`,
26
+ `tool concurrency: ${decision.maxToolConcurrency}`,
27
+ `subagent lanes: ${decision.maxParallelLanes}`,
28
+ `heavy processes: ${decision.maxHeavyProcesses}`,
29
+ `reasons: ${describeResourceReasons(decision.reasons)}`,
30
+ ];
31
+ }
32
+ /**
33
+ * Verbose probe details for explicit local diagnostics only (§19.1 verbose,
34
+ * §11.3: raw values never enter tool results or model prompts).
35
+ */
36
+ export function formatResourceProbeLines(snapshot) {
37
+ const heapRatio = snapshot.heapLimitBytes > 0 ? snapshot.heapUsedBytes / snapshot.heapLimitBytes : null;
38
+ const lines = [
39
+ `observed at: ${snapshot.observedAt}`,
40
+ `environment: ${snapshot.environment} (${snapshot.platform}/${snapshot.arch}, ${snapshot.logicalCpuCount} CPUs)`,
41
+ `effective available memory: ${formatMiB(effectiveAvailableMemoryBytes(snapshot))}`,
42
+ ` process available: ${formatMiB(snapshot.processAvailableMemoryBytes)}`,
43
+ ` constrained limit: ${formatMiB(snapshot.constrainedMemoryBytes)}`,
44
+ ` host free: ${formatMiB(snapshot.hostFreeMemoryBytes)} of ${formatMiB(snapshot.hostTotalMemoryBytes)}`,
45
+ `workspace disk available: ${formatMiB(snapshot.workspaceAvailableBytes)}`,
46
+ `heap: ${formatMiB(snapshot.heapUsedBytes)} of ${formatMiB(snapshot.heapLimitBytes)}${heapRatio === null ? "" : ` (${(heapRatio * 100).toFixed(1)}%)`}`,
47
+ `system CPU: ${snapshot.systemCpuPercent === null ? "unknown" : `${snapshot.systemCpuPercent.toFixed(1)}%`}`,
48
+ ];
49
+ if (snapshot.diagnostics.length > 0) {
50
+ lines.push(`diagnostics: ${snapshot.diagnostics.join(", ")}`);
51
+ }
52
+ return lines;
53
+ }
54
+ /** Policy view: mode, §6.7 thresholds, §7.2 caps, and §18.1 validation errors. */
55
+ export function formatResourcePolicyLines(resolved) {
56
+ const { thresholds, caps } = resolved.admission;
57
+ const lines = [
58
+ `mode: ${resolved.mode}`,
59
+ `probe deadline: ${resolved.maxProbeMs ?? 300} ms, CPU sample: ${resolved.cpuSampleMs ?? 180} ms`,
60
+ `memory MiB (constrained/critical): ${thresholds.constrainedAvailableMemoryMiB}/${thresholds.criticalAvailableMemoryMiB}`,
61
+ `disk MiB (constrained/critical): ${thresholds.constrainedDiskFreeMiB}/${thresholds.criticalDiskFreeMiB}`,
62
+ `heap ratio (constrained/critical): ${thresholds.constrainedHeapRatio}/${thresholds.criticalHeapRatio}`,
63
+ `busy CPU percent: ${thresholds.busyCpuPercent}`,
64
+ `caps normal: tools ${caps.normal.maxToolConcurrency}, lanes ${caps.normal.maxParallelLanes}, heavy ${caps.normal.maxHeavyProcesses}`,
65
+ `caps constrained: tools ${caps.constrained.maxToolConcurrency}, lanes ${caps.constrained.maxParallelLanes}, heavy ${caps.constrained.maxHeavyProcesses}`,
66
+ `caps critical: tools ${caps.critical.maxToolConcurrency}, lanes ${caps.critical.maxParallelLanes}, heavy ${caps.critical.maxHeavyProcesses}`,
67
+ ];
68
+ for (const error of resolved.errors) {
69
+ lines.push(`error: ${error}`);
70
+ }
71
+ return lines;
72
+ }
73
+ export function buildResourceDoctorReport(input) {
74
+ const { snapshot, decision, resolved } = input;
75
+ const heapRatio = snapshot.heapLimitBytes > 0 ? snapshot.heapUsedBytes / snapshot.heapLimitBytes : null;
76
+ return {
77
+ schemaVersion: 1,
78
+ command: "resource_doctor",
79
+ mode: resolved.mode,
80
+ pressure: decision.pressure,
81
+ action: decision.action,
82
+ caps: {
83
+ maxToolConcurrency: decision.maxToolConcurrency,
84
+ maxParallelLanes: decision.maxParallelLanes,
85
+ maxHeavyProcesses: decision.maxHeavyProcesses,
86
+ },
87
+ reasons: decision.reasons,
88
+ probeDurationMs: Math.round(input.probeDurationMs),
89
+ snapshot: {
90
+ environment: snapshot.environment,
91
+ logicalCpuCount: snapshot.logicalCpuCount,
92
+ effectiveAvailableMemoryMiB: toMiB(effectiveAvailableMemoryBytes(snapshot)),
93
+ processAvailableMemoryMiB: toMiB(snapshot.processAvailableMemoryBytes),
94
+ constrainedMemoryMiB: toMiB(snapshot.constrainedMemoryBytes),
95
+ hostFreeMemoryMiB: toMiB(snapshot.hostFreeMemoryBytes),
96
+ hostTotalMemoryMiB: toMiB(snapshot.hostTotalMemoryBytes),
97
+ workspaceAvailableMiB: toMiB(snapshot.workspaceAvailableBytes),
98
+ heapUsedRatio: heapRatio === null ? null : Number(heapRatio.toFixed(4)),
99
+ systemCpuPercent: snapshot.systemCpuPercent,
100
+ diagnostics: snapshot.diagnostics,
101
+ },
102
+ settingsErrors: resolved.errors,
103
+ };
104
+ }
105
+ const MIB = 1024 * 1024;
106
+ function toMiB(bytes) {
107
+ return bytes === null ? null : Math.round(bytes / MIB);
108
+ }
109
+ function formatMiB(bytes) {
110
+ const mib = toMiB(bytes);
111
+ return mib === null ? "unknown" : `${mib.toLocaleString()} MiB`;
112
+ }
113
+ //# sourceMappingURL=resource-governor-format.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resource-governor-format.js","sourceRoot":"","sources":["../../src/core/resource-governor-format.ts"],"names":[],"mappings":"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","sourcesContent":["import type { HostResourceSnapshot } from \"./host-resource-snapshot.ts\";\nimport { effectiveAvailableMemoryBytes } from \"./host-resource-snapshot.ts\";\nimport type { ResourceAdmissionDecision, ResourceGovernorMode, ResourceReasonCode } from \"./resource-admission.ts\";\nimport type { ResolvedResourceGovernorSettings } from \"./resource-governor-settings.ts\";\n\nconst REASON_LABELS: Record<ResourceReasonCode, string> = {\n\t\"resource.memory.low\": \"memory low\",\n\t\"resource.memory.critical\": \"memory critical\",\n\t\"resource.disk.low\": \"disk low\",\n\t\"resource.disk.critical\": \"disk critical\",\n\t\"resource.cpu.busy\": \"CPU busy\",\n\t\"resource.heap.high\": \"heap high\",\n\t\"resource.heap.critical\": \"heap critical\",\n\t\"resource.probe.partial\": \"partial probe\",\n\t\"resource.probe.timeout\": \"probe timeout\",\n};\n\n/** Humanize reason codes for the §19.1 summary line (\"memory low, CPU busy\"). */\nexport function describeResourceReasons(reasons: readonly ResourceReasonCode[]): string {\n\tif (reasons.length === 0) {\n\t\treturn \"none\";\n\t}\n\treturn reasons.map((reason) => REASON_LABELS[reason] ?? reason).join(\", \");\n}\n\n/** §19.1 summary block shared by `/resource` and the doctor's human output. */\nexport function formatResourceSummaryLines(mode: ResourceGovernorMode, decision: ResourceAdmissionDecision): string[] {\n\treturn [\n\t\t`mode: ${mode}`,\n\t\t`pressure: ${decision.pressure}`,\n\t\t`action: ${decision.action}`,\n\t\t`tool concurrency: ${decision.maxToolConcurrency}`,\n\t\t`subagent lanes: ${decision.maxParallelLanes}`,\n\t\t`heavy processes: ${decision.maxHeavyProcesses}`,\n\t\t`reasons: ${describeResourceReasons(decision.reasons)}`,\n\t];\n}\n\n/**\n * Verbose probe details for explicit local diagnostics only (§19.1 verbose,\n * §11.3: raw values never enter tool results or model prompts).\n */\nexport function formatResourceProbeLines(snapshot: HostResourceSnapshot): string[] {\n\tconst heapRatio = snapshot.heapLimitBytes > 0 ? snapshot.heapUsedBytes / snapshot.heapLimitBytes : null;\n\tconst lines = [\n\t\t`observed at: ${snapshot.observedAt}`,\n\t\t`environment: ${snapshot.environment} (${snapshot.platform}/${snapshot.arch}, ${snapshot.logicalCpuCount} CPUs)`,\n\t\t`effective available memory: ${formatMiB(effectiveAvailableMemoryBytes(snapshot))}`,\n\t\t` process available: ${formatMiB(snapshot.processAvailableMemoryBytes)}`,\n\t\t` constrained limit: ${formatMiB(snapshot.constrainedMemoryBytes)}`,\n\t\t` host free: ${formatMiB(snapshot.hostFreeMemoryBytes)} of ${formatMiB(snapshot.hostTotalMemoryBytes)}`,\n\t\t`workspace disk available: ${formatMiB(snapshot.workspaceAvailableBytes)}`,\n\t\t`heap: ${formatMiB(snapshot.heapUsedBytes)} of ${formatMiB(snapshot.heapLimitBytes)}${heapRatio === null ? \"\" : ` (${(heapRatio * 100).toFixed(1)}%)`}`,\n\t\t`system CPU: ${snapshot.systemCpuPercent === null ? \"unknown\" : `${snapshot.systemCpuPercent.toFixed(1)}%`}`,\n\t];\n\tif (snapshot.diagnostics.length > 0) {\n\t\tlines.push(`diagnostics: ${snapshot.diagnostics.join(\", \")}`);\n\t}\n\treturn lines;\n}\n\n/** Policy view: mode, §6.7 thresholds, §7.2 caps, and §18.1 validation errors. */\nexport function formatResourcePolicyLines(resolved: ResolvedResourceGovernorSettings): string[] {\n\tconst { thresholds, caps } = resolved.admission;\n\tconst lines = [\n\t\t`mode: ${resolved.mode}`,\n\t\t`probe deadline: ${resolved.maxProbeMs ?? 300} ms, CPU sample: ${resolved.cpuSampleMs ?? 180} ms`,\n\t\t`memory MiB (constrained/critical): ${thresholds.constrainedAvailableMemoryMiB}/${thresholds.criticalAvailableMemoryMiB}`,\n\t\t`disk MiB (constrained/critical): ${thresholds.constrainedDiskFreeMiB}/${thresholds.criticalDiskFreeMiB}`,\n\t\t`heap ratio (constrained/critical): ${thresholds.constrainedHeapRatio}/${thresholds.criticalHeapRatio}`,\n\t\t`busy CPU percent: ${thresholds.busyCpuPercent}`,\n\t\t`caps normal: tools ${caps.normal.maxToolConcurrency}, lanes ${caps.normal.maxParallelLanes}, heavy ${caps.normal.maxHeavyProcesses}`,\n\t\t`caps constrained: tools ${caps.constrained.maxToolConcurrency}, lanes ${caps.constrained.maxParallelLanes}, heavy ${caps.constrained.maxHeavyProcesses}`,\n\t\t`caps critical: tools ${caps.critical.maxToolConcurrency}, lanes ${caps.critical.maxParallelLanes}, heavy ${caps.critical.maxHeavyProcesses}`,\n\t];\n\tfor (const error of resolved.errors) {\n\t\tlines.push(`error: ${error}`);\n\t}\n\treturn lines;\n}\n\n/** Bounded JSON schema for `omk doctor resources --json` (§19.2). */\nexport interface ResourceDoctorReport {\n\treadonly schemaVersion: 1;\n\treadonly command: \"resource_doctor\";\n\treadonly mode: ResourceGovernorMode;\n\treadonly pressure: ResourceAdmissionDecision[\"pressure\"];\n\treadonly action: ResourceAdmissionDecision[\"action\"];\n\treadonly caps: {\n\t\treadonly maxToolConcurrency: number;\n\t\treadonly maxParallelLanes: number;\n\t\treadonly maxHeavyProcesses: number;\n\t};\n\treadonly reasons: readonly ResourceReasonCode[];\n\treadonly probeDurationMs: number;\n\treadonly snapshot: {\n\t\treadonly environment: HostResourceSnapshot[\"environment\"];\n\t\treadonly logicalCpuCount: number;\n\t\treadonly effectiveAvailableMemoryMiB: number | null;\n\t\treadonly processAvailableMemoryMiB: number | null;\n\t\treadonly constrainedMemoryMiB: number | null;\n\t\treadonly hostFreeMemoryMiB: number | null;\n\t\treadonly hostTotalMemoryMiB: number | null;\n\t\treadonly workspaceAvailableMiB: number | null;\n\t\treadonly heapUsedRatio: number | null;\n\t\treadonly systemCpuPercent: number | null;\n\t\treadonly diagnostics: readonly string[];\n\t};\n\treadonly settingsErrors: readonly string[];\n}\n\nexport function buildResourceDoctorReport(input: {\n\treadonly snapshot: HostResourceSnapshot;\n\treadonly decision: ResourceAdmissionDecision;\n\treadonly resolved: ResolvedResourceGovernorSettings;\n\treadonly probeDurationMs: number;\n}): ResourceDoctorReport {\n\tconst { snapshot, decision, resolved } = input;\n\tconst heapRatio = snapshot.heapLimitBytes > 0 ? snapshot.heapUsedBytes / snapshot.heapLimitBytes : null;\n\treturn {\n\t\tschemaVersion: 1,\n\t\tcommand: \"resource_doctor\",\n\t\tmode: resolved.mode,\n\t\tpressure: decision.pressure,\n\t\taction: decision.action,\n\t\tcaps: {\n\t\t\tmaxToolConcurrency: decision.maxToolConcurrency,\n\t\t\tmaxParallelLanes: decision.maxParallelLanes,\n\t\t\tmaxHeavyProcesses: decision.maxHeavyProcesses,\n\t\t},\n\t\treasons: decision.reasons,\n\t\tprobeDurationMs: Math.round(input.probeDurationMs),\n\t\tsnapshot: {\n\t\t\tenvironment: snapshot.environment,\n\t\t\tlogicalCpuCount: snapshot.logicalCpuCount,\n\t\t\teffectiveAvailableMemoryMiB: toMiB(effectiveAvailableMemoryBytes(snapshot)),\n\t\t\tprocessAvailableMemoryMiB: toMiB(snapshot.processAvailableMemoryBytes),\n\t\t\tconstrainedMemoryMiB: toMiB(snapshot.constrainedMemoryBytes),\n\t\t\thostFreeMemoryMiB: toMiB(snapshot.hostFreeMemoryBytes),\n\t\t\thostTotalMemoryMiB: toMiB(snapshot.hostTotalMemoryBytes),\n\t\t\tworkspaceAvailableMiB: toMiB(snapshot.workspaceAvailableBytes),\n\t\t\theapUsedRatio: heapRatio === null ? null : Number(heapRatio.toFixed(4)),\n\t\t\tsystemCpuPercent: snapshot.systemCpuPercent,\n\t\t\tdiagnostics: snapshot.diagnostics,\n\t\t},\n\t\tsettingsErrors: resolved.errors,\n\t};\n}\n\nconst MIB = 1024 * 1024;\n\nfunction toMiB(bytes: number | null): number | null {\n\treturn bytes === null ? null : Math.round(bytes / MIB);\n}\n\nfunction formatMiB(bytes: number | null): string {\n\tconst mib = toMiB(bytes);\n\treturn mib === null ? \"unknown\" : `${mib.toLocaleString()} MiB`;\n}\n"]}
@@ -0,0 +1,56 @@
1
+ import type { ResourceAdmissionConfig, ResourceGovernorMode } from "./resource-admission.ts";
2
+ export { buildResourceDoctorReport, describeResourceReasons, formatResourcePolicyLines, formatResourceProbeLines, formatResourceSummaryLines, type ResourceDoctorReport, } from "./resource-governor-format.ts";
3
+ /**
4
+ * Resource governor settings surface (OMK v0.97.x roadmap §18, M1/PR2).
5
+ *
6
+ * Pure translation from the additive `resourceGovernor` settings block (and
7
+ * the §18.2 `OMK_RESOURCE_GOVERNOR` env override) to the layer-1 admission
8
+ * config plus probe options. Validation follows §18.1: explicit errors over
9
+ * silent clamps, and invalid input fails closed to the defaults.
10
+ *
11
+ * Resolved values feed the `/resource` command, `omk doctor resources`, and
12
+ * the M2 prompt preflight: `observe` records decisions only, while
13
+ * `adaptive`/`strict` let the run lease throttle per-run tool concurrency.
14
+ * Lane and heavy-process caps become authoritative in later milestones.
15
+ */
16
+ /** Additive settings block (§18, §29.1: absent block keeps v0.96.1 behavior). */
17
+ export interface ResourceGovernorSettings {
18
+ enabled?: boolean;
19
+ mode?: ResourceGovernorMode;
20
+ maxProbeMs?: number;
21
+ cpuSampleMs?: number;
22
+ constrainedAvailableMemoryMiB?: number;
23
+ criticalAvailableMemoryMiB?: number;
24
+ constrainedDiskFreeMiB?: number;
25
+ criticalDiskFreeMiB?: number;
26
+ constrainedHeapRatio?: number;
27
+ criticalHeapRatio?: number;
28
+ busyCpuPercent?: number;
29
+ normalMaxToolConcurrency?: number;
30
+ constrainedMaxToolConcurrency?: number;
31
+ criticalMaxToolConcurrency?: number;
32
+ normalMaxParallelLanes?: number;
33
+ constrainedMaxParallelLanes?: number;
34
+ criticalMaxParallelLanes?: number;
35
+ normalMaxHeavyProcesses?: number;
36
+ constrainedMaxHeavyProcesses?: number;
37
+ criticalMaxHeavyProcesses?: number;
38
+ }
39
+ export declare const RESOURCE_GOVERNOR_MODE_ENV = "OMK_RESOURCE_GOVERNOR";
40
+ export declare const DEFAULT_RESOURCE_GOVERNOR_MODE: ResourceGovernorMode;
41
+ export interface ResolvedResourceGovernorSettings {
42
+ readonly mode: ResourceGovernorMode;
43
+ /** Only present when the setting is valid; the probe applies its own defaults otherwise. */
44
+ readonly maxProbeMs?: number;
45
+ readonly cpuSampleMs?: number;
46
+ readonly admission: ResourceAdmissionConfig;
47
+ /** §18.1 explicit validation errors; non-empty means defaults were applied for the failed area. */
48
+ readonly errors: readonly string[];
49
+ }
50
+ /**
51
+ * Resolve settings + environment into governor mode, probe options, and the
52
+ * admission config. Never throws; every invalid field produces an explicit
53
+ * error and falls back to the corresponding default (§18.1 fail-closed).
54
+ */
55
+ export declare function resolveResourceGovernorSettings(settings?: ResourceGovernorSettings, env?: NodeJS.ProcessEnv): ResolvedResourceGovernorSettings;
56
+ //# sourceMappingURL=resource-governor-settings.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resource-governor-settings.d.ts","sourceRoot":"","sources":["../../src/core/resource-governor-settings.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACX,uBAAuB,EAEvB,oBAAoB,EACpB,MAAM,yBAAyB,CAAC;AAIjC,OAAO,EACN,yBAAyB,EACzB,uBAAuB,EACvB,yBAAyB,EACzB,wBAAwB,EACxB,0BAA0B,EAC1B,KAAK,oBAAoB,GACzB,MAAM,+BAA+B,CAAC;AAEvC;;;;;;;;;;;;GAYG;AAEH,mFAAiF;AACjF,MAAM,WAAW,wBAAwB;IACxC,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,IAAI,CAAC,EAAE,oBAAoB,CAAC;IAE5B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB,6BAA6B,CAAC,EAAE,MAAM,CAAC;IACvC,0BAA0B,CAAC,EAAE,MAAM,CAAC;IACpC,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,cAAc,CAAC,EAAE,MAAM,CAAC;IAExB,wBAAwB,CAAC,EAAE,MAAM,CAAC;IAClC,6BAA6B,CAAC,EAAE,MAAM,CAAC;IACvC,0BAA0B,CAAC,EAAE,MAAM,CAAC;IACpC,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,2BAA2B,CAAC,EAAE,MAAM,CAAC;IACrC,wBAAwB,CAAC,EAAE,MAAM,CAAC;IAClC,uBAAuB,CAAC,EAAE,MAAM,CAAC;IACjC,4BAA4B,CAAC,EAAE,MAAM,CAAC;IACtC,yBAAyB,CAAC,EAAE,MAAM,CAAC;CACnC;AAED,eAAO,MAAM,0BAA0B,0BAA0B,CAAC;AAClE,eAAO,MAAM,8BAA8B,EAAE,oBAAgC,CAAC;AAG9E,MAAM,WAAW,gCAAgC;IAChD,QAAQ,CAAC,IAAI,EAAE,oBAAoB,CAAC;IACpC,4FAA4F;IAC5F,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,SAAS,EAAE,uBAAuB,CAAC;IAC5C,oGAAmG;IACnG,QAAQ,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;CACnC;AAED;;;;GAIG;AACH,wBAAgB,+BAA+B,CAC9C,QAAQ,CAAC,EAAE,wBAAwB,EACnC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAClC,gCAAgC,CA6ClC","sourcesContent":["import type {\n\tResourceAdmissionConfig,\n\tResourceAdmissionConfigOverrides,\n\tResourceGovernorMode,\n} from \"./resource-admission.ts\";\nimport { resolveResourceAdmissionConfig } from \"./resource-admission.ts\";\nimport { MAX_CPU_SAMPLE_MS, MIN_CPU_SAMPLE_MS } from \"./system-cpu-sampler.ts\";\n\nexport {\n\tbuildResourceDoctorReport,\n\tdescribeResourceReasons,\n\tformatResourcePolicyLines,\n\tformatResourceProbeLines,\n\tformatResourceSummaryLines,\n\ttype ResourceDoctorReport,\n} from \"./resource-governor-format.ts\";\n\n/**\n * Resource governor settings surface (OMK v0.97.x roadmap §18, M1/PR2).\n *\n * Pure translation from the additive `resourceGovernor` settings block (and\n * the §18.2 `OMK_RESOURCE_GOVERNOR` env override) to the layer-1 admission\n * config plus probe options. Validation follows §18.1: explicit errors over\n * silent clamps, and invalid input fails closed to the defaults.\n *\n * Resolved values feed the `/resource` command, `omk doctor resources`, and\n * the M2 prompt preflight: `observe` records decisions only, while\n * `adaptive`/`strict` let the run lease throttle per-run tool concurrency.\n * Lane and heavy-process caps become authoritative in later milestones.\n */\n\n/** Additive settings block (§18, §29.1: absent block keeps v0.96.1 behavior). */\nexport interface ResourceGovernorSettings {\n\tenabled?: boolean; // default: true; false forces mode \"off\"\n\tmode?: ResourceGovernorMode; // default: \"observe\" (v0.97.0-alpha rollout stage, §7.4)\n\n\tmaxProbeMs?: number; // default: 300; async probe deadline, valid 50..5000 (§18.1)\n\tcpuSampleMs?: number; // default: 180; CPU two-sample interval, valid 150..250 (§6.3)\n\n\tconstrainedAvailableMemoryMiB?: number; // default: 1536 (§6.7)\n\tcriticalAvailableMemoryMiB?: number; // default: 512\n\tconstrainedDiskFreeMiB?: number; // default: 4096\n\tcriticalDiskFreeMiB?: number; // default: 1024\n\tconstrainedHeapRatio?: number; // default: 0.75\n\tcriticalHeapRatio?: number; // default: 0.85\n\tbusyCpuPercent?: number; // default: 85\n\n\tnormalMaxToolConcurrency?: number; // default: 4 (§7.2)\n\tconstrainedMaxToolConcurrency?: number; // default: 2\n\tcriticalMaxToolConcurrency?: number; // default: 1\n\tnormalMaxParallelLanes?: number; // default: 4\n\tconstrainedMaxParallelLanes?: number; // default: 2\n\tcriticalMaxParallelLanes?: number; // default: 1\n\tnormalMaxHeavyProcesses?: number; // default: 2\n\tconstrainedMaxHeavyProcesses?: number; // default: 1\n\tcriticalMaxHeavyProcesses?: number; // default: 1\n}\n\nexport const RESOURCE_GOVERNOR_MODE_ENV = \"OMK_RESOURCE_GOVERNOR\";\nexport const DEFAULT_RESOURCE_GOVERNOR_MODE: ResourceGovernorMode = \"observe\";\nconst RESOURCE_GOVERNOR_MODES: ReadonlySet<string> = new Set([\"off\", \"observe\", \"adaptive\", \"strict\"]);\n\nexport interface ResolvedResourceGovernorSettings {\n\treadonly mode: ResourceGovernorMode;\n\t/** Only present when the setting is valid; the probe applies its own defaults otherwise. */\n\treadonly maxProbeMs?: number;\n\treadonly cpuSampleMs?: number;\n\treadonly admission: ResourceAdmissionConfig;\n\t/** §18.1 explicit validation errors; non-empty means defaults were applied for the failed area. */\n\treadonly errors: readonly string[];\n}\n\n/**\n * Resolve settings + environment into governor mode, probe options, and the\n * admission config. Never throws; every invalid field produces an explicit\n * error and falls back to the corresponding default (§18.1 fail-closed).\n */\nexport function resolveResourceGovernorSettings(\n\tsettings?: ResourceGovernorSettings,\n\tenv: NodeJS.ProcessEnv = process.env,\n): ResolvedResourceGovernorSettings {\n\tconst errors: string[] = [];\n\tconst mode = resolveMode(settings, env, errors);\n\n\tconst maxProbeMs = validateRange(settings?.maxProbeMs, \"maxProbeMs\", 50, 5000, errors);\n\tconst cpuSampleMs = validateRange(\n\t\tsettings?.cpuSampleMs,\n\t\t\"cpuSampleMs\",\n\t\tMIN_CPU_SAMPLE_MS,\n\t\tMAX_CPU_SAMPLE_MS,\n\t\terrors,\n\t);\n\n\tconst overrides: ResourceAdmissionConfigOverrides = {\n\t\tthresholds: pruneUndefined({\n\t\t\tconstrainedAvailableMemoryMiB: settings?.constrainedAvailableMemoryMiB,\n\t\t\tcriticalAvailableMemoryMiB: settings?.criticalAvailableMemoryMiB,\n\t\t\tconstrainedDiskFreeMiB: settings?.constrainedDiskFreeMiB,\n\t\t\tcriticalDiskFreeMiB: settings?.criticalDiskFreeMiB,\n\t\t\tconstrainedHeapRatio: settings?.constrainedHeapRatio,\n\t\t\tcriticalHeapRatio: settings?.criticalHeapRatio,\n\t\t\tbusyCpuPercent: settings?.busyCpuPercent,\n\t\t}),\n\t\tcaps: {\n\t\t\tnormal: pruneUndefined({\n\t\t\t\tmaxToolConcurrency: settings?.normalMaxToolConcurrency,\n\t\t\t\tmaxParallelLanes: settings?.normalMaxParallelLanes,\n\t\t\t\tmaxHeavyProcesses: settings?.normalMaxHeavyProcesses,\n\t\t\t}),\n\t\t\tconstrained: pruneUndefined({\n\t\t\t\tmaxToolConcurrency: settings?.constrainedMaxToolConcurrency,\n\t\t\t\tmaxParallelLanes: settings?.constrainedMaxParallelLanes,\n\t\t\t\tmaxHeavyProcesses: settings?.constrainedMaxHeavyProcesses,\n\t\t\t}),\n\t\t\tcritical: pruneUndefined({\n\t\t\t\tmaxToolConcurrency: settings?.criticalMaxToolConcurrency,\n\t\t\t\tmaxParallelLanes: settings?.criticalMaxParallelLanes,\n\t\t\t\tmaxHeavyProcesses: settings?.criticalMaxHeavyProcesses,\n\t\t\t}),\n\t\t},\n\t};\n\tconst admission = resolveResourceAdmissionConfig(overrides);\n\terrors.push(...admission.errors);\n\n\treturn { mode, maxProbeMs, cpuSampleMs, admission: admission.config, errors };\n}\n\nfunction resolveMode(\n\tsettings: ResourceGovernorSettings | undefined,\n\tenv: NodeJS.ProcessEnv,\n\terrors: string[],\n): ResourceGovernorMode {\n\t// §18.2/§30.1: the env override is the operator kill switch and wins.\n\tconst envMode = env[RESOURCE_GOVERNOR_MODE_ENV]?.trim();\n\tif (envMode !== undefined && envMode !== \"\") {\n\t\tif (RESOURCE_GOVERNOR_MODES.has(envMode)) {\n\t\t\treturn envMode as ResourceGovernorMode;\n\t\t}\n\t\terrors.push(`resourceGovernor.mode: invalid ${RESOURCE_GOVERNOR_MODE_ENV} value ${JSON.stringify(envMode)}`);\n\t}\n\tif (settings?.enabled === false) {\n\t\treturn \"off\";\n\t}\n\tconst configured = settings?.mode;\n\tif (configured !== undefined) {\n\t\tif (RESOURCE_GOVERNOR_MODES.has(configured)) {\n\t\t\treturn configured;\n\t\t}\n\t\terrors.push(`resourceGovernor.mode: invalid value ${JSON.stringify(configured)}`);\n\t}\n\treturn DEFAULT_RESOURCE_GOVERNOR_MODE;\n}\n\nfunction validateRange(\n\tvalue: number | undefined,\n\tfield: string,\n\tmin: number,\n\tmax: number,\n\terrors: string[],\n): number | undefined {\n\tif (value === undefined) {\n\t\treturn undefined;\n\t}\n\tif (!Number.isSafeInteger(value) || value < min || value > max) {\n\t\terrors.push(`resourceGovernor.${field}: must be an integer within [${min}, ${max}], got ${String(value)}`);\n\t\treturn undefined;\n\t}\n\treturn value;\n}\n\nfunction pruneUndefined<T extends Record<string, number | undefined>>(record: T): Partial<T> {\n\tconst pruned: Record<string, number> = {};\n\tfor (const [key, value] of Object.entries(record)) {\n\t\tif (value !== undefined) {\n\t\t\tpruned[key] = value;\n\t\t}\n\t}\n\treturn pruned as Partial<T>;\n}\n"]}
@@ -0,0 +1,89 @@
1
+ import { resolveResourceAdmissionConfig } from "./resource-admission.js";
2
+ import { MAX_CPU_SAMPLE_MS, MIN_CPU_SAMPLE_MS } from "./system-cpu-sampler.js";
3
+ export { buildResourceDoctorReport, describeResourceReasons, formatResourcePolicyLines, formatResourceProbeLines, formatResourceSummaryLines, } from "./resource-governor-format.js";
4
+ export const RESOURCE_GOVERNOR_MODE_ENV = "OMK_RESOURCE_GOVERNOR";
5
+ export const DEFAULT_RESOURCE_GOVERNOR_MODE = "observe";
6
+ const RESOURCE_GOVERNOR_MODES = new Set(["off", "observe", "adaptive", "strict"]);
7
+ /**
8
+ * Resolve settings + environment into governor mode, probe options, and the
9
+ * admission config. Never throws; every invalid field produces an explicit
10
+ * error and falls back to the corresponding default (§18.1 fail-closed).
11
+ */
12
+ export function resolveResourceGovernorSettings(settings, env = process.env) {
13
+ const errors = [];
14
+ const mode = resolveMode(settings, env, errors);
15
+ const maxProbeMs = validateRange(settings?.maxProbeMs, "maxProbeMs", 50, 5000, errors);
16
+ const cpuSampleMs = validateRange(settings?.cpuSampleMs, "cpuSampleMs", MIN_CPU_SAMPLE_MS, MAX_CPU_SAMPLE_MS, errors);
17
+ const overrides = {
18
+ thresholds: pruneUndefined({
19
+ constrainedAvailableMemoryMiB: settings?.constrainedAvailableMemoryMiB,
20
+ criticalAvailableMemoryMiB: settings?.criticalAvailableMemoryMiB,
21
+ constrainedDiskFreeMiB: settings?.constrainedDiskFreeMiB,
22
+ criticalDiskFreeMiB: settings?.criticalDiskFreeMiB,
23
+ constrainedHeapRatio: settings?.constrainedHeapRatio,
24
+ criticalHeapRatio: settings?.criticalHeapRatio,
25
+ busyCpuPercent: settings?.busyCpuPercent,
26
+ }),
27
+ caps: {
28
+ normal: pruneUndefined({
29
+ maxToolConcurrency: settings?.normalMaxToolConcurrency,
30
+ maxParallelLanes: settings?.normalMaxParallelLanes,
31
+ maxHeavyProcesses: settings?.normalMaxHeavyProcesses,
32
+ }),
33
+ constrained: pruneUndefined({
34
+ maxToolConcurrency: settings?.constrainedMaxToolConcurrency,
35
+ maxParallelLanes: settings?.constrainedMaxParallelLanes,
36
+ maxHeavyProcesses: settings?.constrainedMaxHeavyProcesses,
37
+ }),
38
+ critical: pruneUndefined({
39
+ maxToolConcurrency: settings?.criticalMaxToolConcurrency,
40
+ maxParallelLanes: settings?.criticalMaxParallelLanes,
41
+ maxHeavyProcesses: settings?.criticalMaxHeavyProcesses,
42
+ }),
43
+ },
44
+ };
45
+ const admission = resolveResourceAdmissionConfig(overrides);
46
+ errors.push(...admission.errors);
47
+ return { mode, maxProbeMs, cpuSampleMs, admission: admission.config, errors };
48
+ }
49
+ function resolveMode(settings, env, errors) {
50
+ // §18.2/§30.1: the env override is the operator kill switch and wins.
51
+ const envMode = env[RESOURCE_GOVERNOR_MODE_ENV]?.trim();
52
+ if (envMode !== undefined && envMode !== "") {
53
+ if (RESOURCE_GOVERNOR_MODES.has(envMode)) {
54
+ return envMode;
55
+ }
56
+ errors.push(`resourceGovernor.mode: invalid ${RESOURCE_GOVERNOR_MODE_ENV} value ${JSON.stringify(envMode)}`);
57
+ }
58
+ if (settings?.enabled === false) {
59
+ return "off";
60
+ }
61
+ const configured = settings?.mode;
62
+ if (configured !== undefined) {
63
+ if (RESOURCE_GOVERNOR_MODES.has(configured)) {
64
+ return configured;
65
+ }
66
+ errors.push(`resourceGovernor.mode: invalid value ${JSON.stringify(configured)}`);
67
+ }
68
+ return DEFAULT_RESOURCE_GOVERNOR_MODE;
69
+ }
70
+ function validateRange(value, field, min, max, errors) {
71
+ if (value === undefined) {
72
+ return undefined;
73
+ }
74
+ if (!Number.isSafeInteger(value) || value < min || value > max) {
75
+ errors.push(`resourceGovernor.${field}: must be an integer within [${min}, ${max}], got ${String(value)}`);
76
+ return undefined;
77
+ }
78
+ return value;
79
+ }
80
+ function pruneUndefined(record) {
81
+ const pruned = {};
82
+ for (const [key, value] of Object.entries(record)) {
83
+ if (value !== undefined) {
84
+ pruned[key] = value;
85
+ }
86
+ }
87
+ return pruned;
88
+ }
89
+ //# sourceMappingURL=resource-governor-settings.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resource-governor-settings.js","sourceRoot":"","sources":["../../src/core/resource-governor-settings.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n\tResourceAdmissionConfig,\n\tResourceAdmissionConfigOverrides,\n\tResourceGovernorMode,\n} from \"./resource-admission.ts\";\nimport { resolveResourceAdmissionConfig } from \"./resource-admission.ts\";\nimport { MAX_CPU_SAMPLE_MS, MIN_CPU_SAMPLE_MS } from \"./system-cpu-sampler.ts\";\n\nexport {\n\tbuildResourceDoctorReport,\n\tdescribeResourceReasons,\n\tformatResourcePolicyLines,\n\tformatResourceProbeLines,\n\tformatResourceSummaryLines,\n\ttype ResourceDoctorReport,\n} from \"./resource-governor-format.ts\";\n\n/**\n * Resource governor settings surface (OMK v0.97.x roadmap §18, M1/PR2).\n *\n * Pure translation from the additive `resourceGovernor` settings block (and\n * the §18.2 `OMK_RESOURCE_GOVERNOR` env override) to the layer-1 admission\n * config plus probe options. Validation follows §18.1: explicit errors over\n * silent clamps, and invalid input fails closed to the defaults.\n *\n * Resolved values feed the `/resource` command, `omk doctor resources`, and\n * the M2 prompt preflight: `observe` records decisions only, while\n * `adaptive`/`strict` let the run lease throttle per-run tool concurrency.\n * Lane and heavy-process caps become authoritative in later milestones.\n */\n\n/** Additive settings block (§18, §29.1: absent block keeps v0.96.1 behavior). */\nexport interface ResourceGovernorSettings {\n\tenabled?: boolean; // default: true; false forces mode \"off\"\n\tmode?: ResourceGovernorMode; // default: \"observe\" (v0.97.0-alpha rollout stage, §7.4)\n\n\tmaxProbeMs?: number; // default: 300; async probe deadline, valid 50..5000 (§18.1)\n\tcpuSampleMs?: number; // default: 180; CPU two-sample interval, valid 150..250 (§6.3)\n\n\tconstrainedAvailableMemoryMiB?: number; // default: 1536 (§6.7)\n\tcriticalAvailableMemoryMiB?: number; // default: 512\n\tconstrainedDiskFreeMiB?: number; // default: 4096\n\tcriticalDiskFreeMiB?: number; // default: 1024\n\tconstrainedHeapRatio?: number; // default: 0.75\n\tcriticalHeapRatio?: number; // default: 0.85\n\tbusyCpuPercent?: number; // default: 85\n\n\tnormalMaxToolConcurrency?: number; // default: 4 (§7.2)\n\tconstrainedMaxToolConcurrency?: number; // default: 2\n\tcriticalMaxToolConcurrency?: number; // default: 1\n\tnormalMaxParallelLanes?: number; // default: 4\n\tconstrainedMaxParallelLanes?: number; // default: 2\n\tcriticalMaxParallelLanes?: number; // default: 1\n\tnormalMaxHeavyProcesses?: number; // default: 2\n\tconstrainedMaxHeavyProcesses?: number; // default: 1\n\tcriticalMaxHeavyProcesses?: number; // default: 1\n}\n\nexport const RESOURCE_GOVERNOR_MODE_ENV = \"OMK_RESOURCE_GOVERNOR\";\nexport const DEFAULT_RESOURCE_GOVERNOR_MODE: ResourceGovernorMode = \"observe\";\nconst RESOURCE_GOVERNOR_MODES: ReadonlySet<string> = new Set([\"off\", \"observe\", \"adaptive\", \"strict\"]);\n\nexport interface ResolvedResourceGovernorSettings {\n\treadonly mode: ResourceGovernorMode;\n\t/** Only present when the setting is valid; the probe applies its own defaults otherwise. */\n\treadonly maxProbeMs?: number;\n\treadonly cpuSampleMs?: number;\n\treadonly admission: ResourceAdmissionConfig;\n\t/** §18.1 explicit validation errors; non-empty means defaults were applied for the failed area. */\n\treadonly errors: readonly string[];\n}\n\n/**\n * Resolve settings + environment into governor mode, probe options, and the\n * admission config. Never throws; every invalid field produces an explicit\n * error and falls back to the corresponding default (§18.1 fail-closed).\n */\nexport function resolveResourceGovernorSettings(\n\tsettings?: ResourceGovernorSettings,\n\tenv: NodeJS.ProcessEnv = process.env,\n): ResolvedResourceGovernorSettings {\n\tconst errors: string[] = [];\n\tconst mode = resolveMode(settings, env, errors);\n\n\tconst maxProbeMs = validateRange(settings?.maxProbeMs, \"maxProbeMs\", 50, 5000, errors);\n\tconst cpuSampleMs = validateRange(\n\t\tsettings?.cpuSampleMs,\n\t\t\"cpuSampleMs\",\n\t\tMIN_CPU_SAMPLE_MS,\n\t\tMAX_CPU_SAMPLE_MS,\n\t\terrors,\n\t);\n\n\tconst overrides: ResourceAdmissionConfigOverrides = {\n\t\tthresholds: pruneUndefined({\n\t\t\tconstrainedAvailableMemoryMiB: settings?.constrainedAvailableMemoryMiB,\n\t\t\tcriticalAvailableMemoryMiB: settings?.criticalAvailableMemoryMiB,\n\t\t\tconstrainedDiskFreeMiB: settings?.constrainedDiskFreeMiB,\n\t\t\tcriticalDiskFreeMiB: settings?.criticalDiskFreeMiB,\n\t\t\tconstrainedHeapRatio: settings?.constrainedHeapRatio,\n\t\t\tcriticalHeapRatio: settings?.criticalHeapRatio,\n\t\t\tbusyCpuPercent: settings?.busyCpuPercent,\n\t\t}),\n\t\tcaps: {\n\t\t\tnormal: pruneUndefined({\n\t\t\t\tmaxToolConcurrency: settings?.normalMaxToolConcurrency,\n\t\t\t\tmaxParallelLanes: settings?.normalMaxParallelLanes,\n\t\t\t\tmaxHeavyProcesses: settings?.normalMaxHeavyProcesses,\n\t\t\t}),\n\t\t\tconstrained: pruneUndefined({\n\t\t\t\tmaxToolConcurrency: settings?.constrainedMaxToolConcurrency,\n\t\t\t\tmaxParallelLanes: settings?.constrainedMaxParallelLanes,\n\t\t\t\tmaxHeavyProcesses: settings?.constrainedMaxHeavyProcesses,\n\t\t\t}),\n\t\t\tcritical: pruneUndefined({\n\t\t\t\tmaxToolConcurrency: settings?.criticalMaxToolConcurrency,\n\t\t\t\tmaxParallelLanes: settings?.criticalMaxParallelLanes,\n\t\t\t\tmaxHeavyProcesses: settings?.criticalMaxHeavyProcesses,\n\t\t\t}),\n\t\t},\n\t};\n\tconst admission = resolveResourceAdmissionConfig(overrides);\n\terrors.push(...admission.errors);\n\n\treturn { mode, maxProbeMs, cpuSampleMs, admission: admission.config, errors };\n}\n\nfunction resolveMode(\n\tsettings: ResourceGovernorSettings | undefined,\n\tenv: NodeJS.ProcessEnv,\n\terrors: string[],\n): ResourceGovernorMode {\n\t// §18.2/§30.1: the env override is the operator kill switch and wins.\n\tconst envMode = env[RESOURCE_GOVERNOR_MODE_ENV]?.trim();\n\tif (envMode !== undefined && envMode !== \"\") {\n\t\tif (RESOURCE_GOVERNOR_MODES.has(envMode)) {\n\t\t\treturn envMode as ResourceGovernorMode;\n\t\t}\n\t\terrors.push(`resourceGovernor.mode: invalid ${RESOURCE_GOVERNOR_MODE_ENV} value ${JSON.stringify(envMode)}`);\n\t}\n\tif (settings?.enabled === false) {\n\t\treturn \"off\";\n\t}\n\tconst configured = settings?.mode;\n\tif (configured !== undefined) {\n\t\tif (RESOURCE_GOVERNOR_MODES.has(configured)) {\n\t\t\treturn configured;\n\t\t}\n\t\terrors.push(`resourceGovernor.mode: invalid value ${JSON.stringify(configured)}`);\n\t}\n\treturn DEFAULT_RESOURCE_GOVERNOR_MODE;\n}\n\nfunction validateRange(\n\tvalue: number | undefined,\n\tfield: string,\n\tmin: number,\n\tmax: number,\n\terrors: string[],\n): number | undefined {\n\tif (value === undefined) {\n\t\treturn undefined;\n\t}\n\tif (!Number.isSafeInteger(value) || value < min || value > max) {\n\t\terrors.push(`resourceGovernor.${field}: must be an integer within [${min}, ${max}], got ${String(value)}`);\n\t\treturn undefined;\n\t}\n\treturn value;\n}\n\nfunction pruneUndefined<T extends Record<string, number | undefined>>(record: T): Partial<T> {\n\tconst pruned: Record<string, number> = {};\n\tfor (const [key, value] of Object.entries(record)) {\n\t\tif (value !== undefined) {\n\t\t\tpruned[key] = value;\n\t\t}\n\t}\n\treturn pruned as Partial<T>;\n}\n"]}