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.
- package/CHANGELOG.md +41 -0
- package/README.md +12 -2
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +1 -0
- package/dist/cli/args.js.map +1 -1
- package/dist/commands/resource-doctor-cli.d.ts +22 -0
- package/dist/commands/resource-doctor-cli.d.ts.map +1 -0
- package/dist/commands/resource-doctor-cli.js +98 -0
- package/dist/commands/resource-doctor-cli.js.map +1 -0
- package/dist/core/agent-session.d.ts +37 -1
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +289 -5
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/completion-sound.d.ts +109 -0
- package/dist/core/completion-sound.d.ts.map +1 -0
- package/dist/core/completion-sound.js +165 -0
- package/dist/core/completion-sound.js.map +1 -0
- package/dist/core/context-budget-system-prompt-items.d.ts.map +1 -1
- package/dist/core/context-budget-system-prompt-items.js +2 -2
- package/dist/core/context-budget-system-prompt-items.js.map +1 -1
- package/dist/core/host-resource-probe.d.ts +63 -0
- package/dist/core/host-resource-probe.d.ts.map +1 -0
- package/dist/core/host-resource-probe.js +126 -0
- package/dist/core/host-resource-probe.js.map +1 -0
- package/dist/core/host-resource-snapshot.d.ts +54 -0
- package/dist/core/host-resource-snapshot.d.ts.map +1 -0
- package/dist/core/host-resource-snapshot.js +139 -0
- package/dist/core/host-resource-snapshot.js.map +1 -0
- package/dist/core/prompt-settlement.d.ts +81 -0
- package/dist/core/prompt-settlement.d.ts.map +1 -0
- package/dist/core/prompt-settlement.js +84 -0
- package/dist/core/prompt-settlement.js.map +1 -0
- package/dist/core/provider-resilience.d.ts.map +1 -1
- package/dist/core/provider-resilience.js +1 -1
- package/dist/core/provider-resilience.js.map +1 -1
- package/dist/core/provider-retry.d.ts +7 -0
- package/dist/core/provider-retry.d.ts.map +1 -1
- package/dist/core/provider-retry.js +24 -0
- package/dist/core/provider-retry.js.map +1 -1
- package/dist/core/provider-usage.d.ts +15 -2
- package/dist/core/provider-usage.d.ts.map +1 -1
- package/dist/core/provider-usage.js +110 -2
- package/dist/core/provider-usage.js.map +1 -1
- package/dist/core/reasoning-router-resolver.d.ts.map +1 -1
- package/dist/core/reasoning-router-resolver.js +1 -1
- package/dist/core/reasoning-router-resolver.js.map +1 -1
- package/dist/core/resource-admission-config.d.ts +55 -0
- package/dist/core/resource-admission-config.d.ts.map +1 -0
- package/dist/core/resource-admission-config.js +97 -0
- package/dist/core/resource-admission-config.js.map +1 -0
- package/dist/core/resource-admission.d.ts +81 -0
- package/dist/core/resource-admission.d.ts.map +1 -0
- package/dist/core/resource-admission.js +128 -0
- package/dist/core/resource-admission.js.map +1 -0
- package/dist/core/resource-governor-format.d.ts +50 -0
- package/dist/core/resource-governor-format.d.ts.map +1 -0
- package/dist/core/resource-governor-format.js +113 -0
- package/dist/core/resource-governor-format.js.map +1 -0
- package/dist/core/resource-governor-settings.d.ts +56 -0
- package/dist/core/resource-governor-settings.d.ts.map +1 -0
- package/dist/core/resource-governor-settings.js +89 -0
- package/dist/core/resource-governor-settings.js.map +1 -0
- package/dist/core/resource-observation-journal.d.ts +74 -0
- package/dist/core/resource-observation-journal.d.ts.map +1 -0
- package/dist/core/resource-observation-journal.js +198 -0
- package/dist/core/resource-observation-journal.js.map +1 -0
- package/dist/core/resource-safety-gate.d.ts +54 -0
- package/dist/core/resource-safety-gate.d.ts.map +1 -0
- package/dist/core/resource-safety-gate.js +51 -0
- package/dist/core/resource-safety-gate.js.map +1 -0
- package/dist/core/run-journal.d.ts.map +1 -1
- package/dist/core/run-journal.js +13 -1
- package/dist/core/run-journal.js.map +1 -1
- package/dist/core/run-resource-lease.d.ts +73 -0
- package/dist/core/run-resource-lease.d.ts.map +1 -0
- package/dist/core/run-resource-lease.js +78 -0
- package/dist/core/run-resource-lease.js.map +1 -0
- package/dist/core/session-bash-service.d.ts +12 -0
- package/dist/core/session-bash-service.d.ts.map +1 -1
- package/dist/core/session-bash-service.js +14 -0
- package/dist/core/session-bash-service.js.map +1 -1
- package/dist/core/session-system-prompt.d.ts +2 -0
- package/dist/core/session-system-prompt.d.ts.map +1 -1
- package/dist/core/session-system-prompt.js +2 -0
- package/dist/core/session-system-prompt.js.map +1 -1
- package/dist/core/session-termination.d.ts +7 -2
- package/dist/core/session-termination.d.ts.map +1 -1
- package/dist/core/session-termination.js +29 -1
- package/dist/core/session-termination.js.map +1 -1
- package/dist/core/settings-manager.d.ts +12 -0
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +36 -0
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/slash-commands.d.ts.map +1 -1
- package/dist/core/slash-commands.js +1 -0
- package/dist/core/slash-commands.js.map +1 -1
- package/dist/core/subagent-lane-launcher.d.ts +64 -0
- package/dist/core/subagent-lane-launcher.d.ts.map +1 -0
- package/dist/core/subagent-lane-launcher.js +107 -0
- package/dist/core/subagent-lane-launcher.js.map +1 -0
- package/dist/core/system-cpu-sampler.d.ts +45 -0
- package/dist/core/system-cpu-sampler.d.ts.map +1 -0
- package/dist/core/system-cpu-sampler.js +95 -0
- package/dist/core/system-cpu-sampler.js.map +1 -0
- package/dist/core/system-prompt.d.ts.map +1 -1
- package/dist/core/system-prompt.js +5 -8
- package/dist/core/system-prompt.js.map +1 -1
- package/dist/core/workload-classifier.d.ts +32 -0
- package/dist/core/workload-classifier.d.ts.map +1 -0
- package/dist/core/workload-classifier.js +66 -0
- package/dist/core/workload-classifier.js.map +1 -0
- package/dist/core/workload-family-matcher.d.ts +3 -0
- package/dist/core/workload-family-matcher.d.ts.map +1 -0
- package/dist/core/workload-family-matcher.js +126 -0
- package/dist/core/workload-family-matcher.js.map +1 -0
- package/dist/core/workload-permit-pool.d.ts +78 -0
- package/dist/core/workload-permit-pool.d.ts.map +1 -0
- package/dist/core/workload-permit-pool.js +138 -0
- package/dist/core/workload-permit-pool.js.map +1 -0
- package/dist/core/workload-shard-executor.d.ts +68 -0
- package/dist/core/workload-shard-executor.d.ts.map +1 -0
- package/dist/core/workload-shard-executor.js +156 -0
- package/dist/core/workload-shard-executor.js.map +1 -0
- package/dist/core/workload-shard-plan.d.ts +81 -0
- package/dist/core/workload-shard-plan.d.ts.map +1 -0
- package/dist/core/workload-shard-plan.js +183 -0
- package/dist/core/workload-shard-plan.js.map +1 -0
- package/dist/core/workload-shard-runner.d.ts +11 -0
- package/dist/core/workload-shard-runner.d.ts.map +1 -0
- package/dist/core/workload-shard-runner.js +98 -0
- package/dist/core/workload-shard-runner.js.map +1 -0
- package/dist/core/workload-shard-store.d.ts +44 -0
- package/dist/core/workload-shard-store.d.ts.map +1 -0
- package/dist/core/workload-shard-store.js +189 -0
- package/dist/core/workload-shard-store.js.map +1 -0
- package/dist/core/workload-sharders.d.ts +47 -0
- package/dist/core/workload-sharders.d.ts.map +1 -0
- package/dist/core/workload-sharders.js +162 -0
- package/dist/core/workload-sharders.js.map +1 -0
- package/dist/core/workload-shell-scan.d.ts +12 -0
- package/dist/core/workload-shell-scan.d.ts.map +1 -0
- package/dist/core/workload-shell-scan.js +79 -0
- package/dist/core/workload-shell-scan.js.map +1 -0
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +6 -0
- package/dist/main.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +4 -0
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +81 -0
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/docs/providers.md +1 -1
- package/docs/sdk.md +4 -0
- package/docs/settings.md +64 -2
- package/docs/skills.md +16 -0
- package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
- package/examples/extensions/custom-provider-anthropic/package.json +1 -1
- package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
- package/examples/extensions/gondolin/package-lock.json +2 -2
- package/examples/extensions/gondolin/package.json +1 -1
- package/examples/extensions/sandbox/package-lock.json +2 -2
- package/examples/extensions/sandbox/package.json +1 -1
- package/examples/extensions/subagent/agent-capability-router.test.ts +7 -0
- package/examples/extensions/subagent/domain-profiles.ts +0 -1
- package/examples/extensions/with-deps/package-lock.json +2 -2
- package/examples/extensions/with-deps/package.json +1 -1
- package/npm-shrinkwrap.json +18 -18
- package/package.json +7 -9
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Governor rollout mode (§7.4). Mode enforcement (probe/record/cap wiring)
|
|
3
|
+
* lands in later milestones; the type is part of the layer-1 contract.
|
|
4
|
+
*/
|
|
5
|
+
export type ResourceGovernorMode = "off" | "observe" | "adaptive" | "strict";
|
|
6
|
+
export interface ResourceAdmissionThresholds {
|
|
7
|
+
readonly constrainedAvailableMemoryMiB: number;
|
|
8
|
+
readonly criticalAvailableMemoryMiB: number;
|
|
9
|
+
readonly constrainedDiskFreeMiB: number;
|
|
10
|
+
readonly criticalDiskFreeMiB: number;
|
|
11
|
+
readonly constrainedHeapRatio: number;
|
|
12
|
+
readonly criticalHeapRatio: number;
|
|
13
|
+
readonly busyCpuPercent: number;
|
|
14
|
+
}
|
|
15
|
+
export interface ResourcePressureCaps {
|
|
16
|
+
readonly maxToolConcurrency: number;
|
|
17
|
+
readonly maxParallelLanes: number;
|
|
18
|
+
readonly maxHeavyProcesses: number;
|
|
19
|
+
}
|
|
20
|
+
export interface ResourceAdmissionCapTable {
|
|
21
|
+
readonly normal: ResourcePressureCaps;
|
|
22
|
+
readonly constrained: ResourcePressureCaps;
|
|
23
|
+
readonly critical: ResourcePressureCaps;
|
|
24
|
+
}
|
|
25
|
+
export interface ResourceAdmissionConfig {
|
|
26
|
+
readonly thresholds: ResourceAdmissionThresholds;
|
|
27
|
+
readonly caps: ResourceAdmissionCapTable;
|
|
28
|
+
}
|
|
29
|
+
/** §6.7 default thresholds. */
|
|
30
|
+
export declare const DEFAULT_RESOURCE_ADMISSION_THRESHOLDS: ResourceAdmissionThresholds;
|
|
31
|
+
/** §7.2 default caps per pressure tier. */
|
|
32
|
+
export declare const DEFAULT_RESOURCE_ADMISSION_CAP_TABLE: ResourceAdmissionCapTable;
|
|
33
|
+
export declare const DEFAULT_RESOURCE_ADMISSION_CONFIG: ResourceAdmissionConfig;
|
|
34
|
+
export type ResourceAdmissionConfigOverrides = {
|
|
35
|
+
thresholds?: Partial<ResourceAdmissionThresholds>;
|
|
36
|
+
caps?: {
|
|
37
|
+
normal?: Partial<ResourcePressureCaps>;
|
|
38
|
+
constrained?: Partial<ResourcePressureCaps>;
|
|
39
|
+
critical?: Partial<ResourcePressureCaps>;
|
|
40
|
+
};
|
|
41
|
+
};
|
|
42
|
+
export interface ResolvedResourceAdmissionConfig {
|
|
43
|
+
readonly config: ResourceAdmissionConfig;
|
|
44
|
+
/** §18.1: explicit errors over silent clamps. Non-empty means fail-closed defaults were applied. */
|
|
45
|
+
readonly errors: readonly string[];
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Validate overrides against §18.1 and merge them over the defaults.
|
|
49
|
+
* Any validation error fails closed: the full default config is returned
|
|
50
|
+
* together with the explicit error list (startup diagnostics are the
|
|
51
|
+
* caller's responsibility in later slices).
|
|
52
|
+
*/
|
|
53
|
+
export declare function resolveResourceAdmissionConfig(overrides?: ResourceAdmissionConfigOverrides): ResolvedResourceAdmissionConfig;
|
|
54
|
+
export declare function validateResourceAdmissionConfig(config: ResourceAdmissionConfig): readonly string[];
|
|
55
|
+
//# sourceMappingURL=resource-admission-config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resource-admission-config.d.ts","sourceRoot":"","sources":["../../src/core/resource-admission-config.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,MAAM,MAAM,oBAAoB,GAAG,KAAK,GAAG,SAAS,GAAG,UAAU,GAAG,QAAQ,CAAC;AAE7E,MAAM,WAAW,2BAA2B;IAC3C,QAAQ,CAAC,6BAA6B,EAAE,MAAM,CAAC;IAC/C,QAAQ,CAAC,0BAA0B,EAAE,MAAM,CAAC;IAC5C,QAAQ,CAAC,sBAAsB,EAAE,MAAM,CAAC;IACxC,QAAQ,CAAC,mBAAmB,EAAE,MAAM,CAAC;IACrC,QAAQ,CAAC,oBAAoB,EAAE,MAAM,CAAC;IACtC,QAAQ,CAAC,iBAAiB,EAAE,MAAM,CAAC;IACnC,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;CAChC;AAED,MAAM,WAAW,oBAAoB;IACpC,QAAQ,CAAC,kBAAkB,EAAE,MAAM,CAAC;IACpC,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;IAClC,QAAQ,CAAC,iBAAiB,EAAE,MAAM,CAAC;CACnC;AAED,MAAM,WAAW,yBAAyB;IACzC,QAAQ,CAAC,MAAM,EAAE,oBAAoB,CAAC;IACtC,QAAQ,CAAC,WAAW,EAAE,oBAAoB,CAAC;IAC3C,QAAQ,CAAC,QAAQ,EAAE,oBAAoB,CAAC;CACxC;AAED,MAAM,WAAW,uBAAuB;IACvC,QAAQ,CAAC,UAAU,EAAE,2BAA2B,CAAC;IACjD,QAAQ,CAAC,IAAI,EAAE,yBAAyB,CAAC;CACzC;AAED,gCAA+B;AAC/B,eAAO,MAAM,qCAAqC,EAAE,2BAQnD,CAAC;AAEF,4CAA2C;AAC3C,eAAO,MAAM,oCAAoC,EAAE,yBAIlD,CAAC;AAEF,eAAO,MAAM,iCAAiC,EAAE,uBAG/C,CAAC;AAEF,MAAM,MAAM,gCAAgC,GAAG;IAC9C,UAAU,CAAC,EAAE,OAAO,CAAC,2BAA2B,CAAC,CAAC;IAClD,IAAI,CAAC,EAAE;QACN,MAAM,CAAC,EAAE,OAAO,CAAC,oBAAoB,CAAC,CAAC;QACvC,WAAW,CAAC,EAAE,OAAO,CAAC,oBAAoB,CAAC,CAAC;QAC5C,QAAQ,CAAC,EAAE,OAAO,CAAC,oBAAoB,CAAC,CAAC;KACzC,CAAC;CACF,CAAC;AAEF,MAAM,WAAW,+BAA+B;IAC/C,QAAQ,CAAC,MAAM,EAAE,uBAAuB,CAAC;IACzC,qGAAoG;IACpG,QAAQ,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;CACnC;AAKD;;;;;GAKG;AACH,wBAAgB,8BAA8B,CAC7C,SAAS,CAAC,EAAE,gCAAgC,GAC1C,+BAA+B,CAcjC;AAED,wBAAgB,+BAA+B,CAAC,MAAM,EAAE,uBAAuB,GAAG,SAAS,MAAM,EAAE,CA0DlG","sourcesContent":["/**\n * Governor rollout mode (§7.4). Mode enforcement (probe/record/cap wiring)\n * lands in later milestones; the type is part of the layer-1 contract.\n */\nexport type ResourceGovernorMode = \"off\" | \"observe\" | \"adaptive\" | \"strict\";\n\nexport interface ResourceAdmissionThresholds {\n\treadonly constrainedAvailableMemoryMiB: number;\n\treadonly criticalAvailableMemoryMiB: number;\n\treadonly constrainedDiskFreeMiB: number;\n\treadonly criticalDiskFreeMiB: number;\n\treadonly constrainedHeapRatio: number;\n\treadonly criticalHeapRatio: number;\n\treadonly busyCpuPercent: number;\n}\n\nexport interface ResourcePressureCaps {\n\treadonly maxToolConcurrency: number;\n\treadonly maxParallelLanes: number;\n\treadonly maxHeavyProcesses: number;\n}\n\nexport interface ResourceAdmissionCapTable {\n\treadonly normal: ResourcePressureCaps;\n\treadonly constrained: ResourcePressureCaps;\n\treadonly critical: ResourcePressureCaps;\n}\n\nexport interface ResourceAdmissionConfig {\n\treadonly thresholds: ResourceAdmissionThresholds;\n\treadonly caps: ResourceAdmissionCapTable;\n}\n\n/** §6.7 default thresholds. */\nexport const DEFAULT_RESOURCE_ADMISSION_THRESHOLDS: ResourceAdmissionThresholds = {\n\tconstrainedAvailableMemoryMiB: 1536,\n\tcriticalAvailableMemoryMiB: 512,\n\tconstrainedDiskFreeMiB: 4096,\n\tcriticalDiskFreeMiB: 1024,\n\tconstrainedHeapRatio: 0.75,\n\tcriticalHeapRatio: 0.85,\n\tbusyCpuPercent: 85,\n};\n\n/** §7.2 default caps per pressure tier. */\nexport const DEFAULT_RESOURCE_ADMISSION_CAP_TABLE: ResourceAdmissionCapTable = {\n\tnormal: { maxToolConcurrency: 4, maxParallelLanes: 4, maxHeavyProcesses: 2 },\n\tconstrained: { maxToolConcurrency: 2, maxParallelLanes: 2, maxHeavyProcesses: 1 },\n\tcritical: { maxToolConcurrency: 1, maxParallelLanes: 1, maxHeavyProcesses: 1 },\n};\n\nexport const DEFAULT_RESOURCE_ADMISSION_CONFIG: ResourceAdmissionConfig = {\n\tthresholds: DEFAULT_RESOURCE_ADMISSION_THRESHOLDS,\n\tcaps: DEFAULT_RESOURCE_ADMISSION_CAP_TABLE,\n};\n\nexport type ResourceAdmissionConfigOverrides = {\n\tthresholds?: Partial<ResourceAdmissionThresholds>;\n\tcaps?: {\n\t\tnormal?: Partial<ResourcePressureCaps>;\n\t\tconstrained?: Partial<ResourcePressureCaps>;\n\t\tcritical?: Partial<ResourcePressureCaps>;\n\t};\n};\n\nexport interface ResolvedResourceAdmissionConfig {\n\treadonly config: ResourceAdmissionConfig;\n\t/** §18.1: explicit errors over silent clamps. Non-empty means fail-closed defaults were applied. */\n\treadonly errors: readonly string[];\n}\n\nconst MIN_CAP = 1;\nconst MAX_CAP = 64;\n\n/**\n * Validate overrides against §18.1 and merge them over the defaults.\n * Any validation error fails closed: the full default config is returned\n * together with the explicit error list (startup diagnostics are the\n * caller's responsibility in later slices).\n */\nexport function resolveResourceAdmissionConfig(\n\toverrides?: ResourceAdmissionConfigOverrides,\n): ResolvedResourceAdmissionConfig {\n\tconst merged: ResourceAdmissionConfig = {\n\t\tthresholds: { ...DEFAULT_RESOURCE_ADMISSION_THRESHOLDS, ...overrides?.thresholds },\n\t\tcaps: {\n\t\t\tnormal: { ...DEFAULT_RESOURCE_ADMISSION_CAP_TABLE.normal, ...overrides?.caps?.normal },\n\t\t\tconstrained: { ...DEFAULT_RESOURCE_ADMISSION_CAP_TABLE.constrained, ...overrides?.caps?.constrained },\n\t\t\tcritical: { ...DEFAULT_RESOURCE_ADMISSION_CAP_TABLE.critical, ...overrides?.caps?.critical },\n\t\t},\n\t};\n\tconst errors = validateResourceAdmissionConfig(merged);\n\tif (errors.length > 0) {\n\t\treturn { config: DEFAULT_RESOURCE_ADMISSION_CONFIG, errors };\n\t}\n\treturn { config: merged, errors };\n}\n\nexport function validateResourceAdmissionConfig(config: ResourceAdmissionConfig): readonly string[] {\n\tconst errors: string[] = [];\n\tconst { thresholds, caps } = config;\n\n\tconst positiveMiB: ReadonlyArray<readonly [string, number]> = [\n\t\t[\"constrainedAvailableMemoryMiB\", thresholds.constrainedAvailableMemoryMiB],\n\t\t[\"criticalAvailableMemoryMiB\", thresholds.criticalAvailableMemoryMiB],\n\t\t[\"constrainedDiskFreeMiB\", thresholds.constrainedDiskFreeMiB],\n\t\t[\"criticalDiskFreeMiB\", thresholds.criticalDiskFreeMiB],\n\t];\n\tfor (const [name, value] of positiveMiB) {\n\t\tif (!Number.isSafeInteger(value) || value <= 0) {\n\t\t\terrors.push(`resourceGovernor.${name}: must be a positive safe integer, got ${String(value)}`);\n\t\t}\n\t}\n\tif (thresholds.criticalAvailableMemoryMiB > thresholds.constrainedAvailableMemoryMiB) {\n\t\terrors.push(\"resourceGovernor.criticalAvailableMemoryMiB: must be <= constrainedAvailableMemoryMiB\");\n\t}\n\tif (thresholds.criticalDiskFreeMiB > thresholds.constrainedDiskFreeMiB) {\n\t\terrors.push(\"resourceGovernor.criticalDiskFreeMiB: must be <= constrainedDiskFreeMiB\");\n\t}\n\tfor (const [name, value] of [\n\t\t[\"constrainedHeapRatio\", thresholds.constrainedHeapRatio],\n\t\t[\"criticalHeapRatio\", thresholds.criticalHeapRatio],\n\t] as const) {\n\t\tif (typeof value !== \"number\" || !Number.isFinite(value) || value <= 0 || value > 1) {\n\t\t\terrors.push(`resourceGovernor.${name}: must be within (0, 1], got ${String(value)}`);\n\t\t}\n\t}\n\tif (thresholds.criticalHeapRatio < thresholds.constrainedHeapRatio) {\n\t\terrors.push(\"resourceGovernor.criticalHeapRatio: must be >= constrainedHeapRatio\");\n\t}\n\tif (\n\t\t!Number.isFinite(thresholds.busyCpuPercent) ||\n\t\tthresholds.busyCpuPercent < 1 ||\n\t\tthresholds.busyCpuPercent > 100\n\t) {\n\t\terrors.push(`resourceGovernor.busyCpuPercent: must be within [1, 100], got ${String(thresholds.busyCpuPercent)}`);\n\t}\n\n\tconst capFields = [\"maxToolConcurrency\", \"maxParallelLanes\", \"maxHeavyProcesses\"] as const;\n\tfor (const tier of [\"normal\", \"constrained\", \"critical\"] as const) {\n\t\tfor (const field of capFields) {\n\t\t\tconst value = caps[tier][field];\n\t\t\tif (!Number.isSafeInteger(value) || value < MIN_CAP || value > MAX_CAP) {\n\t\t\t\terrors.push(\n\t\t\t\t\t`resourceGovernor.caps.${tier}.${field}: must be an integer within [${MIN_CAP}, ${MAX_CAP}], got ${String(value)}`,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t}\n\t// §4.4 / §18.1 monotonicity: normalCap >= constrainedCap >= criticalCap.\n\tfor (const field of capFields) {\n\t\tif (caps.normal[field] < caps.constrained[field] || caps.constrained[field] < caps.critical[field]) {\n\t\t\terrors.push(`resourceGovernor.caps.${field}: requires normal >= constrained >= critical`);\n\t\t}\n\t}\n\treturn errors;\n}\n"]}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/** §6.7 default thresholds. */
|
|
2
|
+
export const DEFAULT_RESOURCE_ADMISSION_THRESHOLDS = {
|
|
3
|
+
constrainedAvailableMemoryMiB: 1536,
|
|
4
|
+
criticalAvailableMemoryMiB: 512,
|
|
5
|
+
constrainedDiskFreeMiB: 4096,
|
|
6
|
+
criticalDiskFreeMiB: 1024,
|
|
7
|
+
constrainedHeapRatio: 0.75,
|
|
8
|
+
criticalHeapRatio: 0.85,
|
|
9
|
+
busyCpuPercent: 85,
|
|
10
|
+
};
|
|
11
|
+
/** §7.2 default caps per pressure tier. */
|
|
12
|
+
export const DEFAULT_RESOURCE_ADMISSION_CAP_TABLE = {
|
|
13
|
+
normal: { maxToolConcurrency: 4, maxParallelLanes: 4, maxHeavyProcesses: 2 },
|
|
14
|
+
constrained: { maxToolConcurrency: 2, maxParallelLanes: 2, maxHeavyProcesses: 1 },
|
|
15
|
+
critical: { maxToolConcurrency: 1, maxParallelLanes: 1, maxHeavyProcesses: 1 },
|
|
16
|
+
};
|
|
17
|
+
export const DEFAULT_RESOURCE_ADMISSION_CONFIG = {
|
|
18
|
+
thresholds: DEFAULT_RESOURCE_ADMISSION_THRESHOLDS,
|
|
19
|
+
caps: DEFAULT_RESOURCE_ADMISSION_CAP_TABLE,
|
|
20
|
+
};
|
|
21
|
+
const MIN_CAP = 1;
|
|
22
|
+
const MAX_CAP = 64;
|
|
23
|
+
/**
|
|
24
|
+
* Validate overrides against §18.1 and merge them over the defaults.
|
|
25
|
+
* Any validation error fails closed: the full default config is returned
|
|
26
|
+
* together with the explicit error list (startup diagnostics are the
|
|
27
|
+
* caller's responsibility in later slices).
|
|
28
|
+
*/
|
|
29
|
+
export function resolveResourceAdmissionConfig(overrides) {
|
|
30
|
+
const merged = {
|
|
31
|
+
thresholds: { ...DEFAULT_RESOURCE_ADMISSION_THRESHOLDS, ...overrides?.thresholds },
|
|
32
|
+
caps: {
|
|
33
|
+
normal: { ...DEFAULT_RESOURCE_ADMISSION_CAP_TABLE.normal, ...overrides?.caps?.normal },
|
|
34
|
+
constrained: { ...DEFAULT_RESOURCE_ADMISSION_CAP_TABLE.constrained, ...overrides?.caps?.constrained },
|
|
35
|
+
critical: { ...DEFAULT_RESOURCE_ADMISSION_CAP_TABLE.critical, ...overrides?.caps?.critical },
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
const errors = validateResourceAdmissionConfig(merged);
|
|
39
|
+
if (errors.length > 0) {
|
|
40
|
+
return { config: DEFAULT_RESOURCE_ADMISSION_CONFIG, errors };
|
|
41
|
+
}
|
|
42
|
+
return { config: merged, errors };
|
|
43
|
+
}
|
|
44
|
+
export function validateResourceAdmissionConfig(config) {
|
|
45
|
+
const errors = [];
|
|
46
|
+
const { thresholds, caps } = config;
|
|
47
|
+
const positiveMiB = [
|
|
48
|
+
["constrainedAvailableMemoryMiB", thresholds.constrainedAvailableMemoryMiB],
|
|
49
|
+
["criticalAvailableMemoryMiB", thresholds.criticalAvailableMemoryMiB],
|
|
50
|
+
["constrainedDiskFreeMiB", thresholds.constrainedDiskFreeMiB],
|
|
51
|
+
["criticalDiskFreeMiB", thresholds.criticalDiskFreeMiB],
|
|
52
|
+
];
|
|
53
|
+
for (const [name, value] of positiveMiB) {
|
|
54
|
+
if (!Number.isSafeInteger(value) || value <= 0) {
|
|
55
|
+
errors.push(`resourceGovernor.${name}: must be a positive safe integer, got ${String(value)}`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
if (thresholds.criticalAvailableMemoryMiB > thresholds.constrainedAvailableMemoryMiB) {
|
|
59
|
+
errors.push("resourceGovernor.criticalAvailableMemoryMiB: must be <= constrainedAvailableMemoryMiB");
|
|
60
|
+
}
|
|
61
|
+
if (thresholds.criticalDiskFreeMiB > thresholds.constrainedDiskFreeMiB) {
|
|
62
|
+
errors.push("resourceGovernor.criticalDiskFreeMiB: must be <= constrainedDiskFreeMiB");
|
|
63
|
+
}
|
|
64
|
+
for (const [name, value] of [
|
|
65
|
+
["constrainedHeapRatio", thresholds.constrainedHeapRatio],
|
|
66
|
+
["criticalHeapRatio", thresholds.criticalHeapRatio],
|
|
67
|
+
]) {
|
|
68
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value <= 0 || value > 1) {
|
|
69
|
+
errors.push(`resourceGovernor.${name}: must be within (0, 1], got ${String(value)}`);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
if (thresholds.criticalHeapRatio < thresholds.constrainedHeapRatio) {
|
|
73
|
+
errors.push("resourceGovernor.criticalHeapRatio: must be >= constrainedHeapRatio");
|
|
74
|
+
}
|
|
75
|
+
if (!Number.isFinite(thresholds.busyCpuPercent) ||
|
|
76
|
+
thresholds.busyCpuPercent < 1 ||
|
|
77
|
+
thresholds.busyCpuPercent > 100) {
|
|
78
|
+
errors.push(`resourceGovernor.busyCpuPercent: must be within [1, 100], got ${String(thresholds.busyCpuPercent)}`);
|
|
79
|
+
}
|
|
80
|
+
const capFields = ["maxToolConcurrency", "maxParallelLanes", "maxHeavyProcesses"];
|
|
81
|
+
for (const tier of ["normal", "constrained", "critical"]) {
|
|
82
|
+
for (const field of capFields) {
|
|
83
|
+
const value = caps[tier][field];
|
|
84
|
+
if (!Number.isSafeInteger(value) || value < MIN_CAP || value > MAX_CAP) {
|
|
85
|
+
errors.push(`resourceGovernor.caps.${tier}.${field}: must be an integer within [${MIN_CAP}, ${MAX_CAP}], got ${String(value)}`);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
// §4.4 / §18.1 monotonicity: normalCap >= constrainedCap >= criticalCap.
|
|
90
|
+
for (const field of capFields) {
|
|
91
|
+
if (caps.normal[field] < caps.constrained[field] || caps.constrained[field] < caps.critical[field]) {
|
|
92
|
+
errors.push(`resourceGovernor.caps.${field}: requires normal >= constrained >= critical`);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return errors;
|
|
96
|
+
}
|
|
97
|
+
//# sourceMappingURL=resource-admission-config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resource-admission-config.js","sourceRoot":"","sources":["../../src/core/resource-admission-config.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Governor rollout mode (§7.4). Mode enforcement (probe/record/cap wiring)\n * lands in later milestones; the type is part of the layer-1 contract.\n */\nexport type ResourceGovernorMode = \"off\" | \"observe\" | \"adaptive\" | \"strict\";\n\nexport interface ResourceAdmissionThresholds {\n\treadonly constrainedAvailableMemoryMiB: number;\n\treadonly criticalAvailableMemoryMiB: number;\n\treadonly constrainedDiskFreeMiB: number;\n\treadonly criticalDiskFreeMiB: number;\n\treadonly constrainedHeapRatio: number;\n\treadonly criticalHeapRatio: number;\n\treadonly busyCpuPercent: number;\n}\n\nexport interface ResourcePressureCaps {\n\treadonly maxToolConcurrency: number;\n\treadonly maxParallelLanes: number;\n\treadonly maxHeavyProcesses: number;\n}\n\nexport interface ResourceAdmissionCapTable {\n\treadonly normal: ResourcePressureCaps;\n\treadonly constrained: ResourcePressureCaps;\n\treadonly critical: ResourcePressureCaps;\n}\n\nexport interface ResourceAdmissionConfig {\n\treadonly thresholds: ResourceAdmissionThresholds;\n\treadonly caps: ResourceAdmissionCapTable;\n}\n\n/** §6.7 default thresholds. */\nexport const DEFAULT_RESOURCE_ADMISSION_THRESHOLDS: ResourceAdmissionThresholds = {\n\tconstrainedAvailableMemoryMiB: 1536,\n\tcriticalAvailableMemoryMiB: 512,\n\tconstrainedDiskFreeMiB: 4096,\n\tcriticalDiskFreeMiB: 1024,\n\tconstrainedHeapRatio: 0.75,\n\tcriticalHeapRatio: 0.85,\n\tbusyCpuPercent: 85,\n};\n\n/** §7.2 default caps per pressure tier. */\nexport const DEFAULT_RESOURCE_ADMISSION_CAP_TABLE: ResourceAdmissionCapTable = {\n\tnormal: { maxToolConcurrency: 4, maxParallelLanes: 4, maxHeavyProcesses: 2 },\n\tconstrained: { maxToolConcurrency: 2, maxParallelLanes: 2, maxHeavyProcesses: 1 },\n\tcritical: { maxToolConcurrency: 1, maxParallelLanes: 1, maxHeavyProcesses: 1 },\n};\n\nexport const DEFAULT_RESOURCE_ADMISSION_CONFIG: ResourceAdmissionConfig = {\n\tthresholds: DEFAULT_RESOURCE_ADMISSION_THRESHOLDS,\n\tcaps: DEFAULT_RESOURCE_ADMISSION_CAP_TABLE,\n};\n\nexport type ResourceAdmissionConfigOverrides = {\n\tthresholds?: Partial<ResourceAdmissionThresholds>;\n\tcaps?: {\n\t\tnormal?: Partial<ResourcePressureCaps>;\n\t\tconstrained?: Partial<ResourcePressureCaps>;\n\t\tcritical?: Partial<ResourcePressureCaps>;\n\t};\n};\n\nexport interface ResolvedResourceAdmissionConfig {\n\treadonly config: ResourceAdmissionConfig;\n\t/** §18.1: explicit errors over silent clamps. Non-empty means fail-closed defaults were applied. */\n\treadonly errors: readonly string[];\n}\n\nconst MIN_CAP = 1;\nconst MAX_CAP = 64;\n\n/**\n * Validate overrides against §18.1 and merge them over the defaults.\n * Any validation error fails closed: the full default config is returned\n * together with the explicit error list (startup diagnostics are the\n * caller's responsibility in later slices).\n */\nexport function resolveResourceAdmissionConfig(\n\toverrides?: ResourceAdmissionConfigOverrides,\n): ResolvedResourceAdmissionConfig {\n\tconst merged: ResourceAdmissionConfig = {\n\t\tthresholds: { ...DEFAULT_RESOURCE_ADMISSION_THRESHOLDS, ...overrides?.thresholds },\n\t\tcaps: {\n\t\t\tnormal: { ...DEFAULT_RESOURCE_ADMISSION_CAP_TABLE.normal, ...overrides?.caps?.normal },\n\t\t\tconstrained: { ...DEFAULT_RESOURCE_ADMISSION_CAP_TABLE.constrained, ...overrides?.caps?.constrained },\n\t\t\tcritical: { ...DEFAULT_RESOURCE_ADMISSION_CAP_TABLE.critical, ...overrides?.caps?.critical },\n\t\t},\n\t};\n\tconst errors = validateResourceAdmissionConfig(merged);\n\tif (errors.length > 0) {\n\t\treturn { config: DEFAULT_RESOURCE_ADMISSION_CONFIG, errors };\n\t}\n\treturn { config: merged, errors };\n}\n\nexport function validateResourceAdmissionConfig(config: ResourceAdmissionConfig): readonly string[] {\n\tconst errors: string[] = [];\n\tconst { thresholds, caps } = config;\n\n\tconst positiveMiB: ReadonlyArray<readonly [string, number]> = [\n\t\t[\"constrainedAvailableMemoryMiB\", thresholds.constrainedAvailableMemoryMiB],\n\t\t[\"criticalAvailableMemoryMiB\", thresholds.criticalAvailableMemoryMiB],\n\t\t[\"constrainedDiskFreeMiB\", thresholds.constrainedDiskFreeMiB],\n\t\t[\"criticalDiskFreeMiB\", thresholds.criticalDiskFreeMiB],\n\t];\n\tfor (const [name, value] of positiveMiB) {\n\t\tif (!Number.isSafeInteger(value) || value <= 0) {\n\t\t\terrors.push(`resourceGovernor.${name}: must be a positive safe integer, got ${String(value)}`);\n\t\t}\n\t}\n\tif (thresholds.criticalAvailableMemoryMiB > thresholds.constrainedAvailableMemoryMiB) {\n\t\terrors.push(\"resourceGovernor.criticalAvailableMemoryMiB: must be <= constrainedAvailableMemoryMiB\");\n\t}\n\tif (thresholds.criticalDiskFreeMiB > thresholds.constrainedDiskFreeMiB) {\n\t\terrors.push(\"resourceGovernor.criticalDiskFreeMiB: must be <= constrainedDiskFreeMiB\");\n\t}\n\tfor (const [name, value] of [\n\t\t[\"constrainedHeapRatio\", thresholds.constrainedHeapRatio],\n\t\t[\"criticalHeapRatio\", thresholds.criticalHeapRatio],\n\t] as const) {\n\t\tif (typeof value !== \"number\" || !Number.isFinite(value) || value <= 0 || value > 1) {\n\t\t\terrors.push(`resourceGovernor.${name}: must be within (0, 1], got ${String(value)}`);\n\t\t}\n\t}\n\tif (thresholds.criticalHeapRatio < thresholds.constrainedHeapRatio) {\n\t\terrors.push(\"resourceGovernor.criticalHeapRatio: must be >= constrainedHeapRatio\");\n\t}\n\tif (\n\t\t!Number.isFinite(thresholds.busyCpuPercent) ||\n\t\tthresholds.busyCpuPercent < 1 ||\n\t\tthresholds.busyCpuPercent > 100\n\t) {\n\t\terrors.push(`resourceGovernor.busyCpuPercent: must be within [1, 100], got ${String(thresholds.busyCpuPercent)}`);\n\t}\n\n\tconst capFields = [\"maxToolConcurrency\", \"maxParallelLanes\", \"maxHeavyProcesses\"] as const;\n\tfor (const tier of [\"normal\", \"constrained\", \"critical\"] as const) {\n\t\tfor (const field of capFields) {\n\t\t\tconst value = caps[tier][field];\n\t\t\tif (!Number.isSafeInteger(value) || value < MIN_CAP || value > MAX_CAP) {\n\t\t\t\terrors.push(\n\t\t\t\t\t`resourceGovernor.caps.${tier}.${field}: must be an integer within [${MIN_CAP}, ${MAX_CAP}], got ${String(value)}`,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t}\n\t// §4.4 / §18.1 monotonicity: normalCap >= constrainedCap >= criticalCap.\n\tfor (const field of capFields) {\n\t\tif (caps.normal[field] < caps.constrained[field] || caps.constrained[field] < caps.critical[field]) {\n\t\t\terrors.push(`resourceGovernor.caps.${field}: requires normal >= constrained >= critical`);\n\t\t}\n\t}\n\treturn errors;\n}\n"]}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { type HostResourceSnapshot } from "./host-resource-snapshot.ts";
|
|
2
|
+
import { type ResourceAdmissionConfig, type ResourceAdmissionThresholds } from "./resource-admission-config.ts";
|
|
3
|
+
export type { ResolvedResourceAdmissionConfig, ResourceAdmissionCapTable, ResourceAdmissionConfig, ResourceAdmissionConfigOverrides, ResourceAdmissionThresholds, ResourceGovernorMode, ResourcePressureCaps, } from "./resource-admission-config.ts";
|
|
4
|
+
export { DEFAULT_RESOURCE_ADMISSION_CAP_TABLE, DEFAULT_RESOURCE_ADMISSION_CONFIG, DEFAULT_RESOURCE_ADMISSION_THRESHOLDS, resolveResourceAdmissionConfig, validateResourceAdmissionConfig, } from "./resource-admission-config.ts";
|
|
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 declare const RESOURCE_ADMISSION_VERSION: 1;
|
|
25
|
+
export type ResourcePressure = "normal" | "constrained" | "critical";
|
|
26
|
+
export type ResourceAdmissionAction = "allow" | "throttle" | "defer-heavy";
|
|
27
|
+
export type ResourceReasonCode = "resource.memory.low" | "resource.memory.critical" | "resource.disk.low" | "resource.disk.critical" | "resource.cpu.busy" | "resource.heap.high" | "resource.heap.critical" | "resource.probe.partial" | "resource.probe.timeout";
|
|
28
|
+
export interface ResourceAdmissionDecision {
|
|
29
|
+
readonly schemaVersion: typeof RESOURCE_ADMISSION_VERSION;
|
|
30
|
+
readonly decisionId: string;
|
|
31
|
+
readonly snapshotDigest: string;
|
|
32
|
+
readonly pressure: ResourcePressure;
|
|
33
|
+
readonly action: ResourceAdmissionAction;
|
|
34
|
+
readonly maxToolConcurrency: number;
|
|
35
|
+
readonly maxParallelLanes: number;
|
|
36
|
+
readonly maxHeavyProcesses: number;
|
|
37
|
+
readonly reasons: readonly ResourceReasonCode[];
|
|
38
|
+
readonly decidedAt: string;
|
|
39
|
+
}
|
|
40
|
+
/** Bounded model-facing hint (§6.2). Never expose the raw snapshot to the model. */
|
|
41
|
+
export interface ModelResourceBudgetHint {
|
|
42
|
+
readonly pressure: ResourcePressure;
|
|
43
|
+
readonly maxToolConcurrency: number;
|
|
44
|
+
readonly maxParallelLanes: number;
|
|
45
|
+
readonly maxHeavyProcesses: number;
|
|
46
|
+
readonly reasons: readonly ResourceReasonCode[];
|
|
47
|
+
}
|
|
48
|
+
/** Total order for pressure levels: `normal` (0) < `constrained` (1) < `critical` (2). */
|
|
49
|
+
export declare function resourcePressureRank(pressure: ResourcePressure): number;
|
|
50
|
+
export interface ResourcePressureEvaluation {
|
|
51
|
+
readonly pressure: ResourcePressure;
|
|
52
|
+
readonly reasons: readonly ResourceReasonCode[];
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Derive pressure and reason codes from a snapshot (§4.3, §6.6, §6.7).
|
|
56
|
+
* Pure and monotonic: strictly worse inputs never yield a better pressure.
|
|
57
|
+
*/
|
|
58
|
+
export declare function evaluateResourcePressure(snapshot: HostResourceSnapshot, thresholds?: ResourceAdmissionThresholds): ResourcePressureEvaluation;
|
|
59
|
+
/**
|
|
60
|
+
* Existing user settings that admission must never exceed (§7.3).
|
|
61
|
+
* `0` or `undefined` means "unlimited" and is limited to the admission cap.
|
|
62
|
+
*/
|
|
63
|
+
export interface ConfiguredResourceCaps {
|
|
64
|
+
readonly maxToolConcurrency?: number;
|
|
65
|
+
readonly maxParallelLanes?: number;
|
|
66
|
+
readonly maxHeavyProcesses?: number;
|
|
67
|
+
}
|
|
68
|
+
export interface ResourceAdmissionInput {
|
|
69
|
+
readonly snapshot: HostResourceSnapshot;
|
|
70
|
+
readonly config?: ResourceAdmissionConfig;
|
|
71
|
+
readonly configuredCaps?: ConfiguredResourceCaps;
|
|
72
|
+
/** Injectable for deterministic tests; defaults to a UUID-based id. */
|
|
73
|
+
readonly decisionId?: string;
|
|
74
|
+
/** Injectable ISO timestamp; defaults to the current time. */
|
|
75
|
+
readonly decidedAt?: string;
|
|
76
|
+
}
|
|
77
|
+
/** Map a snapshot to an immutable admission decision (§7.1-§7.3). */
|
|
78
|
+
export declare function decideResourceAdmission(input: ResourceAdmissionInput): ResourceAdmissionDecision;
|
|
79
|
+
/** Bounded hint for the model (§6.2). Carries no raw host values. */
|
|
80
|
+
export declare function toModelResourceBudgetHint(decision: ResourceAdmissionDecision): ModelResourceBudgetHint;
|
|
81
|
+
//# sourceMappingURL=resource-admission.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resource-admission.d.ts","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,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"]}
|