open-multi-agent-kit 0.96.1 → 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 (167) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/README.md +12 -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 +37 -1
  11. package/dist/core/agent-session.d.ts.map +1 -1
  12. package/dist/core/agent-session.js +289 -5
  13. package/dist/core/agent-session.js.map +1 -1
  14. package/dist/core/completion-sound.d.ts +109 -0
  15. package/dist/core/completion-sound.d.ts.map +1 -0
  16. package/dist/core/completion-sound.js +165 -0
  17. package/dist/core/completion-sound.js.map +1 -0
  18. package/dist/core/context-budget-system-prompt-items.d.ts.map +1 -1
  19. package/dist/core/context-budget-system-prompt-items.js +2 -2
  20. package/dist/core/context-budget-system-prompt-items.js.map +1 -1
  21. package/dist/core/host-resource-probe.d.ts +63 -0
  22. package/dist/core/host-resource-probe.d.ts.map +1 -0
  23. package/dist/core/host-resource-probe.js +126 -0
  24. package/dist/core/host-resource-probe.js.map +1 -0
  25. package/dist/core/host-resource-snapshot.d.ts +54 -0
  26. package/dist/core/host-resource-snapshot.d.ts.map +1 -0
  27. package/dist/core/host-resource-snapshot.js +139 -0
  28. package/dist/core/host-resource-snapshot.js.map +1 -0
  29. package/dist/core/prompt-settlement.d.ts +81 -0
  30. package/dist/core/prompt-settlement.d.ts.map +1 -0
  31. package/dist/core/prompt-settlement.js +84 -0
  32. package/dist/core/prompt-settlement.js.map +1 -0
  33. package/dist/core/provider-resilience.d.ts.map +1 -1
  34. package/dist/core/provider-resilience.js +1 -1
  35. package/dist/core/provider-resilience.js.map +1 -1
  36. package/dist/core/provider-retry.d.ts +7 -0
  37. package/dist/core/provider-retry.d.ts.map +1 -1
  38. package/dist/core/provider-retry.js +24 -0
  39. package/dist/core/provider-retry.js.map +1 -1
  40. package/dist/core/provider-usage.d.ts +15 -2
  41. package/dist/core/provider-usage.d.ts.map +1 -1
  42. package/dist/core/provider-usage.js +110 -2
  43. package/dist/core/provider-usage.js.map +1 -1
  44. package/dist/core/reasoning-router-resolver.d.ts.map +1 -1
  45. package/dist/core/reasoning-router-resolver.js +1 -1
  46. package/dist/core/reasoning-router-resolver.js.map +1 -1
  47. package/dist/core/resource-admission-config.d.ts +55 -0
  48. package/dist/core/resource-admission-config.d.ts.map +1 -0
  49. package/dist/core/resource-admission-config.js +97 -0
  50. package/dist/core/resource-admission-config.js.map +1 -0
  51. package/dist/core/resource-admission.d.ts +81 -0
  52. package/dist/core/resource-admission.d.ts.map +1 -0
  53. package/dist/core/resource-admission.js +128 -0
  54. package/dist/core/resource-admission.js.map +1 -0
  55. package/dist/core/resource-governor-format.d.ts +50 -0
  56. package/dist/core/resource-governor-format.d.ts.map +1 -0
  57. package/dist/core/resource-governor-format.js +113 -0
  58. package/dist/core/resource-governor-format.js.map +1 -0
  59. package/dist/core/resource-governor-settings.d.ts +56 -0
  60. package/dist/core/resource-governor-settings.d.ts.map +1 -0
  61. package/dist/core/resource-governor-settings.js +89 -0
  62. package/dist/core/resource-governor-settings.js.map +1 -0
  63. package/dist/core/resource-observation-journal.d.ts +74 -0
  64. package/dist/core/resource-observation-journal.d.ts.map +1 -0
  65. package/dist/core/resource-observation-journal.js +198 -0
  66. package/dist/core/resource-observation-journal.js.map +1 -0
  67. package/dist/core/resource-safety-gate.d.ts +54 -0
  68. package/dist/core/resource-safety-gate.d.ts.map +1 -0
  69. package/dist/core/resource-safety-gate.js +51 -0
  70. package/dist/core/resource-safety-gate.js.map +1 -0
  71. package/dist/core/run-journal.d.ts.map +1 -1
  72. package/dist/core/run-journal.js +13 -1
  73. package/dist/core/run-journal.js.map +1 -1
  74. package/dist/core/run-resource-lease.d.ts +73 -0
  75. package/dist/core/run-resource-lease.d.ts.map +1 -0
  76. package/dist/core/run-resource-lease.js +78 -0
  77. package/dist/core/run-resource-lease.js.map +1 -0
  78. package/dist/core/session-bash-service.d.ts +12 -0
  79. package/dist/core/session-bash-service.d.ts.map +1 -1
  80. package/dist/core/session-bash-service.js +14 -0
  81. package/dist/core/session-bash-service.js.map +1 -1
  82. package/dist/core/session-system-prompt.d.ts +2 -0
  83. package/dist/core/session-system-prompt.d.ts.map +1 -1
  84. package/dist/core/session-system-prompt.js +2 -0
  85. package/dist/core/session-system-prompt.js.map +1 -1
  86. package/dist/core/session-termination.d.ts +7 -2
  87. package/dist/core/session-termination.d.ts.map +1 -1
  88. package/dist/core/session-termination.js +29 -1
  89. package/dist/core/session-termination.js.map +1 -1
  90. package/dist/core/settings-manager.d.ts +12 -0
  91. package/dist/core/settings-manager.d.ts.map +1 -1
  92. package/dist/core/settings-manager.js +36 -0
  93. package/dist/core/settings-manager.js.map +1 -1
  94. package/dist/core/slash-commands.d.ts.map +1 -1
  95. package/dist/core/slash-commands.js +1 -0
  96. package/dist/core/slash-commands.js.map +1 -1
  97. package/dist/core/subagent-lane-launcher.d.ts +64 -0
  98. package/dist/core/subagent-lane-launcher.d.ts.map +1 -0
  99. package/dist/core/subagent-lane-launcher.js +107 -0
  100. package/dist/core/subagent-lane-launcher.js.map +1 -0
  101. package/dist/core/system-cpu-sampler.d.ts +45 -0
  102. package/dist/core/system-cpu-sampler.d.ts.map +1 -0
  103. package/dist/core/system-cpu-sampler.js +95 -0
  104. package/dist/core/system-cpu-sampler.js.map +1 -0
  105. package/dist/core/system-prompt.d.ts.map +1 -1
  106. package/dist/core/system-prompt.js +5 -8
  107. package/dist/core/system-prompt.js.map +1 -1
  108. package/dist/core/workload-classifier.d.ts +32 -0
  109. package/dist/core/workload-classifier.d.ts.map +1 -0
  110. package/dist/core/workload-classifier.js +66 -0
  111. package/dist/core/workload-classifier.js.map +1 -0
  112. package/dist/core/workload-family-matcher.d.ts +3 -0
  113. package/dist/core/workload-family-matcher.d.ts.map +1 -0
  114. package/dist/core/workload-family-matcher.js +126 -0
  115. package/dist/core/workload-family-matcher.js.map +1 -0
  116. package/dist/core/workload-permit-pool.d.ts +78 -0
  117. package/dist/core/workload-permit-pool.d.ts.map +1 -0
  118. package/dist/core/workload-permit-pool.js +138 -0
  119. package/dist/core/workload-permit-pool.js.map +1 -0
  120. package/dist/core/workload-shard-executor.d.ts +68 -0
  121. package/dist/core/workload-shard-executor.d.ts.map +1 -0
  122. package/dist/core/workload-shard-executor.js +156 -0
  123. package/dist/core/workload-shard-executor.js.map +1 -0
  124. package/dist/core/workload-shard-plan.d.ts +81 -0
  125. package/dist/core/workload-shard-plan.d.ts.map +1 -0
  126. package/dist/core/workload-shard-plan.js +183 -0
  127. package/dist/core/workload-shard-plan.js.map +1 -0
  128. package/dist/core/workload-shard-runner.d.ts +11 -0
  129. package/dist/core/workload-shard-runner.d.ts.map +1 -0
  130. package/dist/core/workload-shard-runner.js +98 -0
  131. package/dist/core/workload-shard-runner.js.map +1 -0
  132. package/dist/core/workload-shard-store.d.ts +44 -0
  133. package/dist/core/workload-shard-store.d.ts.map +1 -0
  134. package/dist/core/workload-shard-store.js +189 -0
  135. package/dist/core/workload-shard-store.js.map +1 -0
  136. package/dist/core/workload-sharders.d.ts +47 -0
  137. package/dist/core/workload-sharders.d.ts.map +1 -0
  138. package/dist/core/workload-sharders.js +162 -0
  139. package/dist/core/workload-sharders.js.map +1 -0
  140. package/dist/core/workload-shell-scan.d.ts +12 -0
  141. package/dist/core/workload-shell-scan.d.ts.map +1 -0
  142. package/dist/core/workload-shell-scan.js +79 -0
  143. package/dist/core/workload-shell-scan.js.map +1 -0
  144. package/dist/main.d.ts.map +1 -1
  145. package/dist/main.js +6 -0
  146. package/dist/main.js.map +1 -1
  147. package/dist/modes/interactive/interactive-mode.d.ts +4 -0
  148. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  149. package/dist/modes/interactive/interactive-mode.js +81 -0
  150. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  151. package/docs/providers.md +1 -1
  152. package/docs/sdk.md +4 -0
  153. package/docs/settings.md +64 -2
  154. package/docs/skills.md +16 -0
  155. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  156. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  157. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  158. package/examples/extensions/gondolin/package-lock.json +2 -2
  159. package/examples/extensions/gondolin/package.json +1 -1
  160. package/examples/extensions/sandbox/package-lock.json +2 -2
  161. package/examples/extensions/sandbox/package.json +1 -1
  162. package/examples/extensions/subagent/agent-capability-router.test.ts +7 -0
  163. package/examples/extensions/subagent/domain-profiles.ts +0 -1
  164. package/examples/extensions/with-deps/package-lock.json +2 -2
  165. package/examples/extensions/with-deps/package.json +1 -1
  166. package/npm-shrinkwrap.json +18 -18
  167. package/package.json +7 -9
