@coool/opencode-adaptive 0.0.0-stage → 1.0.1
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/LICENSE +21 -0
- package/README.md +99 -2
- package/dist/adapters/codex-capacity/index.d.ts +4 -0
- package/dist/adapters/codex-capacity/index.js +5 -0
- package/dist/adapters/codex-capacity/index.js.map +1 -0
- package/dist/adapters/codex-capacity/parse.d.ts +28 -0
- package/dist/adapters/codex-capacity/parse.js +146 -0
- package/dist/adapters/codex-capacity/parse.js.map +1 -0
- package/dist/adapters/codex-capacity/reading.d.ts +9 -0
- package/dist/adapters/codex-capacity/reading.js +45 -0
- package/dist/adapters/codex-capacity/reading.js.map +1 -0
- package/dist/adapters/codex-capacity/sensor.d.ts +23 -0
- package/dist/adapters/codex-capacity/sensor.js +30 -0
- package/dist/adapters/codex-capacity/sensor.js.map +1 -0
- package/dist/adapters/codex-capacity/types.d.ts +31 -0
- package/dist/adapters/codex-capacity/types.js +18 -0
- package/dist/adapters/codex-capacity/types.js.map +1 -0
- package/dist/adapters/opencode/escalation-tool.d.ts +51 -0
- package/dist/adapters/opencode/escalation-tool.js +61 -0
- package/dist/adapters/opencode/escalation-tool.js.map +1 -0
- package/dist/adapters/opencode/events.d.ts +23 -0
- package/dist/adapters/opencode/events.js +68 -0
- package/dist/adapters/opencode/events.js.map +1 -0
- package/dist/adapters/opencode/hooks.d.ts +34 -0
- package/dist/adapters/opencode/hooks.js +29 -0
- package/dist/adapters/opencode/hooks.js.map +1 -0
- package/dist/adapters/opencode/index.d.ts +16 -0
- package/dist/adapters/opencode/index.js +9 -0
- package/dist/adapters/opencode/index.js.map +1 -0
- package/dist/adapters/opencode/inventory.d.ts +30 -0
- package/dist/adapters/opencode/inventory.js +53 -0
- package/dist/adapters/opencode/inventory.js.map +1 -0
- package/dist/adapters/opencode/report.d.ts +91 -0
- package/dist/adapters/opencode/report.js +145 -0
- package/dist/adapters/opencode/report.js.map +1 -0
- package/dist/adapters/opencode/session-ops.d.ts +39 -0
- package/dist/adapters/opencode/session-ops.js +32 -0
- package/dist/adapters/opencode/session-ops.js.map +1 -0
- package/dist/adapters/opencode/storage.d.ts +15 -0
- package/dist/adapters/opencode/storage.js +8 -0
- package/dist/adapters/opencode/storage.js.map +1 -0
- package/dist/adapters/opencode/tier-tool.d.ts +37 -0
- package/dist/adapters/opencode/tier-tool.js +72 -0
- package/dist/adapters/opencode/tier-tool.js.map +1 -0
- package/dist/adapters/opencode/types.d.ts +101 -0
- package/dist/adapters/opencode/types.js +2 -0
- package/dist/adapters/opencode/types.js.map +1 -0
- package/dist/core/compatibility/gate.d.ts +7 -0
- package/dist/core/compatibility/gate.js +51 -0
- package/dist/core/compatibility/gate.js.map +1 -0
- package/dist/core/compatibility/index.d.ts +2 -0
- package/dist/core/compatibility/index.js +3 -0
- package/dist/core/compatibility/index.js.map +1 -0
- package/dist/core/compatibility/types.d.ts +53 -0
- package/dist/core/compatibility/types.js +27 -0
- package/dist/core/compatibility/types.js.map +1 -0
- package/dist/core/scheduler/admit.d.ts +6 -0
- package/dist/core/scheduler/admit.js +154 -0
- package/dist/core/scheduler/admit.js.map +1 -0
- package/dist/core/scheduler/escalate.d.ts +6 -0
- package/dist/core/scheduler/escalate.js +45 -0
- package/dist/core/scheduler/escalate.js.map +1 -0
- package/dist/core/scheduler/index.d.ts +4 -0
- package/dist/core/scheduler/index.js +5 -0
- package/dist/core/scheduler/index.js.map +1 -0
- package/dist/core/scheduler/mode.d.ts +8 -0
- package/dist/core/scheduler/mode.js +10 -0
- package/dist/core/scheduler/mode.js.map +1 -0
- package/dist/core/scheduler/types.d.ts +136 -0
- package/dist/core/scheduler/types.js +75 -0
- package/dist/core/scheduler/types.js.map +1 -0
- package/dist/core/tier-policy/index.d.ts +3 -0
- package/dist/core/tier-policy/index.js +4 -0
- package/dist/core/tier-policy/index.js.map +1 -0
- package/dist/core/tier-policy/resolve.d.ts +18 -0
- package/dist/core/tier-policy/resolve.js +52 -0
- package/dist/core/tier-policy/resolve.js.map +1 -0
- package/dist/core/tier-policy/tier-list.d.ts +10 -0
- package/dist/core/tier-policy/tier-list.js +23 -0
- package/dist/core/tier-policy/tier-list.js.map +1 -0
- package/dist/core/tier-policy/types.d.ts +86 -0
- package/dist/core/tier-policy/types.js +49 -0
- package/dist/core/tier-policy/types.js.map +1 -0
- package/dist/core/work-unit/index.d.ts +3 -0
- package/dist/core/work-unit/index.js +4 -0
- package/dist/core/work-unit/index.js.map +1 -0
- package/dist/core/work-unit/interruption.d.ts +7 -0
- package/dist/core/work-unit/interruption.js +12 -0
- package/dist/core/work-unit/interruption.js.map +1 -0
- package/dist/core/work-unit/tracker.d.ts +53 -0
- package/dist/core/work-unit/tracker.js +156 -0
- package/dist/core/work-unit/tracker.js.map +1 -0
- package/dist/core/work-unit/types.d.ts +151 -0
- package/dist/core/work-unit/types.js +23 -0
- package/dist/core/work-unit/types.js.map +1 -0
- package/dist/execution/continuation/execute.d.ts +18 -0
- package/dist/execution/continuation/execute.js +31 -0
- package/dist/execution/continuation/execute.js.map +1 -0
- package/dist/execution/continuation/index.d.ts +4 -0
- package/dist/execution/continuation/index.js +4 -0
- package/dist/execution/continuation/index.js.map +1 -0
- package/dist/execution/continuation/plan.d.ts +12 -0
- package/dist/execution/continuation/plan.js +18 -0
- package/dist/execution/continuation/plan.js.map +1 -0
- package/dist/execution/continuation/prompts.d.ts +7 -0
- package/dist/execution/continuation/prompts.js +8 -0
- package/dist/execution/continuation/prompts.js.map +1 -0
- package/dist/execution/continuation/types.d.ts +68 -0
- package/dist/execution/continuation/types.js +15 -0
- package/dist/execution/continuation/types.js.map +1 -0
- package/dist/execution/escalation/index.d.ts +1 -0
- package/dist/execution/escalation/index.js +2 -0
- package/dist/execution/escalation/index.js.map +1 -0
- package/dist/execution/escalation/types.d.ts +30 -0
- package/dist/execution/escalation/types.js +49 -0
- package/dist/execution/escalation/types.js.map +1 -0
- package/dist/execution/index.d.ts +4 -0
- package/dist/execution/index.js +5 -0
- package/dist/execution/index.js.map +1 -0
- package/dist/execution/parking/index.d.ts +2 -0
- package/dist/execution/parking/index.js +2 -0
- package/dist/execution/parking/index.js.map +1 -0
- package/dist/execution/parking/plan.d.ts +39 -0
- package/dist/execution/parking/plan.js +34 -0
- package/dist/execution/parking/plan.js.map +1 -0
- package/dist/execution/retry/decide.d.ts +29 -0
- package/dist/execution/retry/decide.js +75 -0
- package/dist/execution/retry/decide.js.map +1 -0
- package/dist/execution/retry/index.d.ts +2 -0
- package/dist/execution/retry/index.js +3 -0
- package/dist/execution/retry/index.js.map +1 -0
- package/dist/execution/retry/types.d.ts +64 -0
- package/dist/execution/retry/types.js +9 -0
- package/dist/execution/retry/types.js.map +1 -0
- package/dist/gui-experimental.cjs +1 -0
- package/dist/gui.d.ts +16 -0
- package/dist/gui.js +73 -0
- package/dist/gui.js.map +1 -0
- package/dist/health/capacity/index.d.ts +3 -0
- package/dist/health/capacity/index.js +4 -0
- package/dist/health/capacity/index.js.map +1 -0
- package/dist/health/capacity/merge.d.ts +57 -0
- package/dist/health/capacity/merge.js +154 -0
- package/dist/health/capacity/merge.js.map +1 -0
- package/dist/health/capacity/registry.d.ts +40 -0
- package/dist/health/capacity/registry.js +106 -0
- package/dist/health/capacity/registry.js.map +1 -0
- package/dist/health/capacity/types.d.ts +73 -0
- package/dist/health/capacity/types.js +6 -0
- package/dist/health/capacity/types.js.map +1 -0
- package/dist/health/circuit-breaker/breaker.d.ts +91 -0
- package/dist/health/circuit-breaker/breaker.js +150 -0
- package/dist/health/circuit-breaker/breaker.js.map +1 -0
- package/dist/health/circuit-breaker/index.d.ts +2 -0
- package/dist/health/circuit-breaker/index.js +3 -0
- package/dist/health/circuit-breaker/index.js.map +1 -0
- package/dist/health/circuit-breaker/types.d.ts +52 -0
- package/dist/health/circuit-breaker/types.js +13 -0
- package/dist/health/circuit-breaker/types.js.map +1 -0
- package/dist/health/classifier/classify.d.ts +8 -0
- package/dist/health/classifier/classify.js +245 -0
- package/dist/health/classifier/classify.js.map +1 -0
- package/dist/health/classifier/index.d.ts +2 -0
- package/dist/health/classifier/index.js +3 -0
- package/dist/health/classifier/index.js.map +1 -0
- package/dist/health/classifier/types.d.ts +71 -0
- package/dist/health/classifier/types.js +27 -0
- package/dist/health/classifier/types.js.map +1 -0
- package/dist/health/index.d.ts +4 -0
- package/dist/health/index.js +5 -0
- package/dist/health/index.js.map +1 -0
- package/dist/health/resource-health/index.d.ts +4 -0
- package/dist/health/resource-health/index.js +5 -0
- package/dist/health/resource-health/index.js.map +1 -0
- package/dist/health/resource-health/key.d.ts +18 -0
- package/dist/health/resource-health/key.js +54 -0
- package/dist/health/resource-health/key.js.map +1 -0
- package/dist/health/resource-health/registry.d.ts +46 -0
- package/dist/health/resource-health/registry.js +276 -0
- package/dist/health/resource-health/registry.js.map +1 -0
- package/dist/health/resource-health/transitions.d.ts +9 -0
- package/dist/health/resource-health/transitions.js +157 -0
- package/dist/health/resource-health/transitions.js.map +1 -0
- package/dist/health/resource-health/types.d.ts +122 -0
- package/dist/health/resource-health/types.js +44 -0
- package/dist/health/resource-health/types.js.map +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +105 -0
- package/dist/index.js.map +1 -0
- package/dist/plugin-id.d.ts +2 -0
- package/dist/plugin-id.js +3 -0
- package/dist/plugin-id.js.map +1 -0
- package/dist/rpc/definition.d.ts +123 -0
- package/dist/rpc/definition.js +130 -0
- package/dist/rpc/definition.js.map +1 -0
- package/dist/rpc/handlers.d.ts +53 -0
- package/dist/rpc/handlers.js +125 -0
- package/dist/rpc/handlers.js.map +1 -0
- package/dist/rpc/status.d.ts +42 -0
- package/dist/rpc/status.js +45 -0
- package/dist/rpc/status.js.map +1 -0
- package/dist/rpc.d.ts +10 -0
- package/dist/rpc.js +8 -0
- package/dist/rpc.js.map +1 -0
- package/dist/state/capacity/index.d.ts +3 -0
- package/dist/state/capacity/index.js +4 -0
- package/dist/state/capacity/index.js.map +1 -0
- package/dist/state/capacity/parse.d.ts +2 -0
- package/dist/state/capacity/parse.js +122 -0
- package/dist/state/capacity/parse.js.map +1 -0
- package/dist/state/capacity/store.d.ts +18 -0
- package/dist/state/capacity/store.js +78 -0
- package/dist/state/capacity/store.js.map +1 -0
- package/dist/state/capacity/types.d.ts +37 -0
- package/dist/state/capacity/types.js +11 -0
- package/dist/state/capacity/types.js.map +1 -0
- package/dist/state/health/index.d.ts +4 -0
- package/dist/state/health/index.js +4 -0
- package/dist/state/health/index.js.map +1 -0
- package/dist/state/health/parse.d.ts +2 -0
- package/dist/state/health/parse.js +137 -0
- package/dist/state/health/parse.js.map +1 -0
- package/dist/state/health/store.d.ts +17 -0
- package/dist/state/health/store.js +69 -0
- package/dist/state/health/store.js.map +1 -0
- package/dist/state/health/types.d.ts +23 -0
- package/dist/state/health/types.js +5 -0
- package/dist/state/health/types.js.map +1 -0
- package/dist/state/parked-work/index.d.ts +4 -0
- package/dist/state/parked-work/index.js +4 -0
- package/dist/state/parked-work/index.js.map +1 -0
- package/dist/state/parked-work/parse.d.ts +6 -0
- package/dist/state/parked-work/parse.js +83 -0
- package/dist/state/parked-work/parse.js.map +1 -0
- package/dist/state/parked-work/store.d.ts +17 -0
- package/dist/state/parked-work/store.js +98 -0
- package/dist/state/parked-work/store.js.map +1 -0
- package/dist/state/parked-work/types.d.ts +41 -0
- package/dist/state/parked-work/types.js +7 -0
- package/dist/state/parked-work/types.js.map +1 -0
- package/dist/state/receipts/bound.d.ts +14 -0
- package/dist/state/receipts/bound.js +77 -0
- package/dist/state/receipts/bound.js.map +1 -0
- package/dist/state/receipts/index.d.ts +6 -0
- package/dist/state/receipts/index.js +5 -0
- package/dist/state/receipts/index.js.map +1 -0
- package/dist/state/receipts/parse.d.ts +6 -0
- package/dist/state/receipts/parse.js +226 -0
- package/dist/state/receipts/parse.js.map +1 -0
- package/dist/state/receipts/store.d.ts +34 -0
- package/dist/state/receipts/store.js +148 -0
- package/dist/state/receipts/store.js.map +1 -0
- package/dist/state/receipts/types.d.ts +61 -0
- package/dist/state/receipts/types.js +12 -0
- package/dist/state/receipts/types.js.map +1 -0
- package/dist/state/session-modes/index.d.ts +4 -0
- package/dist/state/session-modes/index.js +4 -0
- package/dist/state/session-modes/index.js.map +1 -0
- package/dist/state/session-modes/parse.d.ts +8 -0
- package/dist/state/session-modes/parse.js +62 -0
- package/dist/state/session-modes/parse.js.map +1 -0
- package/dist/state/session-modes/store.d.ts +20 -0
- package/dist/state/session-modes/store.js +87 -0
- package/dist/state/session-modes/store.js.map +1 -0
- package/dist/state/session-modes/types.d.ts +41 -0
- package/dist/state/session-modes/types.js +7 -0
- package/dist/state/session-modes/types.js.map +1 -0
- package/dist/state/settings/index.d.ts +4 -0
- package/dist/state/settings/index.js +4 -0
- package/dist/state/settings/index.js.map +1 -0
- package/dist/state/settings/parse.d.ts +33 -0
- package/dist/state/settings/parse.js +261 -0
- package/dist/state/settings/parse.js.map +1 -0
- package/dist/state/settings/store.d.ts +21 -0
- package/dist/state/settings/store.js +65 -0
- package/dist/state/settings/store.js.map +1 -0
- package/dist/state/settings/types.d.ts +62 -0
- package/dist/state/settings/types.js +12 -0
- package/dist/state/settings/types.js.map +1 -0
- package/dist/state/tier-requests/index.d.ts +4 -0
- package/dist/state/tier-requests/index.js +4 -0
- package/dist/state/tier-requests/index.js.map +1 -0
- package/dist/state/tier-requests/parse.d.ts +3 -0
- package/dist/state/tier-requests/parse.js +53 -0
- package/dist/state/tier-requests/parse.js.map +1 -0
- package/dist/state/tier-requests/store.d.ts +27 -0
- package/dist/state/tier-requests/store.js +96 -0
- package/dist/state/tier-requests/store.js.map +1 -0
- package/dist/state/tier-requests/types.d.ts +30 -0
- package/dist/state/tier-requests/types.js +7 -0
- package/dist/state/tier-requests/types.js.map +1 -0
- package/dist/supervisor/index.d.ts +4 -0
- package/dist/supervisor/index.js +4 -0
- package/dist/supervisor/index.js.map +1 -0
- package/dist/supervisor/log.d.ts +10 -0
- package/dist/supervisor/log.js +14 -0
- package/dist/supervisor/log.js.map +1 -0
- package/dist/supervisor/supervisor.d.ts +86 -0
- package/dist/supervisor/supervisor.js +1629 -0
- package/dist/supervisor/supervisor.js.map +1 -0
- package/dist/supervisor/types.d.ts +49 -0
- package/dist/supervisor/types.js +3 -0
- package/dist/supervisor/types.js.map +1 -0
- package/dist/supervisor/view.d.ts +3 -0
- package/dist/supervisor/view.js +4 -0
- package/dist/supervisor/view.js.map +1 -0
- package/dist/tui.d.ts +15 -0
- package/dist/tui.js +256 -0
- package/dist/tui.js.map +1 -0
- package/dist/ui/gui/api.d.ts +17 -0
- package/dist/ui/gui/api.js +21 -0
- package/dist/ui/gui/api.js.map +1 -0
- package/dist/ui/gui/view.d.ts +26 -0
- package/dist/ui/gui/view.js +49 -0
- package/dist/ui/gui/view.js.map +1 -0
- package/dist/ui/tui/controller.d.ts +97 -0
- package/dist/ui/tui/controller.js +415 -0
- package/dist/ui/tui/controller.js.map +1 -0
- package/dist/ui/tui/menu.d.ts +55 -0
- package/dist/ui/tui/menu.js +176 -0
- package/dist/ui/tui/menu.js.map +1 -0
- package/dist/ui/tui/options.d.ts +19 -0
- package/dist/ui/tui/options.js +17 -0
- package/dist/ui/tui/options.js.map +1 -0
- package/dist/ui/tui/receipts.d.ts +21 -0
- package/dist/ui/tui/receipts.js +112 -0
- package/dist/ui/tui/receipts.js.map +1 -0
- package/dist/ui/tui/status.d.ts +22 -0
- package/dist/ui/tui/status.js +92 -0
- package/dist/ui/tui/status.js.map +1 -0
- package/dist/ui/tui/tiers.d.ts +51 -0
- package/dist/ui/tui/tiers.js +108 -0
- package/dist/ui/tui/tiers.js.map +1 -0
- package/package.json +85 -4
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RPC handler binding (ADP-034/036): adapts the supervisor's state API to the
|
|
3
|
+
* typed RPC methods. Handlers never throw for expected outcomes — an invalid
|
|
4
|
+
* patch is a per-field `PatchResult`, and an internal failure degrades to
|
|
5
|
+
* `{ ok: false }` (fail-open, ADP-031). Read methods declare internal failures
|
|
6
|
+
* through the call context's error factory: a plain throw becomes a server
|
|
7
|
+
* defect whose generic `rpc.internal` message hides the cause (verified on the
|
|
8
|
+
* sibling @coool plugins).
|
|
9
|
+
*/
|
|
10
|
+
import { OPERATING_MODES } from "../core/scheduler/types.js";
|
|
11
|
+
import { TIERS } from "../core/tier-policy/types.js";
|
|
12
|
+
import { LOG_ERROR_LENGTH, logFailOpen } from "../supervisor/log.js";
|
|
13
|
+
import { isValidSessionID } from "../state/session-modes/index.js";
|
|
14
|
+
/** Per-field errors for a session-mode patch, mirroring the settings contract. */
|
|
15
|
+
function sessionModeSetError(input) {
|
|
16
|
+
if (!isValidSessionID(input.sessionID)) {
|
|
17
|
+
return "sessionID must be a non-empty string of at most 128 characters";
|
|
18
|
+
}
|
|
19
|
+
if (input.mode !== null && !OPERATING_MODES.includes(input.mode)) {
|
|
20
|
+
return `mode must be one of ${OPERATING_MODES.join(", ")} or null`;
|
|
21
|
+
}
|
|
22
|
+
return undefined;
|
|
23
|
+
}
|
|
24
|
+
/** Same contract for a tier-request patch (ADP-055): vocabulary-gated tier. */
|
|
25
|
+
function tierRequestSetError(input) {
|
|
26
|
+
if (!isValidSessionID(input.sessionID)) {
|
|
27
|
+
return "sessionID must be a non-empty string of at most 128 characters";
|
|
28
|
+
}
|
|
29
|
+
if (input.tier !== null && !TIERS.includes(input.tier)) {
|
|
30
|
+
return `tier must be one of ${TIERS.join(", ")} or null`;
|
|
31
|
+
}
|
|
32
|
+
return undefined;
|
|
33
|
+
}
|
|
34
|
+
export function adaptiveRpcHandlers(source, emit) {
|
|
35
|
+
return {
|
|
36
|
+
"settings.get": (_input, context) => source
|
|
37
|
+
.settings()
|
|
38
|
+
.catch((error) => context.error("internal", String(error).slice(0, LOG_ERROR_LENGTH))),
|
|
39
|
+
"settings.set": async (patch) => {
|
|
40
|
+
try {
|
|
41
|
+
const result = await source.updateSettings(patch);
|
|
42
|
+
if (result.ok && emit !== undefined) {
|
|
43
|
+
try {
|
|
44
|
+
await emit("settings-changed", { version: result.settings.version });
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
logFailOpen("rpc-emit", error);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return result;
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
logFailOpen("rpc-set", error);
|
|
54
|
+
return { ok: false, errors: ["internal error"] };
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
"status.get": (_input, context) => source
|
|
58
|
+
.status()
|
|
59
|
+
.catch((error) => context.error("internal", String(error).slice(0, LOG_ERROR_LENGTH))),
|
|
60
|
+
"receipts.list": (_input, context) => source
|
|
61
|
+
.receipts()
|
|
62
|
+
.catch((error) => context.error("internal", String(error).slice(0, LOG_ERROR_LENGTH))),
|
|
63
|
+
"session-mode.get": (input, context) => source
|
|
64
|
+
.getSessionMode(input.sessionID)
|
|
65
|
+
.then((mode) => ({ mode }))
|
|
66
|
+
.catch((error) => context.error("internal", String(error).slice(0, LOG_ERROR_LENGTH))),
|
|
67
|
+
"session-mode.set": async (input) => {
|
|
68
|
+
const invalid = sessionModeSetError(input);
|
|
69
|
+
if (invalid !== undefined)
|
|
70
|
+
return { ok: false, errors: [invalid] };
|
|
71
|
+
try {
|
|
72
|
+
await source.setSessionMode(input.sessionID, input.mode);
|
|
73
|
+
}
|
|
74
|
+
catch (error) {
|
|
75
|
+
logFailOpen("rpc-set", error);
|
|
76
|
+
return { ok: false, errors: ["internal error"] };
|
|
77
|
+
}
|
|
78
|
+
if (emit !== undefined) {
|
|
79
|
+
try {
|
|
80
|
+
await emit("session-mode-changed", { sessionID: input.sessionID });
|
|
81
|
+
}
|
|
82
|
+
catch (error) {
|
|
83
|
+
logFailOpen("rpc-emit", error);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return { ok: true };
|
|
87
|
+
},
|
|
88
|
+
"tier-request.get": (input, context) => source
|
|
89
|
+
.getTierRequest(input.sessionID)
|
|
90
|
+
.then((tier) => ({ tier }))
|
|
91
|
+
.catch((error) => context.error("internal", String(error).slice(0, LOG_ERROR_LENGTH))),
|
|
92
|
+
"tier-request.set": async (input) => {
|
|
93
|
+
const invalid = tierRequestSetError(input);
|
|
94
|
+
if (invalid !== undefined)
|
|
95
|
+
return { ok: false, errors: [invalid] };
|
|
96
|
+
if (input.tier !== null) {
|
|
97
|
+
// A non-null tier is a sticky record: noteTierRequest re-validates the
|
|
98
|
+
// vocabulary (post-gate this cannot fail, so treat `false` as internal).
|
|
99
|
+
if (!source.noteTierRequest(input.sessionID, input.tier)) {
|
|
100
|
+
logFailOpen("rpc-set", new Error("tier request rejected"));
|
|
101
|
+
return { ok: false, errors: ["internal error"] };
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
try {
|
|
106
|
+
await source.setTierRequest(input.sessionID, null);
|
|
107
|
+
}
|
|
108
|
+
catch (error) {
|
|
109
|
+
logFailOpen("rpc-set", error);
|
|
110
|
+
return { ok: false, errors: ["internal error"] };
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
if (emit !== undefined) {
|
|
114
|
+
try {
|
|
115
|
+
await emit("tier-request-changed", { sessionID: input.sessionID });
|
|
116
|
+
}
|
|
117
|
+
catch (error) {
|
|
118
|
+
logFailOpen("rpc-emit", error);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return { ok: true };
|
|
122
|
+
},
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
//# sourceMappingURL=handlers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"handlers.js","sourceRoot":"","sources":["../../src/rpc/handlers.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAA;AAE5D,OAAO,EAAE,KAAK,EAAE,MAAM,8BAA8B,CAAA;AAEpD,OAAO,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAA;AACpE,OAAO,EAAE,gBAAgB,EAAE,MAAM,iCAAiC,CAAA;AAsClE,kFAAkF;AAClF,SAAS,mBAAmB,CAAC,KAG5B;IACC,IAAI,CAAC,gBAAgB,CAAC,KAAK,CAAC,SAAS,CAAC,EAAE,CAAC;QACvC,OAAO,gEAAgE,CAAA;IACzE,CAAC;IACD,IAAI,KAAK,CAAC,IAAI,KAAK,IAAI,IAAI,CAAE,eAAqC,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;QACxF,OAAO,uBAAuB,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAA;IACpE,CAAC;IACD,OAAO,SAAS,CAAA;AAClB,CAAC;AAED,+EAA+E;AAC/E,SAAS,mBAAmB,CAAC,KAG5B;IACC,IAAI,CAAC,gBAAgB,CAAC,KAAK,CAAC,SAAS,CAAC,EAAE,CAAC;QACvC,OAAO,gEAAgE,CAAA;IACzE,CAAC;IACD,IAAI,KAAK,CAAC,IAAI,KAAK,IAAI,IAAI,CAAE,KAA2B,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;QAC9E,OAAO,uBAAuB,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAA;IAC1D,CAAC;IACD,OAAO,SAAS,CAAA;AAClB,CAAC;AAiBD,MAAM,UAAU,mBAAmB,CACjC,MAAyB,EACzB,IAAsB;IAEtB,OAAO;QACL,cAAc,EAAE,CAAC,MAAM,EAAE,OAAO,EAAE,EAAE,CAClC,MAAM;aACH,QAAQ,EAAE;aACV,KAAK,CAAC,CAAC,KAAc,EAAE,EAAE,CACxB,OAAO,CAAC,KAAK,CAAC,UAAU,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,gBAAgB,CAAC,CAAC,CACpE;QACL,cAAc,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE;YAC9B,IAAI,CAAC;gBACH,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,cAAc,CAAC,KAAK,CAAC,CAAA;gBACjD,IAAI,MAAM,CAAC,EAAE,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;oBACpC,IAAI,CAAC;wBACH,MAAM,IAAI,CAAC,kBAAkB,EAAE,EAAE,OAAO,EAAE,MAAM,CAAC,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAA;oBACtE,CAAC;oBAAC,OAAO,KAAK,EAAE,CAAC;wBACf,WAAW,CAAC,UAAU,EAAE,KAAK,CAAC,CAAA;oBAChC,CAAC;gBACH,CAAC;gBACD,OAAO,MAAM,CAAA;YACf,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,WAAW,CAAC,SAAS,EAAE,KAAK,CAAC,CAAA;gBAC7B,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,gBAAgB,CAAC,EAAE,CAAA;YAClD,CAAC;QACH,CAAC;QACD,YAAY,EAAE,CAAC,MAAM,EAAE,OAAO,EAAE,EAAE,CAChC,MAAM;aACH,MAAM,EAAE;aACR,KAAK,CAAC,CAAC,KAAc,EAAE,EAAE,CACxB,OAAO,CAAC,KAAK,CAAC,UAAU,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,gBAAgB,CAAC,CAAC,CACpE;QACL,eAAe,EAAE,CAAC,MAAM,EAAE,OAAO,EAAE,EAAE,CACnC,MAAM;aACH,QAAQ,EAAE;aACV,KAAK,CAAC,CAAC,KAAc,EAAE,EAAE,CACxB,OAAO,CAAC,KAAK,CAAC,UAAU,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,gBAAgB,CAAC,CAAC,CACpE;QACL,kBAAkB,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE,CACrC,MAAM;aACH,cAAc,CAAC,KAAK,CAAC,SAAS,CAAC;aAC/B,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC;aAC1B,KAAK,CAAC,CAAC,KAAc,EAAE,EAAE,CACxB,OAAO,CAAC,KAAK,CAAC,UAAU,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,gBAAgB,CAAC,CAAC,CACpE;QACL,kBAAkB,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE;YAClC,MAAM,OAAO,GAAG,mBAAmB,CAAC,KAAK,CAAC,CAAA;YAC1C,IAAI,OAAO,KAAK,SAAS;gBAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,OAAO,CAAC,EAAE,CAAA;YAClE,IAAI,CAAC;gBACH,MAAM,MAAM,CAAC,cAAc,CAAC,KAAK,CAAC,SAAS,EAAE,KAAK,CAAC,IAAI,CAAC,CAAA;YAC1D,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,WAAW,CAAC,SAAS,EAAE,KAAK,CAAC,CAAA;gBAC7B,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,gBAAgB,CAAC,EAAE,CAAA;YAClD,CAAC;YACD,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;gBACvB,IAAI,CAAC;oBACH,MAAM,IAAI,CAAC,sBAAsB,EAAE,EAAE,SAAS,EAAE,KAAK,CAAC,SAAS,EAAE,CAAC,CAAA;gBACpE,CAAC;gBAAC,OAAO,KAAK,EAAE,CAAC;oBACf,WAAW,CAAC,UAAU,EAAE,KAAK,CAAC,CAAA;gBAChC,CAAC;YACH,CAAC;YACD,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,CAAA;QACrB,CAAC;QACD,kBAAkB,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE,CACrC,MAAM;aACH,cAAc,CAAC,KAAK,CAAC,SAAS,CAAC;aAC/B,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC;aAC1B,KAAK,CAAC,CAAC,KAAc,EAAE,EAAE,CACxB,OAAO,CAAC,KAAK,CAAC,UAAU,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,gBAAgB,CAAC,CAAC,CACpE;QACL,kBAAkB,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE;YAClC,MAAM,OAAO,GAAG,mBAAmB,CAAC,KAAK,CAAC,CAAA;YAC1C,IAAI,OAAO,KAAK,SAAS;gBAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,OAAO,CAAC,EAAE,CAAA;YAClE,IAAI,KAAK,CAAC,IAAI,KAAK,IAAI,EAAE,CAAC;gBACxB,uEAAuE;gBACvE,yEAAyE;gBACzE,IAAI,CAAC,MAAM,CAAC,eAAe,CAAC,KAAK,CAAC,SAAS,EAAE,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;oBACzD,WAAW,CAAC,SAAS,EAAE,IAAI,KAAK,CAAC,uBAAuB,CAAC,CAAC,CAAA;oBAC1D,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,gBAAgB,CAAC,EAAE,CAAA;gBAClD,CAAC;YACH,CAAC;iBAAM,CAAC;gBACN,IAAI,CAAC;oBACH,MAAM,MAAM,CAAC,cAAc,CAAC,KAAK,CAAC,SAAS,EAAE,IAAI,CAAC,CAAA;gBACpD,CAAC;gBAAC,OAAO,KAAK,EAAE,CAAC;oBACf,WAAW,CAAC,SAAS,EAAE,KAAK,CAAC,CAAA;oBAC7B,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,gBAAgB,CAAC,EAAE,CAAA;gBAClD,CAAC;YACH,CAAC;YACD,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;gBACvB,IAAI,CAAC;oBACH,MAAM,IAAI,CAAC,sBAAsB,EAAE,EAAE,SAAS,EAAE,KAAK,CAAC,SAAS,EAAE,CAAC,CAAA;gBACpE,CAAC;gBAAC,OAAO,KAAK,EAAE,CAAC;oBACf,WAAW,CAAC,UAAU,EAAE,KAAK,CAAC,CAAA;gBAChC,CAAC;YACH,CAAC;YACD,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,CAAA;QACrB,CAAC;KACF,CAAA;AACH,CAAC","sourcesContent":["/**\n * RPC handler binding (ADP-034/036): adapts the supervisor's state API to the\n * typed RPC methods. Handlers never throw for expected outcomes — an invalid\n * patch is a per-field `PatchResult`, and an internal failure degrades to\n * `{ ok: false }` (fail-open, ADP-031). Read methods declare internal failures\n * through the call context's error factory: a plain throw becomes a server\n * defect whose generic `rpc.internal` message hides the cause (verified on the\n * sibling @coool plugins).\n */\nimport { OPERATING_MODES } from \"../core/scheduler/types.js\"\nimport type { OperatingMode } from \"../core/scheduler/types.js\"\nimport { TIERS } from \"../core/tier-policy/types.js\"\nimport type { TierName } from \"../core/tier-policy/types.js\"\nimport { LOG_ERROR_LENGTH, logFailOpen } from \"../supervisor/log.js\"\nimport { isValidSessionID } from \"../state/session-modes/index.js\"\nimport type { PatchResult } from \"../state/settings/types.js\"\nimport type { SettingsPatch, SettingsV1 } from \"../state/settings/index.js\"\nimport type { Rpc } from \"@opencode/schema/rpc\"\nimport type { Receipt } from \"../state/receipts/types.js\"\nimport type { StatusSnapshot } from \"./status.js\"\nimport type { AdaptiveRpc } from \"./definition.js\"\n\nexport interface AdaptiveRpcSource {\n settings(): Promise<SettingsV1>\n updateSettings(patch: SettingsPatch): Promise<PatchResult>\n status(): Promise<StatusSnapshot>\n receipts(): Promise<Receipt[]>\n getSessionMode(sessionID: string): Promise<OperatingMode | null>\n setSessionMode(sessionID: string, mode: OperatingMode | null): Promise<void>\n getTierRequest(sessionID: string): Promise<TierName | null>\n noteTierRequest(sessionID: string, tier: string): boolean\n setTierRequest(sessionID: string, tier: TierName | null): Promise<void>\n}\n\nexport type SettingsChangedEmit = (name: \"settings-changed\", data: { version: number }) => unknown\n\n/**\n * Correlated emit: each call site pairs the event name with its own payload\n * shape — an invalid pairing is a type error, and the registration site can\n * forward the tuple to the host emitter untouched.\n */\nexport type AdaptiveRpcEmit = (\n ...args:\n | [\"settings-changed\", { version: number }]\n | [\"session-mode-changed\", { sessionID: string }]\n | [\"tier-request-changed\", { sessionID: string }]\n) => unknown\n\n/** A successful tier-request write; mirrors the SessionModeSetResult convention. */\nexport type TierRequestSetResult =\n { readonly ok: true } | { readonly ok: false; readonly errors: readonly string[] }\n\n/** Per-field errors for a session-mode patch, mirroring the settings contract. */\nfunction sessionModeSetError(input: {\n sessionID: string\n mode: OperatingMode | null\n}): string | undefined {\n if (!isValidSessionID(input.sessionID)) {\n return \"sessionID must be a non-empty string of at most 128 characters\"\n }\n if (input.mode !== null && !(OPERATING_MODES as readonly string[]).includes(input.mode)) {\n return `mode must be one of ${OPERATING_MODES.join(\", \")} or null`\n }\n return undefined\n}\n\n/** Same contract for a tier-request patch (ADP-055): vocabulary-gated tier. */\nfunction tierRequestSetError(input: {\n sessionID: string\n tier: string | null\n}): string | undefined {\n if (!isValidSessionID(input.sessionID)) {\n return \"sessionID must be a non-empty string of at most 128 characters\"\n }\n if (input.tier !== null && !(TIERS as readonly string[]).includes(input.tier)) {\n return `tier must be one of ${TIERS.join(\", \")} or null`\n }\n return undefined\n}\n\ntype Methods = AdaptiveRpc[\"methods\"]\ntype MethodName = Extract<keyof Methods, string>\n\n/** The subset of the host's RpcCallContext the handlers use (no plugin import). */\nexport interface AdaptiveRpcCallContext<Name extends MethodName> {\n readonly error: Rpc.ErrorFactory<Methods[Name]>\n}\n\ntype HandlerErrorOf<Name extends MethodName> = Rpc.HandlerError<Methods[Name]>\n\ntype HandlerOf<Name extends MethodName> = (\n input: Rpc.Output<Methods[Name][\"input\"]>,\n context: AdaptiveRpcCallContext<Name>,\n) => Promise<Rpc.HandlerOutput<Methods[Name][\"output\"]> | HandlerErrorOf<Name>>\n\nexport function adaptiveRpcHandlers(\n source: AdaptiveRpcSource,\n emit?: AdaptiveRpcEmit,\n): { [Name in MethodName]: HandlerOf<Name> } {\n return {\n \"settings.get\": (_input, context) =>\n source\n .settings()\n .catch((error: unknown) =>\n context.error(\"internal\", String(error).slice(0, LOG_ERROR_LENGTH)),\n ),\n \"settings.set\": async (patch) => {\n try {\n const result = await source.updateSettings(patch)\n if (result.ok && emit !== undefined) {\n try {\n await emit(\"settings-changed\", { version: result.settings.version })\n } catch (error) {\n logFailOpen(\"rpc-emit\", error)\n }\n }\n return result\n } catch (error) {\n logFailOpen(\"rpc-set\", error)\n return { ok: false, errors: [\"internal error\"] }\n }\n },\n \"status.get\": (_input, context) =>\n source\n .status()\n .catch((error: unknown) =>\n context.error(\"internal\", String(error).slice(0, LOG_ERROR_LENGTH)),\n ),\n \"receipts.list\": (_input, context) =>\n source\n .receipts()\n .catch((error: unknown) =>\n context.error(\"internal\", String(error).slice(0, LOG_ERROR_LENGTH)),\n ),\n \"session-mode.get\": (input, context) =>\n source\n .getSessionMode(input.sessionID)\n .then((mode) => ({ mode }))\n .catch((error: unknown) =>\n context.error(\"internal\", String(error).slice(0, LOG_ERROR_LENGTH)),\n ),\n \"session-mode.set\": async (input) => {\n const invalid = sessionModeSetError(input)\n if (invalid !== undefined) return { ok: false, errors: [invalid] }\n try {\n await source.setSessionMode(input.sessionID, input.mode)\n } catch (error) {\n logFailOpen(\"rpc-set\", error)\n return { ok: false, errors: [\"internal error\"] }\n }\n if (emit !== undefined) {\n try {\n await emit(\"session-mode-changed\", { sessionID: input.sessionID })\n } catch (error) {\n logFailOpen(\"rpc-emit\", error)\n }\n }\n return { ok: true }\n },\n \"tier-request.get\": (input, context) =>\n source\n .getTierRequest(input.sessionID)\n .then((tier) => ({ tier }))\n .catch((error: unknown) =>\n context.error(\"internal\", String(error).slice(0, LOG_ERROR_LENGTH)),\n ),\n \"tier-request.set\": async (input) => {\n const invalid = tierRequestSetError(input)\n if (invalid !== undefined) return { ok: false, errors: [invalid] }\n if (input.tier !== null) {\n // A non-null tier is a sticky record: noteTierRequest re-validates the\n // vocabulary (post-gate this cannot fail, so treat `false` as internal).\n if (!source.noteTierRequest(input.sessionID, input.tier)) {\n logFailOpen(\"rpc-set\", new Error(\"tier request rejected\"))\n return { ok: false, errors: [\"internal error\"] }\n }\n } else {\n try {\n await source.setTierRequest(input.sessionID, null)\n } catch (error) {\n logFailOpen(\"rpc-set\", error)\n return { ok: false, errors: [\"internal error\"] }\n }\n }\n if (emit !== undefined) {\n try {\n await emit(\"tier-request-changed\", { sessionID: input.sessionID })\n } catch (error) {\n logFailOpen(\"rpc-emit\", error)\n }\n }\n return { ok: true }\n },\n }\n}\n"]}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Status snapshot for the control surfaces (ADP-033): a bounded, secret-free
|
|
3
|
+
* view of the settings and the resource-health state of every configured
|
|
4
|
+
* resource. Pure — the supervisor (or any future caller) gathers the inputs.
|
|
5
|
+
*/
|
|
6
|
+
import type { OperatingMode } from "../core/scheduler/types.js";
|
|
7
|
+
import type { ModelRef } from "../core/work-unit/types.js";
|
|
8
|
+
import type { TierName } from "../core/tier-policy/types.js";
|
|
9
|
+
import type { ResourceRecord } from "../health/resource-health/types.js";
|
|
10
|
+
import type { CapacityReading } from "../health/capacity/types.js";
|
|
11
|
+
import type { SettingsV1 } from "../state/settings/index.js";
|
|
12
|
+
export interface TierResourceStatus {
|
|
13
|
+
ref: ModelRef;
|
|
14
|
+
health: ResourceRecord["health"];
|
|
15
|
+
breaker: ResourceRecord["breaker"]["state"];
|
|
16
|
+
windowed: boolean;
|
|
17
|
+
/**
|
|
18
|
+
* Wall-clock deadline of the active window (ms epoch); null when unwindowed.
|
|
19
|
+
* Null — not undefined: the host RPC encoder rejects JSON objects carrying a
|
|
20
|
+
* present-but-undefined key ("Expected JSON value"), which 500'd every
|
|
21
|
+
* status.get once any resource was configured.
|
|
22
|
+
*/
|
|
23
|
+
windowUntil: number | null;
|
|
24
|
+
/** Used capacity as a rounded whole percent; null when no reading exists. */
|
|
25
|
+
usedPercent: number | null;
|
|
26
|
+
/** Wall-clock capacity reset deadline (ms epoch); null when unread. */
|
|
27
|
+
resetAt: number | null;
|
|
28
|
+
}
|
|
29
|
+
export interface StatusSnapshot {
|
|
30
|
+
mode: OperatingMode;
|
|
31
|
+
pin: ModelRef | null;
|
|
32
|
+
tiers: Readonly<Record<TierName, readonly TierResourceStatus[]>>;
|
|
33
|
+
parked: number;
|
|
34
|
+
receipts: number;
|
|
35
|
+
}
|
|
36
|
+
export declare function buildStatus(input: {
|
|
37
|
+
settings: SettingsV1;
|
|
38
|
+
health: readonly ResourceRecord[];
|
|
39
|
+
capacity: Readonly<Record<string, CapacityReading | null>>;
|
|
40
|
+
parked: number;
|
|
41
|
+
receipts: number;
|
|
42
|
+
}): StatusSnapshot;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { schedulerHealthKey } from "../core/scheduler/index.js";
|
|
2
|
+
import { TIERS } from "../core/tier-policy/types.js";
|
|
3
|
+
import { resourceKeyID } from "../health/resource-health/index.js";
|
|
4
|
+
export function buildStatus(input) {
|
|
5
|
+
const byKey = new Map();
|
|
6
|
+
for (const record of input.health)
|
|
7
|
+
byKey.set(resourceKeyID(record.key), record);
|
|
8
|
+
const tiers = {};
|
|
9
|
+
for (const tier of TIERS) {
|
|
10
|
+
tiers[tier] = input.settings.tiers[tier].map((ref) => {
|
|
11
|
+
const record = byKey.get(schedulerHealthKey(ref));
|
|
12
|
+
const capacityReading = input.capacity[schedulerHealthKey(ref)] ?? null;
|
|
13
|
+
return {
|
|
14
|
+
ref,
|
|
15
|
+
health: record?.health ?? "healthy",
|
|
16
|
+
breaker: record?.breaker.state ?? "closed",
|
|
17
|
+
windowed: record?.window !== undefined,
|
|
18
|
+
windowUntil: record?.window?.until ?? null,
|
|
19
|
+
usedPercent: capacityUsedPercent(capacityReading),
|
|
20
|
+
resetAt: capacityReading?.resetAt ?? null,
|
|
21
|
+
};
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
return {
|
|
25
|
+
mode: input.settings.mode,
|
|
26
|
+
pin: input.settings.pin,
|
|
27
|
+
tiers,
|
|
28
|
+
parked: input.parked,
|
|
29
|
+
receipts: input.receipts,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Derives the used-capacity percentage from a reading. Readings may carry any
|
|
34
|
+
* subset of the two ratios: usedRatio directly, or the remainder inferred from
|
|
35
|
+
* remainingRatio. A reading with neither ratio is still present (confidence,
|
|
36
|
+
* resetAt may be set) but yields no percentage — null, not undefined.
|
|
37
|
+
*/
|
|
38
|
+
function capacityUsedPercent(reading) {
|
|
39
|
+
if (reading?.usedRatio !== undefined)
|
|
40
|
+
return Math.round(reading.usedRatio * 100);
|
|
41
|
+
if (reading?.remainingRatio !== undefined)
|
|
42
|
+
return Math.round((1 - reading.remainingRatio) * 100);
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=status.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"status.js","sourceRoot":"","sources":["../../src/rpc/status.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,kBAAkB,EAAE,MAAM,4BAA4B,CAAA;AAG/D,OAAO,EAAE,KAAK,EAAE,MAAM,8BAA8B,CAAA;AAGpD,OAAO,EAAE,aAAa,EAAE,MAAM,oCAAoC,CAAA;AA6BlE,MAAM,UAAU,WAAW,CAAC,KAM3B;IACC,MAAM,KAAK,GAAG,IAAI,GAAG,EAA0B,CAAA;IAC/C,KAAK,MAAM,MAAM,IAAI,KAAK,CAAC,MAAM;QAAE,KAAK,CAAC,GAAG,CAAC,aAAa,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC,CAAA;IAC/E,MAAM,KAAK,GAAG,EAA4C,CAAA;IAC1D,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,KAAK,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE;YACnD,MAAM,MAAM,GAAG,KAAK,CAAC,GAAG,CAAC,kBAAkB,CAAC,GAAG,CAAC,CAAC,CAAA;YACjD,MAAM,eAAe,GAAG,KAAK,CAAC,QAAQ,CAAC,kBAAkB,CAAC,GAAG,CAAC,CAAC,IAAI,IAAI,CAAA;YACvE,OAAO;gBACL,GAAG;gBACH,MAAM,EAAE,MAAM,EAAE,MAAM,IAAI,SAAS;gBACnC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,QAAQ;gBAC1C,QAAQ,EAAE,MAAM,EAAE,MAAM,KAAK,SAAS;gBACtC,WAAW,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,IAAI,IAAI;gBAC1C,WAAW,EAAE,mBAAmB,CAAC,eAAe,CAAC;gBACjD,OAAO,EAAE,eAAe,EAAE,OAAO,IAAI,IAAI;aAC1C,CAAA;QACH,CAAC,CAAC,CAAA;IACJ,CAAC;IACD,OAAO;QACL,IAAI,EAAE,KAAK,CAAC,QAAQ,CAAC,IAAI;QACzB,GAAG,EAAE,KAAK,CAAC,QAAQ,CAAC,GAAG;QACvB,KAAK;QACL,MAAM,EAAE,KAAK,CAAC,MAAM;QACpB,QAAQ,EAAE,KAAK,CAAC,QAAQ;KACzB,CAAA;AACH,CAAC;AAED;;;;;GAKG;AACH,SAAS,mBAAmB,CAAC,OAA+B;IAC1D,IAAI,OAAO,EAAE,SAAS,KAAK,SAAS;QAAE,OAAO,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,SAAS,GAAG,GAAG,CAAC,CAAA;IAChF,IAAI,OAAO,EAAE,cAAc,KAAK,SAAS;QAAE,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,cAAc,CAAC,GAAG,GAAG,CAAC,CAAA;IAChG,OAAO,IAAI,CAAA;AACb,CAAC","sourcesContent":["/**\n * Status snapshot for the control surfaces (ADP-033): a bounded, secret-free\n * view of the settings and the resource-health state of every configured\n * resource. Pure — the supervisor (or any future caller) gathers the inputs.\n */\nimport type { OperatingMode } from \"../core/scheduler/types.js\"\nimport { schedulerHealthKey } from \"../core/scheduler/index.js\"\nimport type { ModelRef } from \"../core/work-unit/types.js\"\nimport type { TierName } from \"../core/tier-policy/types.js\"\nimport { TIERS } from \"../core/tier-policy/types.js\"\nimport type { ResourceRecord } from \"../health/resource-health/types.js\"\nimport type { CapacityReading } from \"../health/capacity/types.js\"\nimport { resourceKeyID } from \"../health/resource-health/index.js\"\nimport type { SettingsV1 } from \"../state/settings/index.js\"\n\nexport interface TierResourceStatus {\n ref: ModelRef\n health: ResourceRecord[\"health\"]\n breaker: ResourceRecord[\"breaker\"][\"state\"]\n windowed: boolean\n /**\n * Wall-clock deadline of the active window (ms epoch); null when unwindowed.\n * Null — not undefined: the host RPC encoder rejects JSON objects carrying a\n * present-but-undefined key (\"Expected JSON value\"), which 500'd every\n * status.get once any resource was configured.\n */\n windowUntil: number | null\n /** Used capacity as a rounded whole percent; null when no reading exists. */\n usedPercent: number | null\n /** Wall-clock capacity reset deadline (ms epoch); null when unread. */\n resetAt: number | null\n}\n\nexport interface StatusSnapshot {\n mode: OperatingMode\n pin: ModelRef | null\n tiers: Readonly<Record<TierName, readonly TierResourceStatus[]>>\n parked: number\n receipts: number\n}\n\nexport function buildStatus(input: {\n settings: SettingsV1\n health: readonly ResourceRecord[]\n capacity: Readonly<Record<string, CapacityReading | null>>\n parked: number\n receipts: number\n}): StatusSnapshot {\n const byKey = new Map<string, ResourceRecord>()\n for (const record of input.health) byKey.set(resourceKeyID(record.key), record)\n const tiers = {} as Record<TierName, TierResourceStatus[]>\n for (const tier of TIERS) {\n tiers[tier] = input.settings.tiers[tier].map((ref) => {\n const record = byKey.get(schedulerHealthKey(ref))\n const capacityReading = input.capacity[schedulerHealthKey(ref)] ?? null\n return {\n ref,\n health: record?.health ?? \"healthy\",\n breaker: record?.breaker.state ?? \"closed\",\n windowed: record?.window !== undefined,\n windowUntil: record?.window?.until ?? null,\n usedPercent: capacityUsedPercent(capacityReading),\n resetAt: capacityReading?.resetAt ?? null,\n }\n })\n }\n return {\n mode: input.settings.mode,\n pin: input.settings.pin,\n tiers,\n parked: input.parked,\n receipts: input.receipts,\n }\n}\n\n/**\n * Derives the used-capacity percentage from a reading. Readings may carry any\n * subset of the two ratios: usedRatio directly, or the remainder inferred from\n * remainingRatio. A reading with neither ratio is still present (confidence,\n * resetAt may be set) but yields no percentage — null, not undefined.\n */\nfunction capacityUsedPercent(reading: CapacityReading | null): number | null {\n if (reading?.usedRatio !== undefined) return Math.round(reading.usedRatio * 100)\n if (reading?.remainingRatio !== undefined) return Math.round((1 - reading.remainingRatio) * 100)\n return null\n}\n"]}
|
package/dist/rpc.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RPC entrypoint: the typed definition shared by the server plugin and the
|
|
3
|
+
* control surfaces, plus the handler binding used server-side.
|
|
4
|
+
*/
|
|
5
|
+
export { adaptiveRpc, ADAPTIVE_RPC_ID } from "./rpc/definition.js";
|
|
6
|
+
export type { AdaptiveRpc } from "./rpc/definition.js";
|
|
7
|
+
export { adaptiveRpcHandlers } from "./rpc/handlers.js";
|
|
8
|
+
export type { AdaptiveRpcSource, AdaptiveRpcEmit, SettingsChangedEmit, TierRequestSetResult, } from "./rpc/handlers.js";
|
|
9
|
+
export { buildStatus } from "./rpc/status.js";
|
|
10
|
+
export type { StatusSnapshot, TierResourceStatus } from "./rpc/status.js";
|
package/dist/rpc.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RPC entrypoint: the typed definition shared by the server plugin and the
|
|
3
|
+
* control surfaces, plus the handler binding used server-side.
|
|
4
|
+
*/
|
|
5
|
+
export { adaptiveRpc, ADAPTIVE_RPC_ID } from "./rpc/definition.js";
|
|
6
|
+
export { adaptiveRpcHandlers } from "./rpc/handlers.js";
|
|
7
|
+
export { buildStatus } from "./rpc/status.js";
|
|
8
|
+
//# sourceMappingURL=rpc.js.map
|
package/dist/rpc.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rpc.js","sourceRoot":"","sources":["../src/rpc.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAA;AAElE,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAA;AAOvD,OAAO,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAA","sourcesContent":["/**\n * RPC entrypoint: the typed definition shared by the server plugin and the\n * control surfaces, plus the handler binding used server-side.\n */\nexport { adaptiveRpc, ADAPTIVE_RPC_ID } from \"./rpc/definition.js\"\nexport type { AdaptiveRpc } from \"./rpc/definition.js\"\nexport { adaptiveRpcHandlers } from \"./rpc/handlers.js\"\nexport type {\n AdaptiveRpcSource,\n AdaptiveRpcEmit,\n SettingsChangedEmit,\n TierRequestSetResult,\n} from \"./rpc/handlers.js\"\nexport { buildStatus } from \"./rpc/status.js\"\nexport type { StatusSnapshot, TierResourceStatus } from \"./rpc/status.js\"\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/state/capacity/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,YAAY,EACZ,gBAAgB,EAChB,qBAAqB,GAItB,MAAM,YAAY,CAAA;AACnB,OAAO,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAA;AACjD,OAAO,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAA","sourcesContent":["export {\n CAPACITY_KEY,\n CAPACITY_VERSION,\n MAX_CAPACITY_READINGS,\n type CapacityPayload,\n type CapacityStorage,\n type PersistedCapacityReading,\n} from \"./types.js\"\nexport { parseCapacityPayload } from \"./parse.js\"\nexport { CapacityReadingStore } from \"./store.js\"\n"]}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { CONFIDENCE_LEVELS } from "../../health/capacity/types.js";
|
|
2
|
+
import { CAPACITY_VERSION, MAX_CAPACITY_READINGS } from "./types.js";
|
|
3
|
+
// Defensive readers: persisted data is untrusted. Every parser is total —
|
|
4
|
+
// accepts unknown, returns typed records, and drops anything invalid — so a
|
|
5
|
+
// corrupted file can never take the plugin down (ADP-031). An older plugin
|
|
6
|
+
// reading a newer payload drops unknown-vocabulary entries; the version field
|
|
7
|
+
// guards the schema itself.
|
|
8
|
+
function isRecord(value) {
|
|
9
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
10
|
+
}
|
|
11
|
+
function isFiniteNumber(raw) {
|
|
12
|
+
return typeof raw === "number" && Number.isFinite(raw);
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* A non-empty bounded identifier. Over-long values are rejected, not
|
|
16
|
+
* truncated: the writer stores keys as-is, so a truncated key would name a
|
|
17
|
+
* different resource.
|
|
18
|
+
*/
|
|
19
|
+
function parseIdentifier(raw) {
|
|
20
|
+
if (typeof raw !== "string" || raw === "" || raw.length > 128)
|
|
21
|
+
return undefined;
|
|
22
|
+
return raw;
|
|
23
|
+
}
|
|
24
|
+
function parseKey(raw) {
|
|
25
|
+
if (!isRecord(raw))
|
|
26
|
+
return undefined;
|
|
27
|
+
const providerID = parseIdentifier(raw["providerID"]);
|
|
28
|
+
if (providerID === undefined)
|
|
29
|
+
return undefined;
|
|
30
|
+
const accountID = parseIdentifier(raw["accountID"]);
|
|
31
|
+
const modelID = parseIdentifier(raw["modelID"]);
|
|
32
|
+
const variantID = parseIdentifier(raw["variantID"]);
|
|
33
|
+
// A present-but-unusable scope field would silently widen the key.
|
|
34
|
+
if (raw["accountID"] !== undefined && accountID === undefined)
|
|
35
|
+
return undefined;
|
|
36
|
+
if (raw["modelID"] !== undefined && modelID === undefined)
|
|
37
|
+
return undefined;
|
|
38
|
+
if (raw["variantID"] !== undefined && variantID === undefined)
|
|
39
|
+
return undefined;
|
|
40
|
+
// Scope fields must form a valid prefix: a variant without a model is not
|
|
41
|
+
// addressable against a model inventory and is not a real resource.
|
|
42
|
+
if (variantID !== undefined && modelID === undefined)
|
|
43
|
+
return undefined;
|
|
44
|
+
return {
|
|
45
|
+
providerID,
|
|
46
|
+
...(accountID !== undefined ? { accountID } : {}),
|
|
47
|
+
...(modelID !== undefined ? { modelID } : {}),
|
|
48
|
+
...(variantID !== undefined ? { variantID } : {}),
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
function parseRatio(raw) {
|
|
52
|
+
return isFiniteNumber(raw) && raw >= 0 && raw <= 1 ? raw : undefined;
|
|
53
|
+
}
|
|
54
|
+
function parseReading(raw) {
|
|
55
|
+
if (!isRecord(raw))
|
|
56
|
+
return undefined;
|
|
57
|
+
const confidence = CONFIDENCE_LEVELS.find((level) => level === raw["confidence"]);
|
|
58
|
+
if (confidence === undefined)
|
|
59
|
+
return undefined;
|
|
60
|
+
const observedAt = raw["observedAt"];
|
|
61
|
+
if (!isFiniteNumber(observedAt) || observedAt < 0)
|
|
62
|
+
return undefined;
|
|
63
|
+
const usedRatio = parseRatio(raw["usedRatio"]);
|
|
64
|
+
const remainingRatio = parseRatio(raw["remainingRatio"]);
|
|
65
|
+
const retryAfterMs = isFiniteNumber(raw["retryAfterMs"]) && raw["retryAfterMs"] >= 0
|
|
66
|
+
? raw["retryAfterMs"]
|
|
67
|
+
: undefined;
|
|
68
|
+
const resetAt = isFiniteNumber(raw["resetAt"]) && raw["resetAt"] >= 0 ? raw["resetAt"] : undefined;
|
|
69
|
+
// A reading with none of the measured fields carries no information and is
|
|
70
|
+
// discarded (mirrors the domain rule in health/capacity/types.ts).
|
|
71
|
+
if (usedRatio === undefined &&
|
|
72
|
+
remainingRatio === undefined &&
|
|
73
|
+
retryAfterMs === undefined &&
|
|
74
|
+
resetAt === undefined) {
|
|
75
|
+
return undefined;
|
|
76
|
+
}
|
|
77
|
+
const evidence = raw["evidence"];
|
|
78
|
+
return {
|
|
79
|
+
...(usedRatio !== undefined ? { usedRatio } : {}),
|
|
80
|
+
...(remainingRatio !== undefined ? { remainingRatio } : {}),
|
|
81
|
+
...(retryAfterMs !== undefined ? { retryAfterMs } : {}),
|
|
82
|
+
...(resetAt !== undefined ? { resetAt } : {}),
|
|
83
|
+
confidence,
|
|
84
|
+
observedAt,
|
|
85
|
+
...(typeof evidence === "string" && evidence.length > 0
|
|
86
|
+
? { evidence: evidence.slice(0, 128) }
|
|
87
|
+
: {}),
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
function parseEntry(raw) {
|
|
91
|
+
if (!isRecord(raw))
|
|
92
|
+
return undefined;
|
|
93
|
+
const key = parseKey(raw["key"]);
|
|
94
|
+
if (key === undefined)
|
|
95
|
+
return undefined;
|
|
96
|
+
const reading = parseReading(raw["reading"]);
|
|
97
|
+
if (reading === undefined)
|
|
98
|
+
return undefined;
|
|
99
|
+
return { key, reading };
|
|
100
|
+
}
|
|
101
|
+
// Every parser is total: accepts unknown, returns typed records, and drops
|
|
102
|
+
// anything invalid — a corrupted file can never take the plugin down
|
|
103
|
+
// (ADP-031). An older plugin reading newer records may drop
|
|
104
|
+
// unknown-vocabulary entries — records are bounded mirrors of an in-memory
|
|
105
|
+
// registry, and the payload version guards the schema itself.
|
|
106
|
+
export function parseCapacityPayload(raw) {
|
|
107
|
+
if (!isRecord(raw))
|
|
108
|
+
return [];
|
|
109
|
+
if (raw["version"] !== CAPACITY_VERSION)
|
|
110
|
+
return [];
|
|
111
|
+
if (!Array.isArray(raw["readings"]))
|
|
112
|
+
return [];
|
|
113
|
+
const list = raw["readings"];
|
|
114
|
+
const readings = [];
|
|
115
|
+
for (const entry of list.slice(0, MAX_CAPACITY_READINGS)) {
|
|
116
|
+
const parsed = parseEntry(entry);
|
|
117
|
+
if (parsed !== undefined)
|
|
118
|
+
readings.push(parsed);
|
|
119
|
+
}
|
|
120
|
+
return readings;
|
|
121
|
+
}
|
|
122
|
+
//# sourceMappingURL=parse.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parse.js","sourceRoot":"","sources":["../../../src/state/capacity/parse.ts"],"names":[],"mappings":"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","sourcesContent":["import { CONFIDENCE_LEVELS } from \"../../health/capacity/types.js\"\nimport type { CapacityReading } from \"../../health/capacity/types.js\"\nimport type { ResourceKey } from \"../../health/resource-health/types.js\"\nimport { CAPACITY_VERSION, MAX_CAPACITY_READINGS, type PersistedCapacityReading } from \"./types.js\"\n\n// Defensive readers: persisted data is untrusted. Every parser is total —\n// accepts unknown, returns typed records, and drops anything invalid — so a\n// corrupted file can never take the plugin down (ADP-031). An older plugin\n// reading a newer payload drops unknown-vocabulary entries; the version field\n// guards the schema itself.\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value)\n}\n\nfunction isFiniteNumber(raw: unknown): raw is number {\n return typeof raw === \"number\" && Number.isFinite(raw)\n}\n\n/**\n * A non-empty bounded identifier. Over-long values are rejected, not\n * truncated: the writer stores keys as-is, so a truncated key would name a\n * different resource.\n */\nfunction parseIdentifier(raw: unknown): string | undefined {\n if (typeof raw !== \"string\" || raw === \"\" || raw.length > 128) return undefined\n return raw\n}\n\nfunction parseKey(raw: unknown): ResourceKey | undefined {\n if (!isRecord(raw)) return undefined\n const providerID = parseIdentifier(raw[\"providerID\"])\n if (providerID === undefined) return undefined\n const accountID = parseIdentifier(raw[\"accountID\"])\n const modelID = parseIdentifier(raw[\"modelID\"])\n const variantID = parseIdentifier(raw[\"variantID\"])\n // A present-but-unusable scope field would silently widen the key.\n if (raw[\"accountID\"] !== undefined && accountID === undefined) return undefined\n if (raw[\"modelID\"] !== undefined && modelID === undefined) return undefined\n if (raw[\"variantID\"] !== undefined && variantID === undefined) return undefined\n // Scope fields must form a valid prefix: a variant without a model is not\n // addressable against a model inventory and is not a real resource.\n if (variantID !== undefined && modelID === undefined) return undefined\n return {\n providerID,\n ...(accountID !== undefined ? { accountID } : {}),\n ...(modelID !== undefined ? { modelID } : {}),\n ...(variantID !== undefined ? { variantID } : {}),\n }\n}\n\nfunction parseRatio(raw: unknown): number | undefined {\n return isFiniteNumber(raw) && raw >= 0 && raw <= 1 ? raw : undefined\n}\n\nfunction parseReading(raw: unknown): CapacityReading | undefined {\n if (!isRecord(raw)) return undefined\n const confidence = CONFIDENCE_LEVELS.find((level) => level === raw[\"confidence\"])\n if (confidence === undefined) return undefined\n const observedAt = raw[\"observedAt\"]\n if (!isFiniteNumber(observedAt) || observedAt < 0) return undefined\n const usedRatio = parseRatio(raw[\"usedRatio\"])\n const remainingRatio = parseRatio(raw[\"remainingRatio\"])\n const retryAfterMs =\n isFiniteNumber(raw[\"retryAfterMs\"]) && raw[\"retryAfterMs\"] >= 0\n ? raw[\"retryAfterMs\"]\n : undefined\n const resetAt = isFiniteNumber(raw[\"resetAt\"]) && raw[\"resetAt\"] >= 0 ? raw[\"resetAt\"] : undefined\n // A reading with none of the measured fields carries no information and is\n // discarded (mirrors the domain rule in health/capacity/types.ts).\n if (\n usedRatio === undefined &&\n remainingRatio === undefined &&\n retryAfterMs === undefined &&\n resetAt === undefined\n ) {\n return undefined\n }\n const evidence = raw[\"evidence\"]\n return {\n ...(usedRatio !== undefined ? { usedRatio } : {}),\n ...(remainingRatio !== undefined ? { remainingRatio } : {}),\n ...(retryAfterMs !== undefined ? { retryAfterMs } : {}),\n ...(resetAt !== undefined ? { resetAt } : {}),\n confidence,\n observedAt,\n ...(typeof evidence === \"string\" && evidence.length > 0\n ? { evidence: evidence.slice(0, 128) }\n : {}),\n }\n}\n\nfunction parseEntry(raw: unknown): PersistedCapacityReading | undefined {\n if (!isRecord(raw)) return undefined\n const key = parseKey(raw[\"key\"])\n if (key === undefined) return undefined\n const reading = parseReading(raw[\"reading\"])\n if (reading === undefined) return undefined\n return { key, reading }\n}\n\n// Every parser is total: accepts unknown, returns typed records, and drops\n// anything invalid — a corrupted file can never take the plugin down\n// (ADP-031). An older plugin reading newer records may drop\n// unknown-vocabulary entries — records are bounded mirrors of an in-memory\n// registry, and the payload version guards the schema itself.\nexport function parseCapacityPayload(raw: unknown): PersistedCapacityReading[] {\n if (!isRecord(raw)) return []\n if (raw[\"version\"] !== CAPACITY_VERSION) return []\n if (!Array.isArray(raw[\"readings\"])) return []\n const list = raw[\"readings\"]\n const readings: PersistedCapacityReading[] = []\n for (const entry of list.slice(0, MAX_CAPACITY_READINGS)) {\n const parsed = parseEntry(entry)\n if (parsed !== undefined) readings.push(parsed)\n }\n return readings\n}\n"]}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { type CapacityStorage, type PersistedCapacityReading } from "./types.js";
|
|
2
|
+
export declare class CapacityReadingStore {
|
|
3
|
+
#private;
|
|
4
|
+
private readonly storage;
|
|
5
|
+
constructor(storage: CapacityStorage);
|
|
6
|
+
/** Load the persisted mirror; unreadable or corrupt data is empty, not an error. */
|
|
7
|
+
load(): Promise<void>;
|
|
8
|
+
snapshot(): readonly PersistedCapacityReading[];
|
|
9
|
+
/**
|
|
10
|
+
* Merge one refresh batch into the mirror and persist it. Merge — not
|
|
11
|
+
* replace — per resource key, with the newest `observedAt` winning: the TUI
|
|
12
|
+
* host loads this plugin into several supervisor instances, and an instance
|
|
13
|
+
* whose sensors have seen nothing must never clobber what the observing
|
|
14
|
+
* instance persisted. Bound by MAX_CAPACITY_READINGS, evicting the oldest
|
|
15
|
+
* observations first.
|
|
16
|
+
*/
|
|
17
|
+
replace(entries: readonly PersistedCapacityReading[]): Promise<void>;
|
|
18
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Persistence mirror of the in-memory capacity registry: loads what any
|
|
3
|
+
* instance observed and keeps it bounded (oldest-observed evicted), merging
|
|
4
|
+
* per resource key on every replace.
|
|
5
|
+
*
|
|
6
|
+
* As everywhere else in state/, memory is only advanced after a successful
|
|
7
|
+
* write, so memory and disk never diverge; write failures propagate to the
|
|
8
|
+
* caller — the wiring layer's fail-open wrapper decides how to degrade
|
|
9
|
+
* (ADP-031).
|
|
10
|
+
*/
|
|
11
|
+
import { resourceKeyID } from "../../health/resource-health/key.js";
|
|
12
|
+
import { CAPACITY_VERSION, MAX_CAPACITY_READINGS, } from "./types.js";
|
|
13
|
+
import { parseCapacityPayload } from "./parse.js";
|
|
14
|
+
export class CapacityReadingStore {
|
|
15
|
+
storage;
|
|
16
|
+
#readings = [];
|
|
17
|
+
#queue = Promise.resolve();
|
|
18
|
+
constructor(storage) {
|
|
19
|
+
this.storage = storage;
|
|
20
|
+
}
|
|
21
|
+
/** Load the persisted mirror; unreadable or corrupt data is empty, not an error. */
|
|
22
|
+
load() {
|
|
23
|
+
return this.#serialize(() => this.#load());
|
|
24
|
+
}
|
|
25
|
+
async #load() {
|
|
26
|
+
let raw;
|
|
27
|
+
try {
|
|
28
|
+
raw = await this.storage.read();
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
this.#readings = [];
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
this.#readings = parseCapacityPayload(raw);
|
|
35
|
+
}
|
|
36
|
+
snapshot() {
|
|
37
|
+
return this.#readings;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Merge one refresh batch into the mirror and persist it. Merge — not
|
|
41
|
+
* replace — per resource key, with the newest `observedAt` winning: the TUI
|
|
42
|
+
* host loads this plugin into several supervisor instances, and an instance
|
|
43
|
+
* whose sensors have seen nothing must never clobber what the observing
|
|
44
|
+
* instance persisted. Bound by MAX_CAPACITY_READINGS, evicting the oldest
|
|
45
|
+
* observations first.
|
|
46
|
+
*/
|
|
47
|
+
replace(entries) {
|
|
48
|
+
return this.#serialize(() => this.#replace(entries));
|
|
49
|
+
}
|
|
50
|
+
async #replace(entries) {
|
|
51
|
+
const byKey = new Map();
|
|
52
|
+
for (const entry of this.#readings) {
|
|
53
|
+
byKey.set(resourceKeyID(entry.key), entry);
|
|
54
|
+
}
|
|
55
|
+
for (const entry of entries) {
|
|
56
|
+
const id = resourceKeyID(entry.key);
|
|
57
|
+
const previous = byKey.get(id);
|
|
58
|
+
if (previous === undefined || entry.reading.observedAt > previous.reading.observedAt) {
|
|
59
|
+
byKey.set(id, entry);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
const merged = [...byKey.values()];
|
|
63
|
+
merged.sort((a, b) => b.reading.observedAt - a.reading.observedAt);
|
|
64
|
+
const next = merged.slice(0, MAX_CAPACITY_READINGS);
|
|
65
|
+
// Persist BEFORE committing memory: a write failure must leave memory and
|
|
66
|
+
// disk consistent (the caller decides how to fail open).
|
|
67
|
+
await this.storage.write({ version: CAPACITY_VERSION, readings: next });
|
|
68
|
+
this.#readings = next;
|
|
69
|
+
}
|
|
70
|
+
// The merge must run after the preceding commit, and reloads cannot race
|
|
71
|
+
// writes. Rejections remain visible to callers without poisoning the tail.
|
|
72
|
+
#serialize(operation) {
|
|
73
|
+
const run = this.#queue.then(operation);
|
|
74
|
+
this.#queue = run.catch(() => undefined);
|
|
75
|
+
return run;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
//# sourceMappingURL=store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.js","sourceRoot":"","sources":["../../../src/state/capacity/store.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Persistence mirror of the in-memory capacity registry: loads what any\n * instance observed and keeps it bounded (oldest-observed evicted), merging\n * per resource key on every replace.\n *\n * As everywhere else in state/, memory is only advanced after a successful\n * write, so memory and disk never diverge; write failures propagate to the\n * caller — the wiring layer's fail-open wrapper decides how to degrade\n * (ADP-031).\n */\nimport { resourceKeyID } from \"../../health/resource-health/key.js\"\nimport {\n CAPACITY_VERSION,\n MAX_CAPACITY_READINGS,\n type CapacityStorage,\n type PersistedCapacityReading,\n} from \"./types.js\"\nimport { parseCapacityPayload } from \"./parse.js\"\n\nexport class CapacityReadingStore {\n #readings: PersistedCapacityReading[] = []\n #queue: Promise<unknown> = Promise.resolve()\n\n constructor(private readonly storage: CapacityStorage) {}\n\n /** Load the persisted mirror; unreadable or corrupt data is empty, not an error. */\n load(): Promise<void> {\n return this.#serialize(() => this.#load())\n }\n\n async #load(): Promise<void> {\n let raw: unknown\n try {\n raw = await this.storage.read()\n } catch {\n this.#readings = []\n return\n }\n this.#readings = parseCapacityPayload(raw)\n }\n\n snapshot(): readonly PersistedCapacityReading[] {\n return this.#readings\n }\n\n /**\n * Merge one refresh batch into the mirror and persist it. Merge — not\n * replace — per resource key, with the newest `observedAt` winning: the TUI\n * host loads this plugin into several supervisor instances, and an instance\n * whose sensors have seen nothing must never clobber what the observing\n * instance persisted. Bound by MAX_CAPACITY_READINGS, evicting the oldest\n * observations first.\n */\n replace(entries: readonly PersistedCapacityReading[]): Promise<void> {\n return this.#serialize(() => this.#replace(entries))\n }\n\n async #replace(entries: readonly PersistedCapacityReading[]): Promise<void> {\n const byKey = new Map<string, PersistedCapacityReading>()\n for (const entry of this.#readings) {\n byKey.set(resourceKeyID(entry.key), entry)\n }\n for (const entry of entries) {\n const id = resourceKeyID(entry.key)\n const previous = byKey.get(id)\n if (previous === undefined || entry.reading.observedAt > previous.reading.observedAt) {\n byKey.set(id, entry)\n }\n }\n const merged = [...byKey.values()]\n merged.sort((a, b) => b.reading.observedAt - a.reading.observedAt)\n const next = merged.slice(0, MAX_CAPACITY_READINGS)\n // Persist BEFORE committing memory: a write failure must leave memory and\n // disk consistent (the caller decides how to fail open).\n await this.storage.write({ version: CAPACITY_VERSION, readings: next })\n this.#readings = next\n }\n\n // The merge must run after the preceding commit, and reloads cannot race\n // writes. Rejections remain visible to callers without poisoning the tail.\n #serialize(operation: () => Promise<void>): Promise<void> {\n const run = this.#queue.then(operation)\n this.#queue = run.catch(() => undefined)\n return run\n }\n}\n"]}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Persisted capacity readings (ADP-020/ADP-031).
|
|
3
|
+
*
|
|
4
|
+
* The host's TUI-mode plugin reconciliation can load the plugin several times
|
|
5
|
+
* into one process: the session hooks (the event pump that observes capacity
|
|
6
|
+
* headers) and the RPC registration that serves status.get can land on
|
|
7
|
+
* DIFFERENT supervisor instances. The in-memory capacity registry lives only
|
|
8
|
+
* on the hook instance, so capacity would be invisible to the status-serving
|
|
9
|
+
* instance — the same trap that health records solve with their persisted
|
|
10
|
+
* mirror. Persisting readings lets every instance read what any instance
|
|
11
|
+
* observed, like receipts/settings/parked work.
|
|
12
|
+
*/
|
|
13
|
+
import type { ResourceKey } from "../../health/resource-health/types.js";
|
|
14
|
+
import type { CapacityReading } from "../../health/capacity/types.js";
|
|
15
|
+
/** Storage key for the capacity mirror (bound by the adapter to ctx.storage). */
|
|
16
|
+
export declare const CAPACITY_KEY = "coool.adaptive/capacity";
|
|
17
|
+
export declare const CAPACITY_VERSION = 1;
|
|
18
|
+
/**
|
|
19
|
+
* Product bound — not a user knob. A capacity reading is only useful while it
|
|
20
|
+
* is fresh (readings older than the max age are treated as absent), so far
|
|
21
|
+
* fewer entries than the health bound suffice to cover every configured
|
|
22
|
+
* resource.
|
|
23
|
+
*/
|
|
24
|
+
export declare const MAX_CAPACITY_READINGS = 64;
|
|
25
|
+
export interface PersistedCapacityReading {
|
|
26
|
+
readonly key: ResourceKey;
|
|
27
|
+
readonly reading: CapacityReading;
|
|
28
|
+
}
|
|
29
|
+
export interface CapacityPayload {
|
|
30
|
+
readonly version: typeof CAPACITY_VERSION;
|
|
31
|
+
readonly readings: readonly PersistedCapacityReading[];
|
|
32
|
+
}
|
|
33
|
+
/** Storage seam — the adapter binds `CAPACITY_KEY` to `ctx.storage`. */
|
|
34
|
+
export interface CapacityStorage {
|
|
35
|
+
read(): Promise<unknown>;
|
|
36
|
+
write(payload: CapacityPayload): Promise<void>;
|
|
37
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** Storage key for the capacity mirror (bound by the adapter to ctx.storage). */
|
|
2
|
+
export const CAPACITY_KEY = "coool.adaptive/capacity";
|
|
3
|
+
export const CAPACITY_VERSION = 1;
|
|
4
|
+
/**
|
|
5
|
+
* Product bound — not a user knob. A capacity reading is only useful while it
|
|
6
|
+
* is fresh (readings older than the max age are treated as absent), so far
|
|
7
|
+
* fewer entries than the health bound suffice to cover every configured
|
|
8
|
+
* resource.
|
|
9
|
+
*/
|
|
10
|
+
export const MAX_CAPACITY_READINGS = 64;
|
|
11
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/state/capacity/types.ts"],"names":[],"mappings":"AAeA,iFAAiF;AACjF,MAAM,CAAC,MAAM,YAAY,GAAG,yBAAyB,CAAA;AAErD,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,CAAA;AAEjC;;;;;GAKG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAG,EAAE,CAAA","sourcesContent":["/**\n * Persisted capacity readings (ADP-020/ADP-031).\n *\n * The host's TUI-mode plugin reconciliation can load the plugin several times\n * into one process: the session hooks (the event pump that observes capacity\n * headers) and the RPC registration that serves status.get can land on\n * DIFFERENT supervisor instances. The in-memory capacity registry lives only\n * on the hook instance, so capacity would be invisible to the status-serving\n * instance — the same trap that health records solve with their persisted\n * mirror. Persisting readings lets every instance read what any instance\n * observed, like receipts/settings/parked work.\n */\nimport type { ResourceKey } from \"../../health/resource-health/types.js\"\nimport type { CapacityReading } from \"../../health/capacity/types.js\"\n\n/** Storage key for the capacity mirror (bound by the adapter to ctx.storage). */\nexport const CAPACITY_KEY = \"coool.adaptive/capacity\"\n\nexport const CAPACITY_VERSION = 1\n\n/**\n * Product bound — not a user knob. A capacity reading is only useful while it\n * is fresh (readings older than the max age are treated as absent), so far\n * fewer entries than the health bound suffice to cover every configured\n * resource.\n */\nexport const MAX_CAPACITY_READINGS = 64\n\nexport interface PersistedCapacityReading {\n readonly key: ResourceKey\n readonly reading: CapacityReading\n}\n\nexport interface CapacityPayload {\n readonly version: typeof CAPACITY_VERSION\n readonly readings: readonly PersistedCapacityReading[]\n}\n\n/** Storage seam — the adapter binds `CAPACITY_KEY` to `ctx.storage`. */\nexport interface CapacityStorage {\n read(): Promise<unknown>\n write(payload: CapacityPayload): Promise<void>\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/state/health/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAA;AAC/C,OAAO,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAA;AAC9C,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAA","sourcesContent":["export { parseHealthPayload } from \"./parse.js\"\nexport { HealthRecordStore } from \"./store.js\"\nexport { HEALTH_KEY, HEALTH_VERSION, MAX_HEALTH_RECORDS } from \"./types.js\"\nexport type { HealthPayload, HealthStorage } from \"./types.js\"\n"]}
|