@@ -0,0 +1,126 @@
1
+ import * as fs from "node:fs";
2
+ import * as os from "node:os";
3
+ import * as v8 from "node:v8";
4
+ import { DEFAULT_CPU_SAMPLE_MS, MAX_CPU_SAMPLE_MS, MIN_CPU_SAMPLE_MS, sampleSystemCpuPercent, } from "./system-cpu-sampler.js";
5
+ export { DEFAULT_CPU_SAMPLE_MS, MAX_CPU_SAMPLE_MS, MIN_CPU_SAMPLE_MS };
6
+ export const DEFAULT_MAX_PROBE_MS = 300;
7
+ export const MIN_MAX_PROBE_MS = 50;
8
+ export const MAX_MAX_PROBE_MS = 5000;
9
+ export function defaultProbeIo() {
10
+ return {
11
+ platform: () => process.platform,
12
+ arch: () => process.arch,
13
+ cpuCount: () => os.cpus().length,
14
+ availableMemory: () => (typeof process.availableMemory === "function" ? process.availableMemory() : null),
15
+ constrainedMemory: () => (typeof process.constrainedMemory === "function" ? process.constrainedMemory() : null),
16
+ totalmem: () => os.totalmem(),
17
+ freemem: () => os.freemem(),
18
+ rssBytes: () => process.memoryUsage().rss,
19
+ heapStatistics: () => {
20
+ const stats = v8.getHeapStatistics();
21
+ return { heapUsedBytes: stats.used_heap_size, heapLimitBytes: stats.heap_size_limit };
22
+ },
23
+ statfs: async (target) => {
24
+ const stats = await fs.promises.statfs(target, { bigint: true });
25
+ return { bavail: stats.bavail, bsize: stats.bsize };
26
+ },
27
+ sampleSystemCpuPercent: (sampleMs) => sampleSystemCpuPercent({ sampleMs }),
28
+ osRelease: () => os.release(),
29
+ fileExists: (target) => fs.existsSync(target),
30
+ };
31
+ }
32
+ /** Coarse environment hint (§4.1). Never an admission authority; `unknown` on any probe error. */
33
+ export function detectEnvironment(io) {
34
+ try {
35
+ if (io.platform() === "linux") {
36
+ if (/microsoft/i.test(io.osRelease())) {
37
+ return "wsl";
38
+ }
39
+ if (io.fileExists("/.dockerenv") || io.fileExists("/run/.containerenv")) {
40
+ return "container";
41
+ }
42
+ }
43
+ return "native";
44
+ }
45
+ catch {
46
+ return "unknown";
47
+ }
48
+ }
49
+ /**
50
+ * Race `run()` against a hard deadline. Never rejects. The bounded deadline
51
+ * timer is intentionally ref'd (see module doc) and cleared once settled, so
52
+ * no timer outlives the probe.
53
+ */
54
+ export async function raceDeadline(run, deadlineMs) {
55
+ let timer = null;
56
+ try {
57
+ const timeout = new Promise((resolve) => {
58
+ timer = setTimeout(() => resolve("timeout"), Math.max(1, deadlineMs));
59
+ });
60
+ const work = Promise.resolve()
61
+ .then(run)
62
+ .then((value) => ({ value, timedOut: false, failed: false }), () => ({ value: null, timedOut: false, failed: true }));
63
+ const settled = await Promise.race([work, timeout]);
64
+ if (settled === "timeout") {
65
+ return { value: null, timedOut: true, failed: false };
66
+ }
67
+ return settled;
68
+ }
69
+ finally {
70
+ if (timer !== null) {
71
+ clearTimeout(timer);
72
+ }
73
+ }
74
+ }
75
+ /** `bavail * bsize` with BigInt overflow clamped to `Number.MAX_SAFE_INTEGER` (§6.5). */
76
+ export function bigintProductToSafeNumber(a, b) {
77
+ if (a < 0n || b < 0n) {
78
+ return null;
79
+ }
80
+ const product = a * b;
81
+ const max = BigInt(Number.MAX_SAFE_INTEGER);
82
+ return Number(product > max ? max : product);
83
+ }
84
+ export function guardValue(read, fallback) {
85
+ try {
86
+ return read();
87
+ }
88
+ catch {
89
+ return fallback;
90
+ }
91
+ }
92
+ export function probePositiveNumber(read, diagnostics, code) {
93
+ try {
94
+ const value = read();
95
+ // Values above MAX_SAFE_INTEGER are "no limit" sentinels, not real byte
96
+ // counts — observed on WSL where process.constrainedMemory() reports the
97
+ // cgroup u64 max (2^64). §6.4: unclear OS semantics degrade to a
98
+ // diagnostic instead of entering the candidate set.
99
+ if (typeof value !== "number" || !Number.isFinite(value) || value <= 0 || value > Number.MAX_SAFE_INTEGER) {
100
+ diagnostics.push(code);
101
+ return null;
102
+ }
103
+ return value;
104
+ }
105
+ catch {
106
+ diagnostics.push(code);
107
+ return null;
108
+ }
109
+ }
110
+ export function probePositiveInteger(read, diagnostics, code) {
111
+ const value = probePositiveNumber(read, diagnostics, code);
112
+ return value === null ? null : Math.floor(value);
113
+ }
114
+ export function sanitizeNonNegative(value) {
115
+ if (typeof value !== "number" || !Number.isFinite(value) || value < 0) {
116
+ return null;
117
+ }
118
+ return value;
119
+ }
120
+ export function clampInteger(value, fallback, min, max) {
121
+ if (typeof value !== "number" || !Number.isFinite(value)) {
122
+ return fallback;
123
+ }
124
+ return Math.floor(Math.max(min, Math.min(max, value)));
125
+ }
126
+ //# sourceMappingURL=host-resource-probe.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"host-resource-probe.js","sourceRoot":"","sources":["../../src/core/host-resource-probe.ts"],"names":[],"mappings":"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","sourcesContent":["import * as fs from \"node:fs\";\nimport * as os from \"node:os\";\nimport * as v8 from \"node:v8\";\nimport {\n\tDEFAULT_CPU_SAMPLE_MS,\n\tMAX_CPU_SAMPLE_MS,\n\tMIN_CPU_SAMPLE_MS,\n\tsampleSystemCpuPercent,\n} from \"./system-cpu-sampler.ts\";\n\nexport { DEFAULT_CPU_SAMPLE_MS, MAX_CPU_SAMPLE_MS, MIN_CPU_SAMPLE_MS };\n\n/** Diagnostic hint only — admission authority comes from measured values (§4.1). */\nexport type HostEnvironment = \"native\" | \"wsl\" | \"container\" | \"unknown\";\n\n/** Injectable probe sources for deterministic, fault-injectable tests. */\nexport interface HostResourceProbeIo {\n\treadonly platform: () => NodeJS.Platform;\n\treadonly arch: () => string;\n\treadonly cpuCount: () => number;\n\treadonly availableMemory: () => number | null | undefined;\n\treadonly constrainedMemory: () => number | null | undefined;\n\treadonly totalmem: () => number;\n\treadonly freemem: () => number;\n\treadonly rssBytes: () => number;\n\treadonly heapStatistics: () => { readonly heapUsedBytes: number; readonly heapLimitBytes: number };\n\treadonly statfs: (target: string) => Promise<{ readonly bavail: bigint; readonly bsize: bigint }>;\n\treadonly sampleSystemCpuPercent: (sampleMs: number) => Promise<number | null>;\n\treadonly osRelease: () => string;\n\treadonly fileExists: (target: string) => boolean;\n}\n\nexport type HostResourceProbeOptions = {\n\t/** Workspace directory for the disk probe. Used for measurement only; never recorded. */\n\tcwd?: string;\n\t/** Hard preflight deadline for async probes; default 300 ms, clamped `[50, 5000]` (§6.3, §18.1). */\n\tmaxProbeMs?: number;\n\t/** CPU two-sample interval; clamped `[150, 250]` ms (§6.3). */\n\tcpuSampleMs?: number;\n\t/** Injectable clock for a deterministic `observedAt`. */\n\tnow?: () => Date;\n\t/** Partial probe source overrides; unspecified sources use real process/OS reads. */\n\tio?: Partial<HostResourceProbeIo>;\n};\n\nexport const DEFAULT_MAX_PROBE_MS = 300;\nexport const MIN_MAX_PROBE_MS = 50;\nexport const MAX_MAX_PROBE_MS = 5000;\n\nexport function defaultProbeIo(): HostResourceProbeIo {\n\treturn {\n\t\tplatform: () => process.platform,\n\t\tarch: () => process.arch,\n\t\tcpuCount: () => os.cpus().length,\n\t\tavailableMemory: () => (typeof process.availableMemory === \"function\" ? process.availableMemory() : null),\n\t\tconstrainedMemory: () => (typeof process.constrainedMemory === \"function\" ? process.constrainedMemory() : null),\n\t\ttotalmem: () => os.totalmem(),\n\t\tfreemem: () => os.freemem(),\n\t\trssBytes: () => process.memoryUsage().rss,\n\t\theapStatistics: () => {\n\t\t\tconst stats = v8.getHeapStatistics();\n\t\t\treturn { heapUsedBytes: stats.used_heap_size, heapLimitBytes: stats.heap_size_limit };\n\t\t},\n\t\tstatfs: async (target) => {\n\t\t\tconst stats = await fs.promises.statfs(target, { bigint: true });\n\t\t\treturn { bavail: stats.bavail, bsize: stats.bsize };\n\t\t},\n\t\tsampleSystemCpuPercent: (sampleMs) => sampleSystemCpuPercent({ sampleMs }),\n\t\tosRelease: () => os.release(),\n\t\tfileExists: (target) => fs.existsSync(target),\n\t};\n}\n\n/** Coarse environment hint (§4.1). Never an admission authority; `unknown` on any probe error. */\nexport function detectEnvironment(io: HostResourceProbeIo): HostEnvironment {\n\ttry {\n\t\tif (io.platform() === \"linux\") {\n\t\t\tif (/microsoft/i.test(io.osRelease())) {\n\t\t\t\treturn \"wsl\";\n\t\t\t}\n\t\t\tif (io.fileExists(\"/.dockerenv\") || io.fileExists(\"/run/.containerenv\")) {\n\t\t\t\treturn \"container\";\n\t\t\t}\n\t\t}\n\t\treturn \"native\";\n\t} catch {\n\t\treturn \"unknown\";\n\t}\n}\n\ntype DeadlineResult<T> = { readonly value: T | null; readonly timedOut: boolean; readonly failed: boolean };\n\n/**\n * Race `run()` against a hard deadline. Never rejects. The bounded deadline\n * timer is intentionally ref'd (see module doc) and cleared once settled, so\n * no timer outlives the probe.\n */\nexport async function raceDeadline<T>(run: () => Promise<T>, deadlineMs: number): Promise<DeadlineResult<T>> {\n\tlet timer: ReturnType<typeof setTimeout> | null = null;\n\ttry {\n\t\tconst timeout = new Promise<\"timeout\">((resolve) => {\n\t\t\ttimer = setTimeout(() => resolve(\"timeout\"), Math.max(1, deadlineMs));\n\t\t});\n\t\tconst work = Promise.resolve()\n\t\t\t.then(run)\n\t\t\t.then(\n\t\t\t\t(value): DeadlineResult<T> => ({ value, timedOut: false, failed: false }),\n\t\t\t\t(): DeadlineResult<T> => ({ value: null, timedOut: false, failed: true }),\n\t\t\t);\n\t\tconst settled = await Promise.race([work, timeout]);\n\t\tif (settled === \"timeout\") {\n\t\t\treturn { value: null, timedOut: true, failed: false };\n\t\t}\n\t\treturn settled;\n\t} finally {\n\t\tif (timer !== null) {\n\t\t\tclearTimeout(timer);\n\t\t}\n\t}\n}\n\n/** `bavail * bsize` with BigInt overflow clamped to `Number.MAX_SAFE_INTEGER` (§6.5). */\nexport function bigintProductToSafeNumber(a: bigint, b: bigint): number | null {\n\tif (a < 0n || b < 0n) {\n\t\treturn null;\n\t}\n\tconst product = a * b;\n\tconst max = BigInt(Number.MAX_SAFE_INTEGER);\n\treturn Number(product > max ? max : product);\n}\n\nexport function guardValue<T>(read: () => T, fallback: T): T {\n\ttry {\n\t\treturn read();\n\t} catch {\n\t\treturn fallback;\n\t}\n}\n\nexport function probePositiveNumber(\n\tread: () => number | null | undefined,\n\tdiagnostics: string[],\n\tcode: string,\n): number | null {\n\ttry {\n\t\tconst value = read();\n\t\t// Values above MAX_SAFE_INTEGER are \"no limit\" sentinels, not real byte\n\t\t// counts — observed on WSL where process.constrainedMemory() reports the\n\t\t// cgroup u64 max (2^64). §6.4: unclear OS semantics degrade to a\n\t\t// diagnostic instead of entering the candidate set.\n\t\tif (typeof value !== \"number\" || !Number.isFinite(value) || value <= 0 || value > Number.MAX_SAFE_INTEGER) {\n\t\t\tdiagnostics.push(code);\n\t\t\treturn null;\n\t\t}\n\t\treturn value;\n\t} catch {\n\t\tdiagnostics.push(code);\n\t\treturn null;\n\t}\n}\n\nexport function probePositiveInteger(read: () => number, diagnostics: string[], code: string): number | null {\n\tconst value = probePositiveNumber(read, diagnostics, code);\n\treturn value === null ? null : Math.floor(value);\n}\n\nexport function sanitizeNonNegative(value: number | undefined): number | null {\n\tif (typeof value !== \"number\" || !Number.isFinite(value) || value < 0) {\n\t\treturn null;\n\t}\n\treturn value;\n}\n\nexport function clampInteger(value: unknown, fallback: number, min: number, max: number): number {\n\tif (typeof value !== \"number\" || !Number.isFinite(value)) {\n\t\treturn fallback;\n\t}\n\treturn Math.floor(Math.max(min, Math.min(max, value)));\n}\n"]}
@@ -0,0 +1,54 @@
1
+ import { DEFAULT_MAX_PROBE_MS, type HostEnvironment, type HostResourceProbeIo, type HostResourceProbeOptions, MAX_MAX_PROBE_MS, MIN_MAX_PROBE_MS } from "./host-resource-probe.ts";
2
+ export { DEFAULT_MAX_PROBE_MS, MAX_MAX_PROBE_MS, MIN_MAX_PROBE_MS };
3
+ export type { HostEnvironment, HostResourceProbeIo, HostResourceProbeOptions };
4
+ /**
5
+ * Host resource snapshot probe (OMK v0.97.x roadmap §6, M1/PR1).
6
+ *
7
+ * The snapshot is an immutable operational fact captured before a prompt run.
8
+ * It is observability-first in this layer: no admission or scheduler behavior
9
+ * changes here (that wiring lands in later milestones).
10
+ *
11
+ * Invariants enforced by this module:
12
+ * - Never throws: every probe source is individually guarded and a failure
13
+ * degrades to `null` plus a bounded diagnostic code (roadmap §2.1, §21).
14
+ * - Bounded latency: async probes race a hard deadline (default 300 ms,
15
+ * roadmap §6.3). Deadline timers stay ref'd while the probe is awaited so
16
+ * the returned promise always settles even when it is the only pending
17
+ * work in the process (headless CLI probes), and are cleared on settle.
18
+ * - Privacy boundary (roadmap §6.2): the snapshot carries only numeric
19
+ * capacity facts, platform/arch, a coarse environment hint, and bounded
20
+ * diagnostic codes. No username, hostname, paths, env dump, mounts,
21
+ * network interfaces, machine identifiers, or file contents.
22
+ */
23
+ export declare const HOST_RESOURCE_SNAPSHOT_VERSION: 1;
24
+ export interface HostResourceSnapshot {
25
+ readonly schemaVersion: typeof HOST_RESOURCE_SNAPSHOT_VERSION;
26
+ readonly observedAt: string;
27
+ readonly platform: NodeJS.Platform;
28
+ readonly arch: string;
29
+ readonly logicalCpuCount: number;
30
+ readonly processAvailableMemoryBytes: number | null;
31
+ readonly constrainedMemoryBytes: number | null;
32
+ readonly hostTotalMemoryBytes: number | null;
33
+ readonly hostFreeMemoryBytes: number | null;
34
+ readonly processRssBytes: number;
35
+ readonly heapUsedBytes: number;
36
+ readonly heapLimitBytes: number;
37
+ readonly systemCpuPercent: number | null;
38
+ readonly workspaceAvailableBytes: number | null;
39
+ readonly environment: HostEnvironment;
40
+ readonly diagnostics: readonly string[];
41
+ }
42
+ /**
43
+ * Capture a host resource snapshot. Resolves within roughly `maxProbeMs` and
44
+ * never rejects; unavailable sources yield `null` fields plus diagnostics.
45
+ */
46
+ export declare function captureHostResourceSnapshot(options?: HostResourceProbeOptions): Promise<HostResourceSnapshot>;
47
+ /**
48
+ * §6.4 effective memory: the minimum of the known positive candidates
49
+ * (process available, constrained, host free), or `null` when none is known.
50
+ */
51
+ export declare function effectiveAvailableMemoryBytes(snapshot: HostResourceSnapshot): number | null;
52
+ /** Stable content digest of a snapshot (used as `snapshotDigest` in admission decisions). */
53
+ export declare function computeHostResourceSnapshotDigest(snapshot: HostResourceSnapshot): string;
54
+ //# sourceMappingURL=host-resource-snapshot.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"host-resource-snapshot.d.ts","sourceRoot":"","sources":["../../src/core/host-resource-snapshot.ts"],"names":[],"mappings":"AACA,OAAO,EAIN,oBAAoB,EAIpB,KAAK,eAAe,EACpB,KAAK,mBAAmB,EACxB,KAAK,wBAAwB,EAE7B,gBAAgB,EAEhB,gBAAgB,EAKhB,MAAM,0BAA0B,CAAC;AAElC,OAAO,EAAE,oBAAoB,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,CAAC;AACpE,YAAY,EAAE,eAAe,EAAE,mBAAmB,EAAE,wBAAwB,EAAE,CAAC;AAE/E;;;;;;;;;;;;;;;;;;GAkBG;AAEH,eAAO,MAAM,8BAA8B,GAAa,CAAC;AAEzD,MAAM,WAAW,oBAAoB;IACpC,QAAQ,CAAC,aAAa,EAAE,OAAO,8BAA8B,CAAC;IAC9D,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC,QAAQ,CAAC;IACnC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IAEjC,QAAQ,CAAC,2BAA2B,EAAE,MAAM,GAAG,IAAI,CAAC;IACpD,QAAQ,CAAC,sBAAsB,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/C,QAAQ,CAAC,oBAAoB,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7C,QAAQ,CAAC,mBAAmB,EAAE,MAAM,GAAG,IAAI,CAAC;IAE5C,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAEhC,QAAQ,CAAC,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IACzC,QAAQ,CAAC,uBAAuB,EAAE,MAAM,GAAG,IAAI,CAAC;IAEhD,QAAQ,CAAC,WAAW,EAAE,eAAe,CAAC;IACtC,QAAQ,CAAC,WAAW,EAAE,SAAS,MAAM,EAAE,CAAC;CACxC;AAED;;;GAGG;AACH,wBAAsB,2BAA2B,CAAC,OAAO,CAAC,EAAE,wBAAwB,GAAG,OAAO,CAAC,oBAAoB,CAAC,CA+FnH;AAED;;;GAGG;AACH,wBAAgB,6BAA6B,CAAC,QAAQ,EAAE,oBAAoB,GAAG,MAAM,GAAG,IAAI,CAU3F;AAED,6FAA6F;AAC7F,wBAAgB,iCAAiC,CAAC,QAAQ,EAAE,oBAAoB,GAAG,MAAM,CAoBxF","sourcesContent":["import { createHash } from \"node:crypto\";\nimport {\n\tbigintProductToSafeNumber,\n\tclampInteger,\n\tDEFAULT_CPU_SAMPLE_MS,\n\tDEFAULT_MAX_PROBE_MS,\n\tdefaultProbeIo,\n\tdetectEnvironment,\n\tguardValue,\n\ttype HostEnvironment,\n\ttype HostResourceProbeIo,\n\ttype HostResourceProbeOptions,\n\tMAX_CPU_SAMPLE_MS,\n\tMAX_MAX_PROBE_MS,\n\tMIN_CPU_SAMPLE_MS,\n\tMIN_MAX_PROBE_MS,\n\tprobePositiveInteger,\n\tprobePositiveNumber,\n\traceDeadline,\n\tsanitizeNonNegative,\n} from \"./host-resource-probe.ts\";\n\nexport { DEFAULT_MAX_PROBE_MS, MAX_MAX_PROBE_MS, MIN_MAX_PROBE_MS };\nexport type { HostEnvironment, HostResourceProbeIo, HostResourceProbeOptions };\n\n/**\n * Host resource snapshot probe (OMK v0.97.x roadmap §6, M1/PR1).\n *\n * The snapshot is an immutable operational fact captured before a prompt run.\n * It is observability-first in this layer: no admission or scheduler behavior\n * changes here (that wiring lands in later milestones).\n *\n * Invariants enforced by this module:\n * - Never throws: every probe source is individually guarded and a failure\n * degrades to `null` plus a bounded diagnostic code (roadmap §2.1, §21).\n * - Bounded latency: async probes race a hard deadline (default 300 ms,\n * roadmap §6.3). Deadline timers stay ref'd while the probe is awaited so\n * the returned promise always settles even when it is the only pending\n * work in the process (headless CLI probes), and are cleared on settle.\n * - Privacy boundary (roadmap §6.2): the snapshot carries only numeric\n * capacity facts, platform/arch, a coarse environment hint, and bounded\n * diagnostic codes. No username, hostname, paths, env dump, mounts,\n * network interfaces, machine identifiers, or file contents.\n */\n\nexport const HOST_RESOURCE_SNAPSHOT_VERSION = 1 as const;\n\nexport interface HostResourceSnapshot {\n\treadonly schemaVersion: typeof HOST_RESOURCE_SNAPSHOT_VERSION;\n\treadonly observedAt: string;\n\treadonly platform: NodeJS.Platform;\n\treadonly arch: string;\n\treadonly logicalCpuCount: number;\n\n\treadonly processAvailableMemoryBytes: number | null;\n\treadonly constrainedMemoryBytes: number | null;\n\treadonly hostTotalMemoryBytes: number | null;\n\treadonly hostFreeMemoryBytes: number | null;\n\n\treadonly processRssBytes: number;\n\treadonly heapUsedBytes: number;\n\treadonly heapLimitBytes: number;\n\n\treadonly systemCpuPercent: number | null;\n\treadonly workspaceAvailableBytes: number | null;\n\n\treadonly environment: HostEnvironment;\n\treadonly diagnostics: readonly string[];\n}\n\n/**\n * Capture a host resource snapshot. Resolves within roughly `maxProbeMs` and\n * never rejects; unavailable sources yield `null` fields plus diagnostics.\n */\nexport async function captureHostResourceSnapshot(options?: HostResourceProbeOptions): Promise<HostResourceSnapshot> {\n\tconst io: HostResourceProbeIo = { ...defaultProbeIo(), ...options?.io };\n\tconst maxProbeMs = clampInteger(options?.maxProbeMs, DEFAULT_MAX_PROBE_MS, MIN_MAX_PROBE_MS, MAX_MAX_PROBE_MS);\n\tconst cpuSampleMs = clampInteger(options?.cpuSampleMs, DEFAULT_CPU_SAMPLE_MS, MIN_CPU_SAMPLE_MS, MAX_CPU_SAMPLE_MS);\n\tconst diagnostics: string[] = [];\n\n\tconst observedAt = guardValue(() => (options?.now?.() ?? new Date()).toISOString(), new Date(0).toISOString());\n\tconst platform = guardValue<NodeJS.Platform>(() => io.platform(), \"linux\");\n\tconst arch = guardValue(() => io.arch(), \"unknown\");\n\tconst logicalCpuCount = Math.max(\n\t\t1,\n\t\tprobePositiveInteger(() => io.cpuCount(), diagnostics, \"probe.cpu-count.unavailable\") ?? 1,\n\t);\n\n\t// Memory semantics note (§6.4): `process.availableMemory()` is free memory\n\t// still available to the process; `process.constrainedMemory()` is the OS\n\t// limit (0 when unknown, and on some platforms it includes current RSS).\n\t// We record raw values and never subtract RSS from them, so the min-known\n\t// policy stays conservative without double subtraction.\n\tconst processAvailableMemoryBytes = probePositiveNumber(\n\t\t() => io.availableMemory(),\n\t\tdiagnostics,\n\t\t\"probe.available-memory.unavailable\",\n\t);\n\tconst constrainedMemoryBytes = probePositiveNumber(\n\t\t() => io.constrainedMemory(),\n\t\tdiagnostics,\n\t\t\"probe.constrained-memory.unavailable\",\n\t);\n\tconst hostTotalMemoryBytes = probePositiveNumber(\n\t\t() => io.totalmem(),\n\t\tdiagnostics,\n\t\t\"probe.host-total-memory.unavailable\",\n\t);\n\tconst hostFreeMemoryBytes = probePositiveNumber(\n\t\t() => io.freemem(),\n\t\tdiagnostics,\n\t\t\"probe.host-free-memory.unavailable\",\n\t);\n\n\tconst processRssBytes = probePositiveNumber(() => io.rssBytes(), diagnostics, \"probe.rss.unavailable\") ?? 0;\n\tconst heap = guardValue(() => io.heapStatistics(), null);\n\tif (heap === null) {\n\t\tdiagnostics.push(\"probe.heap.unavailable\");\n\t}\n\tconst heapUsedBytes = sanitizeNonNegative(heap?.heapUsedBytes) ?? 0;\n\tconst heapLimitBytes = sanitizeNonNegative(heap?.heapLimitBytes) ?? 0;\n\n\t// Async probes share one deadline and run concurrently so total preflight\n\t// wall time stays ~= maxProbeMs (roadmap §24: p95 <= 300 ms).\n\tconst [disk, cpu] = await Promise.all([\n\t\traceDeadline(() => io.statfs(options?.cwd ?? guardValue(() => process.cwd(), \".\")), maxProbeMs),\n\t\traceDeadline(() => io.sampleSystemCpuPercent(cpuSampleMs), maxProbeMs),\n\t]);\n\n\tlet workspaceAvailableBytes: number | null = null;\n\tif (disk.timedOut) {\n\t\tdiagnostics.push(\"probe.disk.timeout\");\n\t} else if (disk.failed || disk.value === null) {\n\t\tdiagnostics.push(\"probe.disk.unavailable\");\n\t} else {\n\t\t// §6.5: bavail (unprivileged-available), never bfree; clamp before Number().\n\t\tworkspaceAvailableBytes = bigintProductToSafeNumber(disk.value.bavail, disk.value.bsize);\n\t\tif (workspaceAvailableBytes === null) {\n\t\t\tdiagnostics.push(\"probe.disk.unavailable\");\n\t\t}\n\t}\n\n\tlet systemCpuPercent: number | null = null;\n\tif (cpu.timedOut) {\n\t\tdiagnostics.push(\"probe.cpu.timeout\");\n\t} else if (cpu.failed || cpu.value === null || !Number.isFinite(cpu.value)) {\n\t\tdiagnostics.push(\"probe.cpu.unavailable\");\n\t} else {\n\t\tsystemCpuPercent = Math.max(0, Math.min(100, cpu.value));\n\t}\n\n\treturn {\n\t\tschemaVersion: HOST_RESOURCE_SNAPSHOT_VERSION,\n\t\tobservedAt,\n\t\tplatform,\n\t\tarch,\n\t\tlogicalCpuCount,\n\t\tprocessAvailableMemoryBytes,\n\t\tconstrainedMemoryBytes,\n\t\thostTotalMemoryBytes,\n\t\thostFreeMemoryBytes,\n\t\tprocessRssBytes,\n\t\theapUsedBytes,\n\t\theapLimitBytes,\n\t\tsystemCpuPercent,\n\t\tworkspaceAvailableBytes,\n\t\tenvironment: detectEnvironment(io),\n\t\tdiagnostics,\n\t};\n}\n\n/**\n * §6.4 effective memory: the minimum of the known positive candidates\n * (process available, constrained, host free), or `null` when none is known.\n */\nexport function effectiveAvailableMemoryBytes(snapshot: HostResourceSnapshot): number | null {\n\tconst candidates = [\n\t\tsnapshot.processAvailableMemoryBytes,\n\t\tsnapshot.constrainedMemoryBytes,\n\t\tsnapshot.hostFreeMemoryBytes,\n\t].filter((value): value is number => typeof value === \"number\" && Number.isFinite(value) && value > 0);\n\tif (candidates.length === 0) {\n\t\treturn null;\n\t}\n\treturn Math.min(...candidates);\n}\n\n/** Stable content digest of a snapshot (used as `snapshotDigest` in admission decisions). */\nexport function computeHostResourceSnapshotDigest(snapshot: HostResourceSnapshot): string {\n\tconst canonical = JSON.stringify([\n\t\tsnapshot.schemaVersion,\n\t\tsnapshot.observedAt,\n\t\tsnapshot.platform,\n\t\tsnapshot.arch,\n\t\tsnapshot.logicalCpuCount,\n\t\tsnapshot.processAvailableMemoryBytes,\n\t\tsnapshot.constrainedMemoryBytes,\n\t\tsnapshot.hostTotalMemoryBytes,\n\t\tsnapshot.hostFreeMemoryBytes,\n\t\tsnapshot.processRssBytes,\n\t\tsnapshot.heapUsedBytes,\n\t\tsnapshot.heapLimitBytes,\n\t\tsnapshot.systemCpuPercent,\n\t\tsnapshot.workspaceAvailableBytes,\n\t\tsnapshot.environment,\n\t\tsnapshot.diagnostics,\n\t]);\n\treturn createHash(\"sha256\").update(canonical, \"utf8\").digest(\"hex\");\n}\n"]}
@@ -0,0 +1,139 @@
1
+ import { createHash } from "node:crypto";
2
+ import { bigintProductToSafeNumber, clampInteger, DEFAULT_CPU_SAMPLE_MS, DEFAULT_MAX_PROBE_MS, defaultProbeIo, detectEnvironment, guardValue, MAX_CPU_SAMPLE_MS, MAX_MAX_PROBE_MS, MIN_CPU_SAMPLE_MS, MIN_MAX_PROBE_MS, probePositiveInteger, probePositiveNumber, raceDeadline, sanitizeNonNegative, } from "./host-resource-probe.js";
3
+ export { DEFAULT_MAX_PROBE_MS, MAX_MAX_PROBE_MS, MIN_MAX_PROBE_MS };
4
+ /**
5
+ * Host resource snapshot probe (OMK v0.97.x roadmap §6, M1/PR1).
6
+ *
7
+ * The snapshot is an immutable operational fact captured before a prompt run.
8
+ * It is observability-first in this layer: no admission or scheduler behavior
9
+ * changes here (that wiring lands in later milestones).
10
+ *
11
+ * Invariants enforced by this module:
12
+ * - Never throws: every probe source is individually guarded and a failure
13
+ * degrades to `null` plus a bounded diagnostic code (roadmap §2.1, §21).
14
+ * - Bounded latency: async probes race a hard deadline (default 300 ms,
15
+ * roadmap §6.3). Deadline timers stay ref'd while the probe is awaited so
16
+ * the returned promise always settles even when it is the only pending
17
+ * work in the process (headless CLI probes), and are cleared on settle.
18
+ * - Privacy boundary (roadmap §6.2): the snapshot carries only numeric
19
+ * capacity facts, platform/arch, a coarse environment hint, and bounded
20
+ * diagnostic codes. No username, hostname, paths, env dump, mounts,
21
+ * network interfaces, machine identifiers, or file contents.
22
+ */
23
+ export const HOST_RESOURCE_SNAPSHOT_VERSION = 1;
24
+ /**
25
+ * Capture a host resource snapshot. Resolves within roughly `maxProbeMs` and
26
+ * never rejects; unavailable sources yield `null` fields plus diagnostics.
27
+ */
28
+ export async function captureHostResourceSnapshot(options) {
29
+ const io = { ...defaultProbeIo(), ...options?.io };
30
+ const maxProbeMs = clampInteger(options?.maxProbeMs, DEFAULT_MAX_PROBE_MS, MIN_MAX_PROBE_MS, MAX_MAX_PROBE_MS);
31
+ const cpuSampleMs = clampInteger(options?.cpuSampleMs, DEFAULT_CPU_SAMPLE_MS, MIN_CPU_SAMPLE_MS, MAX_CPU_SAMPLE_MS);
32
+ const diagnostics = [];
33
+ const observedAt = guardValue(() => (options?.now?.() ?? new Date()).toISOString(), new Date(0).toISOString());
34
+ const platform = guardValue(() => io.platform(), "linux");
35
+ const arch = guardValue(() => io.arch(), "unknown");
36
+ const logicalCpuCount = Math.max(1, probePositiveInteger(() => io.cpuCount(), diagnostics, "probe.cpu-count.unavailable") ?? 1);
37
+ // Memory semantics note (§6.4): `process.availableMemory()` is free memory
38
+ // still available to the process; `process.constrainedMemory()` is the OS
39
+ // limit (0 when unknown, and on some platforms it includes current RSS).
40
+ // We record raw values and never subtract RSS from them, so the min-known
41
+ // policy stays conservative without double subtraction.
42
+ const processAvailableMemoryBytes = probePositiveNumber(() => io.availableMemory(), diagnostics, "probe.available-memory.unavailable");
43
+ const constrainedMemoryBytes = probePositiveNumber(() => io.constrainedMemory(), diagnostics, "probe.constrained-memory.unavailable");
44
+ const hostTotalMemoryBytes = probePositiveNumber(() => io.totalmem(), diagnostics, "probe.host-total-memory.unavailable");
45
+ const hostFreeMemoryBytes = probePositiveNumber(() => io.freemem(), diagnostics, "probe.host-free-memory.unavailable");
46
+ const processRssBytes = probePositiveNumber(() => io.rssBytes(), diagnostics, "probe.rss.unavailable") ?? 0;
47
+ const heap = guardValue(() => io.heapStatistics(), null);
48
+ if (heap === null) {
49
+ diagnostics.push("probe.heap.unavailable");
50
+ }
51
+ const heapUsedBytes = sanitizeNonNegative(heap?.heapUsedBytes) ?? 0;
52
+ const heapLimitBytes = sanitizeNonNegative(heap?.heapLimitBytes) ?? 0;
53
+ // Async probes share one deadline and run concurrently so total preflight
54
+ // wall time stays ~= maxProbeMs (roadmap §24: p95 <= 300 ms).
55
+ const [disk, cpu] = await Promise.all([
56
+ raceDeadline(() => io.statfs(options?.cwd ?? guardValue(() => process.cwd(), ".")), maxProbeMs),
57
+ raceDeadline(() => io.sampleSystemCpuPercent(cpuSampleMs), maxProbeMs),
58
+ ]);
59
+ let workspaceAvailableBytes = null;
60
+ if (disk.timedOut) {
61
+ diagnostics.push("probe.disk.timeout");
62
+ }
63
+ else if (disk.failed || disk.value === null) {
64
+ diagnostics.push("probe.disk.unavailable");
65
+ }
66
+ else {
67
+ // §6.5: bavail (unprivileged-available), never bfree; clamp before Number().
68
+ workspaceAvailableBytes = bigintProductToSafeNumber(disk.value.bavail, disk.value.bsize);
69
+ if (workspaceAvailableBytes === null) {
70
+ diagnostics.push("probe.disk.unavailable");
71
+ }
72
+ }
73
+ let systemCpuPercent = null;
74
+ if (cpu.timedOut) {
75
+ diagnostics.push("probe.cpu.timeout");
76
+ }
77
+ else if (cpu.failed || cpu.value === null || !Number.isFinite(cpu.value)) {
78
+ diagnostics.push("probe.cpu.unavailable");
79
+ }
80
+ else {
81
+ systemCpuPercent = Math.max(0, Math.min(100, cpu.value));
82
+ }
83
+ return {
84
+ schemaVersion: HOST_RESOURCE_SNAPSHOT_VERSION,
85
+ observedAt,
86
+ platform,
87
+ arch,
88
+ logicalCpuCount,
89
+ processAvailableMemoryBytes,
90
+ constrainedMemoryBytes,
91
+ hostTotalMemoryBytes,
92
+ hostFreeMemoryBytes,
93
+ processRssBytes,
94
+ heapUsedBytes,
95
+ heapLimitBytes,
96
+ systemCpuPercent,
97
+ workspaceAvailableBytes,
98
+ environment: detectEnvironment(io),
99
+ diagnostics,
100
+ };
101
+ }
102
+ /**
103
+ * §6.4 effective memory: the minimum of the known positive candidates
104
+ * (process available, constrained, host free), or `null` when none is known.
105
+ */
106
+ export function effectiveAvailableMemoryBytes(snapshot) {
107
+ const candidates = [
108
+ snapshot.processAvailableMemoryBytes,
109
+ snapshot.constrainedMemoryBytes,
110
+ snapshot.hostFreeMemoryBytes,
111
+ ].filter((value) => typeof value === "number" && Number.isFinite(value) && value > 0);
112
+ if (candidates.length === 0) {
113
+ return null;
114
+ }
115
+ return Math.min(...candidates);
116
+ }
117
+ /** Stable content digest of a snapshot (used as `snapshotDigest` in admission decisions). */
118
+ export function computeHostResourceSnapshotDigest(snapshot) {
119
+ const canonical = JSON.stringify([
120
+ snapshot.schemaVersion,
121
+ snapshot.observedAt,
122
+ snapshot.platform,
123
+ snapshot.arch,
124
+ snapshot.logicalCpuCount,
125
+ snapshot.processAvailableMemoryBytes,
126
+ snapshot.constrainedMemoryBytes,
127
+ snapshot.hostTotalMemoryBytes,
128
+ snapshot.hostFreeMemoryBytes,
129
+ snapshot.processRssBytes,
130
+ snapshot.heapUsedBytes,
131
+ snapshot.heapLimitBytes,
132
+ snapshot.systemCpuPercent,
133
+ snapshot.workspaceAvailableBytes,
134
+ snapshot.environment,
135
+ snapshot.diagnostics,
136
+ ]);
137
+ return createHash("sha256").update(canonical, "utf8").digest("hex");
138
+ }
139
+ //# sourceMappingURL=host-resource-snapshot.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"host-resource-snapshot.js","sourceRoot":"","sources":["../../src/core/host-resource-snapshot.ts"],"names":[],"mappings":"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","sourcesContent":["import { createHash } from \"node:crypto\";\nimport {\n\tbigintProductToSafeNumber,\n\tclampInteger,\n\tDEFAULT_CPU_SAMPLE_MS,\n\tDEFAULT_MAX_PROBE_MS,\n\tdefaultProbeIo,\n\tdetectEnvironment,\n\tguardValue,\n\ttype HostEnvironment,\n\ttype HostResourceProbeIo,\n\ttype HostResourceProbeOptions,\n\tMAX_CPU_SAMPLE_MS,\n\tMAX_MAX_PROBE_MS,\n\tMIN_CPU_SAMPLE_MS,\n\tMIN_MAX_PROBE_MS,\n\tprobePositiveInteger,\n\tprobePositiveNumber,\n\traceDeadline,\n\tsanitizeNonNegative,\n} from \"./host-resource-probe.ts\";\n\nexport { DEFAULT_MAX_PROBE_MS, MAX_MAX_PROBE_MS, MIN_MAX_PROBE_MS };\nexport type { HostEnvironment, HostResourceProbeIo, HostResourceProbeOptions };\n\n/**\n * Host resource snapshot probe (OMK v0.97.x roadmap §6, M1/PR1).\n *\n * The snapshot is an immutable operational fact captured before a prompt run.\n * It is observability-first in this layer: no admission or scheduler behavior\n * changes here (that wiring lands in later milestones).\n *\n * Invariants enforced by this module:\n * - Never throws: every probe source is individually guarded and a failure\n * degrades to `null` plus a bounded diagnostic code (roadmap §2.1, §21).\n * - Bounded latency: async probes race a hard deadline (default 300 ms,\n * roadmap §6.3). Deadline timers stay ref'd while the probe is awaited so\n * the returned promise always settles even when it is the only pending\n * work in the process (headless CLI probes), and are cleared on settle.\n * - Privacy boundary (roadmap §6.2): the snapshot carries only numeric\n * capacity facts, platform/arch, a coarse environment hint, and bounded\n * diagnostic codes. No username, hostname, paths, env dump, mounts,\n * network interfaces, machine identifiers, or file contents.\n */\n\nexport const HOST_RESOURCE_SNAPSHOT_VERSION = 1 as const;\n\nexport interface HostResourceSnapshot {\n\treadonly schemaVersion: typeof HOST_RESOURCE_SNAPSHOT_VERSION;\n\treadonly observedAt: string;\n\treadonly platform: NodeJS.Platform;\n\treadonly arch: string;\n\treadonly logicalCpuCount: number;\n\n\treadonly processAvailableMemoryBytes: number | null;\n\treadonly constrainedMemoryBytes: number | null;\n\treadonly hostTotalMemoryBytes: number | null;\n\treadonly hostFreeMemoryBytes: number | null;\n\n\treadonly processRssBytes: number;\n\treadonly heapUsedBytes: number;\n\treadonly heapLimitBytes: number;\n\n\treadonly systemCpuPercent: number | null;\n\treadonly workspaceAvailableBytes: number | null;\n\n\treadonly environment: HostEnvironment;\n\treadonly diagnostics: readonly string[];\n}\n\n/**\n * Capture a host resource snapshot. Resolves within roughly `maxProbeMs` and\n * never rejects; unavailable sources yield `null` fields plus diagnostics.\n */\nexport async function captureHostResourceSnapshot(options?: HostResourceProbeOptions): Promise<HostResourceSnapshot> {\n\tconst io: HostResourceProbeIo = { ...defaultProbeIo(), ...options?.io };\n\tconst maxProbeMs = clampInteger(options?.maxProbeMs, DEFAULT_MAX_PROBE_MS, MIN_MAX_PROBE_MS, MAX_MAX_PROBE_MS);\n\tconst cpuSampleMs = clampInteger(options?.cpuSampleMs, DEFAULT_CPU_SAMPLE_MS, MIN_CPU_SAMPLE_MS, MAX_CPU_SAMPLE_MS);\n\tconst diagnostics: string[] = [];\n\n\tconst observedAt = guardValue(() => (options?.now?.() ?? new Date()).toISOString(), new Date(0).toISOString());\n\tconst platform = guardValue<NodeJS.Platform>(() => io.platform(), \"linux\");\n\tconst arch = guardValue(() => io.arch(), \"unknown\");\n\tconst logicalCpuCount = Math.max(\n\t\t1,\n\t\tprobePositiveInteger(() => io.cpuCount(), diagnostics, \"probe.cpu-count.unavailable\") ?? 1,\n\t);\n\n\t// Memory semantics note (§6.4): `process.availableMemory()` is free memory\n\t// still available to the process; `process.constrainedMemory()` is the OS\n\t// limit (0 when unknown, and on some platforms it includes current RSS).\n\t// We record raw values and never subtract RSS from them, so the min-known\n\t// policy stays conservative without double subtraction.\n\tconst processAvailableMemoryBytes = probePositiveNumber(\n\t\t() => io.availableMemory(),\n\t\tdiagnostics,\n\t\t\"probe.available-memory.unavailable\",\n\t);\n\tconst constrainedMemoryBytes = probePositiveNumber(\n\t\t() => io.constrainedMemory(),\n\t\tdiagnostics,\n\t\t\"probe.constrained-memory.unavailable\",\n\t);\n\tconst hostTotalMemoryBytes = probePositiveNumber(\n\t\t() => io.totalmem(),\n\t\tdiagnostics,\n\t\t\"probe.host-total-memory.unavailable\",\n\t);\n\tconst hostFreeMemoryBytes = probePositiveNumber(\n\t\t() => io.freemem(),\n\t\tdiagnostics,\n\t\t\"probe.host-free-memory.unavailable\",\n\t);\n\n\tconst processRssBytes = probePositiveNumber(() => io.rssBytes(), diagnostics, \"probe.rss.unavailable\") ?? 0;\n\tconst heap = guardValue(() => io.heapStatistics(), null);\n\tif (heap === null) {\n\t\tdiagnostics.push(\"probe.heap.unavailable\");\n\t}\n\tconst heapUsedBytes = sanitizeNonNegative(heap?.heapUsedBytes) ?? 0;\n\tconst heapLimitBytes = sanitizeNonNegative(heap?.heapLimitBytes) ?? 0;\n\n\t// Async probes share one deadline and run concurrently so total preflight\n\t// wall time stays ~= maxProbeMs (roadmap §24: p95 <= 300 ms).\n\tconst [disk, cpu] = await Promise.all([\n\t\traceDeadline(() => io.statfs(options?.cwd ?? guardValue(() => process.cwd(), \".\")), maxProbeMs),\n\t\traceDeadline(() => io.sampleSystemCpuPercent(cpuSampleMs), maxProbeMs),\n\t]);\n\n\tlet workspaceAvailableBytes: number | null = null;\n\tif (disk.timedOut) {\n\t\tdiagnostics.push(\"probe.disk.timeout\");\n\t} else if (disk.failed || disk.value === null) {\n\t\tdiagnostics.push(\"probe.disk.unavailable\");\n\t} else {\n\t\t// §6.5: bavail (unprivileged-available), never bfree; clamp before Number().\n\t\tworkspaceAvailableBytes = bigintProductToSafeNumber(disk.value.bavail, disk.value.bsize);\n\t\tif (workspaceAvailableBytes === null) {\n\t\t\tdiagnostics.push(\"probe.disk.unavailable\");\n\t\t}\n\t}\n\n\tlet systemCpuPercent: number | null = null;\n\tif (cpu.timedOut) {\n\t\tdiagnostics.push(\"probe.cpu.timeout\");\n\t} else if (cpu.failed || cpu.value === null || !Number.isFinite(cpu.value)) {\n\t\tdiagnostics.push(\"probe.cpu.unavailable\");\n\t} else {\n\t\tsystemCpuPercent = Math.max(0, Math.min(100, cpu.value));\n\t}\n\n\treturn {\n\t\tschemaVersion: HOST_RESOURCE_SNAPSHOT_VERSION,\n\t\tobservedAt,\n\t\tplatform,\n\t\tarch,\n\t\tlogicalCpuCount,\n\t\tprocessAvailableMemoryBytes,\n\t\tconstrainedMemoryBytes,\n\t\thostTotalMemoryBytes,\n\t\thostFreeMemoryBytes,\n\t\tprocessRssBytes,\n\t\theapUsedBytes,\n\t\theapLimitBytes,\n\t\tsystemCpuPercent,\n\t\tworkspaceAvailableBytes,\n\t\tenvironment: detectEnvironment(io),\n\t\tdiagnostics,\n\t};\n}\n\n/**\n * §6.4 effective memory: the minimum of the known positive candidates\n * (process available, constrained, host free), or `null` when none is known.\n */\nexport function effectiveAvailableMemoryBytes(snapshot: HostResourceSnapshot): number | null {\n\tconst candidates = [\n\t\tsnapshot.processAvailableMemoryBytes,\n\t\tsnapshot.constrainedMemoryBytes,\n\t\tsnapshot.hostFreeMemoryBytes,\n\t].filter((value): value is number => typeof value === \"number\" && Number.isFinite(value) && value > 0);\n\tif (candidates.length === 0) {\n\t\treturn null;\n\t}\n\treturn Math.min(...candidates);\n}\n\n/** Stable content digest of a snapshot (used as `snapshotDigest` in admission decisions). */\nexport function computeHostResourceSnapshotDigest(snapshot: HostResourceSnapshot): string {\n\tconst canonical = JSON.stringify([\n\t\tsnapshot.schemaVersion,\n\t\tsnapshot.observedAt,\n\t\tsnapshot.platform,\n\t\tsnapshot.arch,\n\t\tsnapshot.logicalCpuCount,\n\t\tsnapshot.processAvailableMemoryBytes,\n\t\tsnapshot.constrainedMemoryBytes,\n\t\tsnapshot.hostTotalMemoryBytes,\n\t\tsnapshot.hostFreeMemoryBytes,\n\t\tsnapshot.processRssBytes,\n\t\tsnapshot.heapUsedBytes,\n\t\tsnapshot.heapLimitBytes,\n\t\tsnapshot.systemCpuPercent,\n\t\tsnapshot.workspaceAvailableBytes,\n\t\tsnapshot.environment,\n\t\tsnapshot.diagnostics,\n\t]);\n\treturn createHash(\"sha256\").update(canonical, \"utf8\").digest(\"hex\");\n}\n"]}
@@ -0,0 +1,81 @@
1
+ /**
2
+ * Prompt settlement coordinator (OMK v0.97.x roadmap §16, M4/PR6).
3
+ *
4
+ * `agent_end` is NOT final (§16.1): provider retries, compaction retries,
5
+ * durable-goal continuations, steering/follow-up queues, shards, and child
6
+ * subagents may still run. `prompt_settled` fires only when the reducer's
7
+ * §16.4 conditions all hold, exactly once per promptRunId. Consumers (the
8
+ * M4 completion sound, notifications) must treat it as a UX signal, never a
9
+ * correctness signal (§3 non-goals).
10
+ *
11
+ * The reducer is a pure function over an immutable state (§16.5). Shard and
12
+ * child counters are wired by M5/M6; they participate in the contract now so
13
+ * those slices only add signal call sites.
14
+ */
15
+ export type PromptSettlementOutcome = "completed" | "failed" | "aborted";
16
+ /** §16.2 prompt run identity. */
17
+ export interface PromptRunRef {
18
+ readonly promptRunId: string;
19
+ readonly sessionId: string;
20
+ readonly startedAt: string;
21
+ readonly lineageId?: string;
22
+ }
23
+ /** §16.3 settlement contract. */
24
+ export interface PromptSettledEvent {
25
+ readonly type: "prompt_settled";
26
+ readonly promptRunId: string;
27
+ readonly outcome: PromptSettlementOutcome;
28
+ readonly durationMs: number;
29
+ readonly terminationKind?: string;
30
+ }
31
+ /** §16.5 coordinator state. */
32
+ export interface PromptSettlementState {
33
+ readonly promptRunId: string;
34
+ readonly startedAtEpochMs: number;
35
+ readonly activeProviderAttempts: number;
36
+ readonly activeTools: number;
37
+ readonly activeShards: number;
38
+ readonly activeChildren: number;
39
+ readonly queuedContinuations: number;
40
+ readonly terminal?: {
41
+ readonly outcome: PromptSettlementOutcome;
42
+ readonly terminationKind?: string;
43
+ };
44
+ readonly emitted: boolean;
45
+ }
46
+ export type PromptSettlementSignal = {
47
+ readonly kind: "provider_attempt";
48
+ readonly delta: 1 | -1;
49
+ } | {
50
+ readonly kind: "tool";
51
+ readonly delta: 1 | -1;
52
+ } | {
53
+ readonly kind: "shard";
54
+ readonly delta: 1 | -1;
55
+ } | {
56
+ readonly kind: "child";
57
+ readonly delta: 1 | -1;
58
+ } | {
59
+ readonly kind: "continuation";
60
+ readonly delta: 1 | -1;
61
+ } | {
62
+ readonly kind: "terminal";
63
+ readonly outcome: PromptSettlementOutcome;
64
+ readonly terminationKind?: string;
65
+ } | {
66
+ readonly kind: "emitted";
67
+ };
68
+ export declare function createPromptSettlementState(promptRunId: string, startedAtEpochMs: number): PromptSettlementState;
69
+ /** Pure reducer (§16.5). Counters clamp at zero; terminal and emitted are latching. */
70
+ export declare function reducePromptSettlement(state: PromptSettlementState, signal: PromptSettlementSignal): PromptSettlementState;
71
+ /** §16.4: every condition must hold before `prompt_settled` may emit. */
72
+ export declare function shouldEmitPromptSettled(state: PromptSettlementState): boolean;
73
+ /**
74
+ * Build the event when (and only when) §16.4 holds, marking the state
75
+ * emitted so the same sequence can never yield a second event.
76
+ */
77
+ export declare function settlePromptIfReady(state: PromptSettlementState, nowEpochMs: number): {
78
+ readonly state: PromptSettlementState;
79
+ readonly event: PromptSettledEvent | null;
80
+ };
81
+ //# sourceMappingURL=prompt-settlement.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prompt-settlement.d.ts","sourceRoot":"","sources":["../../src/core/prompt-settlement.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Prompt settlement coordinator (OMK v0.97.x roadmap §16, M4/PR6).\n *\n * `agent_end` is NOT final (§16.1): provider retries, compaction retries,\n * durable-goal continuations, steering/follow-up queues, shards, and child\n * subagents may still run. `prompt_settled` fires only when the reducer's\n * §16.4 conditions all hold, exactly once per promptRunId. Consumers (the\n * M4 completion sound, notifications) must treat it as a UX signal, never a\n * correctness signal (§3 non-goals).\n *\n * The reducer is a pure function over an immutable state (§16.5). Shard and\n * child counters are wired by M5/M6; they participate in the contract now so\n * those slices only add signal call sites.\n */\n\nexport type PromptSettlementOutcome = \"completed\" | \"failed\" | \"aborted\";\n\n/** §16.2 prompt run identity. */\nexport interface PromptRunRef {\n\treadonly promptRunId: string;\n\treadonly sessionId: string;\n\treadonly startedAt: string;\n\treadonly lineageId?: string;\n}\n\n/** §16.3 settlement contract. */\nexport interface PromptSettledEvent {\n\treadonly type: \"prompt_settled\";\n\treadonly promptRunId: string;\n\treadonly outcome: PromptSettlementOutcome;\n\treadonly durationMs: number;\n\treadonly terminationKind?: string;\n}\n\n/** §16.5 coordinator state. */\nexport interface PromptSettlementState {\n\treadonly promptRunId: string;\n\treadonly startedAtEpochMs: number;\n\treadonly activeProviderAttempts: number;\n\treadonly activeTools: number;\n\treadonly activeShards: number;\n\treadonly activeChildren: number;\n\treadonly queuedContinuations: number;\n\treadonly terminal?: {\n\t\treadonly outcome: PromptSettlementOutcome;\n\t\treadonly terminationKind?: string;\n\t};\n\treadonly emitted: boolean;\n}\n\nexport type PromptSettlementSignal =\n\t| { readonly kind: \"provider_attempt\"; readonly delta: 1 | -1 }\n\t| { readonly kind: \"tool\"; readonly delta: 1 | -1 }\n\t| { readonly kind: \"shard\"; readonly delta: 1 | -1 }\n\t| { readonly kind: \"child\"; readonly delta: 1 | -1 }\n\t| { readonly kind: \"continuation\"; readonly delta: 1 | -1 }\n\t| { readonly kind: \"terminal\"; readonly outcome: PromptSettlementOutcome; readonly terminationKind?: string }\n\t| { readonly kind: \"emitted\" };\n\nexport function createPromptSettlementState(promptRunId: string, startedAtEpochMs: number): PromptSettlementState {\n\treturn {\n\t\tpromptRunId,\n\t\tstartedAtEpochMs,\n\t\tactiveProviderAttempts: 0,\n\t\tactiveTools: 0,\n\t\tactiveShards: 0,\n\t\tactiveChildren: 0,\n\t\tqueuedContinuations: 0,\n\t\temitted: false,\n\t};\n}\n\n/** Pure reducer (§16.5). Counters clamp at zero; terminal and emitted are latching. */\nexport function reducePromptSettlement(\n\tstate: PromptSettlementState,\n\tsignal: PromptSettlementSignal,\n): PromptSettlementState {\n\tswitch (signal.kind) {\n\t\tcase \"provider_attempt\":\n\t\t\treturn { ...state, activeProviderAttempts: clampCount(state.activeProviderAttempts + signal.delta) };\n\t\tcase \"tool\":\n\t\t\treturn { ...state, activeTools: clampCount(state.activeTools + signal.delta) };\n\t\tcase \"shard\":\n\t\t\treturn { ...state, activeShards: clampCount(state.activeShards + signal.delta) };\n\t\tcase \"child\":\n\t\t\treturn { ...state, activeChildren: clampCount(state.activeChildren + signal.delta) };\n\t\tcase \"continuation\":\n\t\t\treturn { ...state, queuedContinuations: clampCount(state.queuedContinuations + signal.delta) };\n\t\tcase \"terminal\":\n\t\t\t// First terminal outcome wins; later reclassification never flips it (§15.3).\n\t\t\tif (state.terminal !== undefined) {\n\t\t\t\treturn state;\n\t\t\t}\n\t\t\treturn { ...state, terminal: { outcome: signal.outcome, terminationKind: signal.terminationKind } };\n\t\tcase \"emitted\":\n\t\t\treturn { ...state, emitted: true };\n\t\tdefault: {\n\t\t\tconst exhaustive: never = signal;\n\t\t\treturn exhaustive;\n\t\t}\n\t}\n}\n\n/** §16.4: every condition must hold before `prompt_settled` may emit. */\nexport function shouldEmitPromptSettled(state: PromptSettlementState): boolean {\n\treturn (\n\t\tstate.terminal !== undefined &&\n\t\t!state.emitted &&\n\t\tstate.activeProviderAttempts === 0 &&\n\t\tstate.activeTools === 0 &&\n\t\tstate.activeShards === 0 &&\n\t\tstate.activeChildren === 0 &&\n\t\tstate.queuedContinuations === 0\n\t);\n}\n\n/**\n * Build the event when (and only when) §16.4 holds, marking the state\n * emitted so the same sequence can never yield a second event.\n */\nexport function settlePromptIfReady(\n\tstate: PromptSettlementState,\n\tnowEpochMs: number,\n): { readonly state: PromptSettlementState; readonly event: PromptSettledEvent | null } {\n\tif (!shouldEmitPromptSettled(state) || state.terminal === undefined) {\n\t\treturn { state, event: null };\n\t}\n\tconst event: PromptSettledEvent = {\n\t\ttype: \"prompt_settled\",\n\t\tpromptRunId: state.promptRunId,\n\t\toutcome: state.terminal.outcome,\n\t\tdurationMs: Math.max(0, nowEpochMs - state.startedAtEpochMs),\n\t\t...(state.terminal.terminationKind !== undefined ? { terminationKind: state.terminal.terminationKind } : {}),\n\t};\n\treturn { state: reducePromptSettlement(state, { kind: \"emitted\" }), event };\n}\n\nfunction clampCount(value: number): number {\n\treturn Math.max(0, value);\n}\n"]}
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Prompt settlement coordinator (OMK v0.97.x roadmap §16, M4/PR6).
3
+ *
4
+ * `agent_end` is NOT final (§16.1): provider retries, compaction retries,
5
+ * durable-goal continuations, steering/follow-up queues, shards, and child
6
+ * subagents may still run. `prompt_settled` fires only when the reducer's
7
+ * §16.4 conditions all hold, exactly once per promptRunId. Consumers (the
8
+ * M4 completion sound, notifications) must treat it as a UX signal, never a
9
+ * correctness signal (§3 non-goals).
10
+ *
11
+ * The reducer is a pure function over an immutable state (§16.5). Shard and
12
+ * child counters are wired by M5/M6; they participate in the contract now so
13
+ * those slices only add signal call sites.
14
+ */
15
+ export function createPromptSettlementState(promptRunId, startedAtEpochMs) {
16
+ return {
17
+ promptRunId,
18
+ startedAtEpochMs,
19
+ activeProviderAttempts: 0,
20
+ activeTools: 0,
21
+ activeShards: 0,
22
+ activeChildren: 0,
23
+ queuedContinuations: 0,
24
+ emitted: false,
25
+ };
26
+ }
27
+ /** Pure reducer (§16.5). Counters clamp at zero; terminal and emitted are latching. */
28
+ export function reducePromptSettlement(state, signal) {
29
+ switch (signal.kind) {
30
+ case "provider_attempt":
31
+ return { ...state, activeProviderAttempts: clampCount(state.activeProviderAttempts + signal.delta) };
32
+ case "tool":
33
+ return { ...state, activeTools: clampCount(state.activeTools + signal.delta) };
34
+ case "shard":
35
+ return { ...state, activeShards: clampCount(state.activeShards + signal.delta) };
36
+ case "child":
37
+ return { ...state, activeChildren: clampCount(state.activeChildren + signal.delta) };
38
+ case "continuation":
39
+ return { ...state, queuedContinuations: clampCount(state.queuedContinuations + signal.delta) };
40
+ case "terminal":
41
+ // First terminal outcome wins; later reclassification never flips it (§15.3).
42
+ if (state.terminal !== undefined) {
43
+ return state;
44
+ }
45
+ return { ...state, terminal: { outcome: signal.outcome, terminationKind: signal.terminationKind } };
46
+ case "emitted":
47
+ return { ...state, emitted: true };
48
+ default: {
49
+ const exhaustive = signal;
50
+ return exhaustive;
51
+ }
52
+ }
53
+ }
54
+ /** §16.4: every condition must hold before `prompt_settled` may emit. */
55
+ export function shouldEmitPromptSettled(state) {
56
+ return (state.terminal !== undefined &&
57
+ !state.emitted &&
58
+ state.activeProviderAttempts === 0 &&
59
+ state.activeTools === 0 &&
60
+ state.activeShards === 0 &&
61
+ state.activeChildren === 0 &&
62
+ state.queuedContinuations === 0);
63
+ }
64
+ /**
65
+ * Build the event when (and only when) §16.4 holds, marking the state
66
+ * emitted so the same sequence can never yield a second event.
67
+ */
68
+ export function settlePromptIfReady(state, nowEpochMs) {
69
+ if (!shouldEmitPromptSettled(state) || state.terminal === undefined) {
70
+ return { state, event: null };
71
+ }
72
+ const event = {
73
+ type: "prompt_settled",
74
+ promptRunId: state.promptRunId,
75
+ outcome: state.terminal.outcome,
76
+ durationMs: Math.max(0, nowEpochMs - state.startedAtEpochMs),
77
+ ...(state.terminal.terminationKind !== undefined ? { terminationKind: state.terminal.terminationKind } : {}),
78
+ };
79
+ return { state: reducePromptSettlement(state, { kind: "emitted" }), event };
80
+ }
81
+ function clampCount(value) {
82
+ return Math.max(0, value);
83
+ }
84
+ //# sourceMappingURL=prompt-settlement.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prompt-settlement.js","sourceRoot":"","sources":["../../src/core/prompt-settlement.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Prompt settlement coordinator (OMK v0.97.x roadmap §16, M4/PR6).\n *\n * `agent_end` is NOT final (§16.1): provider retries, compaction retries,\n * durable-goal continuations, steering/follow-up queues, shards, and child\n * subagents may still run. `prompt_settled` fires only when the reducer's\n * §16.4 conditions all hold, exactly once per promptRunId. Consumers (the\n * M4 completion sound, notifications) must treat it as a UX signal, never a\n * correctness signal (§3 non-goals).\n *\n * The reducer is a pure function over an immutable state (§16.5). Shard and\n * child counters are wired by M5/M6; they participate in the contract now so\n * those slices only add signal call sites.\n */\n\nexport type PromptSettlementOutcome = \"completed\" | \"failed\" | \"aborted\";\n\n/** §16.2 prompt run identity. */\nexport interface PromptRunRef {\n\treadonly promptRunId: string;\n\treadonly sessionId: string;\n\treadonly startedAt: string;\n\treadonly lineageId?: string;\n}\n\n/** §16.3 settlement contract. */\nexport interface PromptSettledEvent {\n\treadonly type: \"prompt_settled\";\n\treadonly promptRunId: string;\n\treadonly outcome: PromptSettlementOutcome;\n\treadonly durationMs: number;\n\treadonly terminationKind?: string;\n}\n\n/** §16.5 coordinator state. */\nexport interface PromptSettlementState {\n\treadonly promptRunId: string;\n\treadonly startedAtEpochMs: number;\n\treadonly activeProviderAttempts: number;\n\treadonly activeTools: number;\n\treadonly activeShards: number;\n\treadonly activeChildren: number;\n\treadonly queuedContinuations: number;\n\treadonly terminal?: {\n\t\treadonly outcome: PromptSettlementOutcome;\n\t\treadonly terminationKind?: string;\n\t};\n\treadonly emitted: boolean;\n}\n\nexport type PromptSettlementSignal =\n\t| { readonly kind: \"provider_attempt\"; readonly delta: 1 | -1 }\n\t| { readonly kind: \"tool\"; readonly delta: 1 | -1 }\n\t| { readonly kind: \"shard\"; readonly delta: 1 | -1 }\n\t| { readonly kind: \"child\"; readonly delta: 1 | -1 }\n\t| { readonly kind: \"continuation\"; readonly delta: 1 | -1 }\n\t| { readonly kind: \"terminal\"; readonly outcome: PromptSettlementOutcome; readonly terminationKind?: string }\n\t| { readonly kind: \"emitted\" };\n\nexport function createPromptSettlementState(promptRunId: string, startedAtEpochMs: number): PromptSettlementState {\n\treturn {\n\t\tpromptRunId,\n\t\tstartedAtEpochMs,\n\t\tactiveProviderAttempts: 0,\n\t\tactiveTools: 0,\n\t\tactiveShards: 0,\n\t\tactiveChildren: 0,\n\t\tqueuedContinuations: 0,\n\t\temitted: false,\n\t};\n}\n\n/** Pure reducer (§16.5). Counters clamp at zero; terminal and emitted are latching. */\nexport function reducePromptSettlement(\n\tstate: PromptSettlementState,\n\tsignal: PromptSettlementSignal,\n): PromptSettlementState {\n\tswitch (signal.kind) {\n\t\tcase \"provider_attempt\":\n\t\t\treturn { ...state, activeProviderAttempts: clampCount(state.activeProviderAttempts + signal.delta) };\n\t\tcase \"tool\":\n\t\t\treturn { ...state, activeTools: clampCount(state.activeTools + signal.delta) };\n\t\tcase \"shard\":\n\t\t\treturn { ...state, activeShards: clampCount(state.activeShards + signal.delta) };\n\t\tcase \"child\":\n\t\t\treturn { ...state, activeChildren: clampCount(state.activeChildren + signal.delta) };\n\t\tcase \"continuation\":\n\t\t\treturn { ...state, queuedContinuations: clampCount(state.queuedContinuations + signal.delta) };\n\t\tcase \"terminal\":\n\t\t\t// First terminal outcome wins; later reclassification never flips it (§15.3).\n\t\t\tif (state.terminal !== undefined) {\n\t\t\t\treturn state;\n\t\t\t}\n\t\t\treturn { ...state, terminal: { outcome: signal.outcome, terminationKind: signal.terminationKind } };\n\t\tcase \"emitted\":\n\t\t\treturn { ...state, emitted: true };\n\t\tdefault: {\n\t\t\tconst exhaustive: never = signal;\n\t\t\treturn exhaustive;\n\t\t}\n\t}\n}\n\n/** §16.4: every condition must hold before `prompt_settled` may emit. */\nexport function shouldEmitPromptSettled(state: PromptSettlementState): boolean {\n\treturn (\n\t\tstate.terminal !== undefined &&\n\t\t!state.emitted &&\n\t\tstate.activeProviderAttempts === 0 &&\n\t\tstate.activeTools === 0 &&\n\t\tstate.activeShards === 0 &&\n\t\tstate.activeChildren === 0 &&\n\t\tstate.queuedContinuations === 0\n\t);\n}\n\n/**\n * Build the event when (and only when) §16.4 holds, marking the state\n * emitted so the same sequence can never yield a second event.\n */\nexport function settlePromptIfReady(\n\tstate: PromptSettlementState,\n\tnowEpochMs: number,\n): { readonly state: PromptSettlementState; readonly event: PromptSettledEvent | null } {\n\tif (!shouldEmitPromptSettled(state) || state.terminal === undefined) {\n\t\treturn { state, event: null };\n\t}\n\tconst event: PromptSettledEvent = {\n\t\ttype: \"prompt_settled\",\n\t\tpromptRunId: state.promptRunId,\n\t\toutcome: state.terminal.outcome,\n\t\tdurationMs: Math.max(0, nowEpochMs - state.startedAtEpochMs),\n\t\t...(state.terminal.terminationKind !== undefined ? { terminationKind: state.terminal.terminationKind } : {}),\n\t};\n\treturn { state: reducePromptSettlement(state, { kind: \"emitted\" }), event };\n}\n\nfunction clampCount(value: number): number {\n\treturn Math.max(0, value);\n}\n"]}