@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,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* User-configured tier lists (ADP-005): each tier is an ordered list of
|
|
3
|
+
* resources; list order is user policy and is never reordered by Adaptive.
|
|
4
|
+
* Normalization only removes unusable entries and exact duplicates — it
|
|
5
|
+
* never reorders and never merges look-alike names (ADP-006: similar model
|
|
6
|
+
* names across providers are never equivalent unless the user configures
|
|
7
|
+
* both, and full-reference equality below is deliberately provider-scoped).
|
|
8
|
+
*/
|
|
9
|
+
import { type ModelRef } from "../work-unit/types.js";
|
|
10
|
+
export declare function normalizeTierList(resources: readonly ModelRef[]): ModelRef[];
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* User-configured tier lists (ADP-005): each tier is an ordered list of
|
|
3
|
+
* resources; list order is user policy and is never reordered by Adaptive.
|
|
4
|
+
* Normalization only removes unusable entries and exact duplicates — it
|
|
5
|
+
* never reorders and never merges look-alike names (ADP-006: similar model
|
|
6
|
+
* names across providers are never equivalent unless the user configures
|
|
7
|
+
* both, and full-reference equality below is deliberately provider-scoped).
|
|
8
|
+
*/
|
|
9
|
+
import { sameModelRef } from "../work-unit/types.js";
|
|
10
|
+
export function normalizeTierList(resources) {
|
|
11
|
+
const kept = [];
|
|
12
|
+
for (const resource of resources) {
|
|
13
|
+
if (resource.providerID.length === 0 || resource.id.length === 0) {
|
|
14
|
+
continue;
|
|
15
|
+
}
|
|
16
|
+
if (kept.some((existing) => sameModelRef(existing, resource))) {
|
|
17
|
+
continue;
|
|
18
|
+
}
|
|
19
|
+
kept.push(resource);
|
|
20
|
+
}
|
|
21
|
+
return kept;
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=tier-list.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tier-list.js","sourceRoot":"","sources":["../../../src/core/tier-policy/tier-list.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAAE,YAAY,EAAiB,MAAM,uBAAuB,CAAA;AAEnE,MAAM,UAAU,iBAAiB,CAAC,SAA8B;IAC9D,MAAM,IAAI,GAAe,EAAE,CAAA;IAC3B,KAAK,MAAM,QAAQ,IAAI,SAAS,EAAE,CAAC;QACjC,IAAI,QAAQ,CAAC,UAAU,CAAC,MAAM,KAAK,CAAC,IAAI,QAAQ,CAAC,EAAE,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACjE,SAAQ;QACV,CAAC;QACD,IAAI,IAAI,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,YAAY,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC,EAAE,CAAC;YAC9D,SAAQ;QACV,CAAC;QACD,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAA;IACrB,CAAC;IACD,OAAO,IAAI,CAAA;AACb,CAAC","sourcesContent":["/**\n * User-configured tier lists (ADP-005): each tier is an ordered list of\n * resources; list order is user policy and is never reordered by Adaptive.\n * Normalization only removes unusable entries and exact duplicates — it\n * never reorders and never merges look-alike names (ADP-006: similar model\n * names across providers are never equivalent unless the user configures\n * both, and full-reference equality below is deliberately provider-scoped).\n */\nimport { sameModelRef, type ModelRef } from \"../work-unit/types.js\"\n\nexport function normalizeTierList(resources: readonly ModelRef[]): ModelRef[] {\n const kept: ModelRef[] = []\n for (const resource of resources) {\n if (resource.providerID.length === 0 || resource.id.length === 0) {\n continue\n }\n if (kept.some((existing) => sameModelRef(existing, resource))) {\n continue\n }\n kept.push(resource)\n }\n return kept\n}\n"]}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tier-policy types (ADP-002..006). Tier names are an ecosystem proposal —
|
|
3
|
+
* OpenCode has no native capability-tier API — so every tier semantic lives
|
|
4
|
+
* in this module and nowhere else.
|
|
5
|
+
*/
|
|
6
|
+
import type { ModelRef } from "../work-unit/types.js";
|
|
7
|
+
export declare const TIERS: readonly ["quick", "standard", "advanced"];
|
|
8
|
+
export type TierName = (typeof TIERS)[number];
|
|
9
|
+
/** Vocabulary guard without a cast: strict member equality. */
|
|
10
|
+
export declare function isTierName(tier: string): tier is TierName;
|
|
11
|
+
/**
|
|
12
|
+
* Request kinds the OpenCode `context` hook exposes for the executing
|
|
13
|
+
* request. Auxiliary kinds are trivial host-internal requests (title,
|
|
14
|
+
* compaction, generate) that must not consume an unnecessarily expensive
|
|
15
|
+
* tier — this is structured host evidence, not prompt keyword matching
|
|
16
|
+
* (ADP-004).
|
|
17
|
+
*/
|
|
18
|
+
export declare const AUXILIARY_KINDS: readonly ["compaction", "title", "generate"];
|
|
19
|
+
export type AuxiliaryKind = (typeof AUXILIARY_KINDS)[number];
|
|
20
|
+
export type RequestKind = "primary" | AuxiliaryKind;
|
|
21
|
+
/** Auxiliary requests route to `quick`. */
|
|
22
|
+
export declare const AUXILIARY_TIER: TierName;
|
|
23
|
+
/** Unambiguous fallback tier (ADP-002). */
|
|
24
|
+
export declare const DEFAULT_TIER: TierName;
|
|
25
|
+
/** How an explicit tier request reached the plugin (provenance for receipts). */
|
|
26
|
+
export declare const TIER_REQUEST_VIAS: readonly ["manual-command", "session-setting"];
|
|
27
|
+
export type TierRequestVia = (typeof TIER_REQUEST_VIAS)[number];
|
|
28
|
+
export interface TierRequest {
|
|
29
|
+
readonly tier: TierName;
|
|
30
|
+
readonly via: TierRequestVia;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Everything the ladder may look at, all optional except the configuration
|
|
34
|
+
* view. Every signal is a structured field supplied by the adapter — never
|
|
35
|
+
* text mined from a prompt.
|
|
36
|
+
*/
|
|
37
|
+
export interface RoutingInput {
|
|
38
|
+
/** Explicit user/model pin (manual control); always wins (ADP-002). */
|
|
39
|
+
readonly pin?: ModelRef | undefined;
|
|
40
|
+
/** Explicit tier request (manual command or session setting). */
|
|
41
|
+
readonly tierRequest?: TierRequest | undefined;
|
|
42
|
+
/** OpenCode agent name of the executing request, when the host supplies one. */
|
|
43
|
+
readonly agent?: string | undefined;
|
|
44
|
+
/** Request kind from the `context` hook, when known. */
|
|
45
|
+
readonly requestKind?: RequestKind | undefined;
|
|
46
|
+
/** User-configured agent-name → tier mapping (role hints are user policy). */
|
|
47
|
+
readonly agentTiers?: Readonly<Record<string, TierName>> | undefined;
|
|
48
|
+
/**
|
|
49
|
+
* Tiers that currently have at least one configured resource (ADP-005).
|
|
50
|
+
* A tier without resources can never be honored — Adaptive does not
|
|
51
|
+
* invent resources and never borrows across tiers (GOAL tenet 4).
|
|
52
|
+
*/
|
|
53
|
+
readonly configuredTiers: readonly TierName[];
|
|
54
|
+
}
|
|
55
|
+
/** Why the ladder produced a tier decision (receipt provenance). */
|
|
56
|
+
export declare const ROUTING_SOURCES: readonly ["pin", "tier-request", "agent-role", "auxiliary-request", "ambiguity", "default"];
|
|
57
|
+
export type RoutingSource = (typeof ROUTING_SOURCES)[number];
|
|
58
|
+
export declare const DEFER_REASONS: readonly ["tier-not-configured"];
|
|
59
|
+
export type DeferReason = (typeof DEFER_REASONS)[number];
|
|
60
|
+
/**
|
|
61
|
+
* Routing outcome (ADP-002/003):
|
|
62
|
+
* - `pin` — route to the pinned resource.
|
|
63
|
+
* - `tier` — route within the named tier.
|
|
64
|
+
* - `defer` — Adaptive has no reason to override ordinary OpenCode behavior;
|
|
65
|
+
* the unit keeps OpenCode's model choice. `tier` records which tier the
|
|
66
|
+
* ladder resolved to before the configuration gate blocked it.
|
|
67
|
+
*/
|
|
68
|
+
export type RoutingDecision = {
|
|
69
|
+
readonly kind: "pin";
|
|
70
|
+
readonly model: ModelRef;
|
|
71
|
+
} | {
|
|
72
|
+
readonly kind: "tier";
|
|
73
|
+
readonly tier: TierName;
|
|
74
|
+
readonly source: RoutingSource;
|
|
75
|
+
} | {
|
|
76
|
+
readonly kind: "defer";
|
|
77
|
+
readonly tier: TierName;
|
|
78
|
+
readonly reason: DeferReason;
|
|
79
|
+
};
|
|
80
|
+
/** True only for the trivial auxiliary request kinds. */
|
|
81
|
+
export declare function isAuxiliary(kind: RequestKind): boolean;
|
|
82
|
+
/**
|
|
83
|
+
* Escalation ladder order (GOAL capability escalation): `quick` → `standard`
|
|
84
|
+
* → `advanced`. `advanced` is the last tier — there is no next.
|
|
85
|
+
*/
|
|
86
|
+
export declare function nextTier(tier: TierName): TierName | undefined;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
export const TIERS = ["quick", "standard", "advanced"];
|
|
2
|
+
/** Vocabulary guard without a cast: strict member equality. */
|
|
3
|
+
export function isTierName(tier) {
|
|
4
|
+
return TIERS.some((name) => name === tier);
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Request kinds the OpenCode `context` hook exposes for the executing
|
|
8
|
+
* request. Auxiliary kinds are trivial host-internal requests (title,
|
|
9
|
+
* compaction, generate) that must not consume an unnecessarily expensive
|
|
10
|
+
* tier — this is structured host evidence, not prompt keyword matching
|
|
11
|
+
* (ADP-004).
|
|
12
|
+
*/
|
|
13
|
+
export const AUXILIARY_KINDS = [
|
|
14
|
+
"compaction",
|
|
15
|
+
"title",
|
|
16
|
+
"generate",
|
|
17
|
+
];
|
|
18
|
+
/** Auxiliary requests route to `quick`. */
|
|
19
|
+
export const AUXILIARY_TIER = "quick";
|
|
20
|
+
/** Unambiguous fallback tier (ADP-002). */
|
|
21
|
+
export const DEFAULT_TIER = "standard";
|
|
22
|
+
/** How an explicit tier request reached the plugin (provenance for receipts). */
|
|
23
|
+
export const TIER_REQUEST_VIAS = [
|
|
24
|
+
"manual-command",
|
|
25
|
+
"session-setting",
|
|
26
|
+
];
|
|
27
|
+
/** Why the ladder produced a tier decision (receipt provenance). */
|
|
28
|
+
export const ROUTING_SOURCES = [
|
|
29
|
+
"pin",
|
|
30
|
+
"tier-request",
|
|
31
|
+
"agent-role",
|
|
32
|
+
"auxiliary-request",
|
|
33
|
+
"ambiguity",
|
|
34
|
+
"default",
|
|
35
|
+
];
|
|
36
|
+
export const DEFER_REASONS = ["tier-not-configured"];
|
|
37
|
+
/** True only for the trivial auxiliary request kinds. */
|
|
38
|
+
export function isAuxiliary(kind) {
|
|
39
|
+
return kind !== "primary";
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Escalation ladder order (GOAL capability escalation): `quick` → `standard`
|
|
43
|
+
* → `advanced`. `advanced` is the last tier — there is no next.
|
|
44
|
+
*/
|
|
45
|
+
export function nextTier(tier) {
|
|
46
|
+
const index = TIERS.indexOf(tier);
|
|
47
|
+
return TIERS[index + 1];
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/core/tier-policy/types.ts"],"names":[],"mappings":"AAOA,MAAM,CAAC,MAAM,KAAK,GAAG,CAAC,OAAO,EAAE,UAAU,EAAE,UAAU,CAAsC,CAAA;AAI3F,+DAA+D;AAC/D,MAAM,UAAU,UAAU,CAAC,IAAY;IACrC,OAAO,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,KAAK,IAAI,CAAC,CAAA;AAC5C,CAAC;AAED;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG;IAC7B,YAAY;IACZ,OAAO;IACP,UAAU;CAC0B,CAAA;AAMtC,2CAA2C;AAC3C,MAAM,CAAC,MAAM,cAAc,GAAa,OAAO,CAAA;AAE/C,2CAA2C;AAC3C,MAAM,CAAC,MAAM,YAAY,GAAa,UAAU,CAAA;AAEhD,iFAAiF;AACjF,MAAM,CAAC,MAAM,iBAAiB,GAAG;IAC/B,gBAAgB;IAChB,iBAAiB;CACmB,CAAA;AAiCtC,oEAAoE;AACpE,MAAM,CAAC,MAAM,eAAe,GAAG;IAC7B,KAAK;IACL,cAAc;IACd,YAAY;IACZ,mBAAmB;IACnB,WAAW;IACX,SAAS;CAC2B,CAAA;AAItC,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,qBAAqB,CAAsC,CAAA;AAiBzF,yDAAyD;AACzD,MAAM,UAAU,WAAW,CAAC,IAAiB;IAC3C,OAAO,IAAI,KAAK,SAAS,CAAA;AAC3B,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,QAAQ,CAAC,IAAc;IACrC,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAA;IACjC,OAAO,KAAK,CAAC,KAAK,GAAG,CAAC,CAAC,CAAA;AACzB,CAAC","sourcesContent":["/**\n * Tier-policy types (ADP-002..006). Tier names are an ecosystem proposal —\n * OpenCode has no native capability-tier API — so every tier semantic lives\n * in this module and nowhere else.\n */\nimport type { ModelRef } from \"../work-unit/types.js\"\n\nexport const TIERS = [\"quick\", \"standard\", \"advanced\"] as const satisfies readonly string[]\n\nexport type TierName = (typeof TIERS)[number]\n\n/** Vocabulary guard without a cast: strict member equality. */\nexport function isTierName(tier: string): tier is TierName {\n return TIERS.some((name) => name === tier)\n}\n\n/**\n * Request kinds the OpenCode `context` hook exposes for the executing\n * request. Auxiliary kinds are trivial host-internal requests (title,\n * compaction, generate) that must not consume an unnecessarily expensive\n * tier — this is structured host evidence, not prompt keyword matching\n * (ADP-004).\n */\nexport const AUXILIARY_KINDS = [\n \"compaction\",\n \"title\",\n \"generate\",\n] as const satisfies readonly string[]\n\nexport type AuxiliaryKind = (typeof AUXILIARY_KINDS)[number]\n\nexport type RequestKind = \"primary\" | AuxiliaryKind\n\n/** Auxiliary requests route to `quick`. */\nexport const AUXILIARY_TIER: TierName = \"quick\"\n\n/** Unambiguous fallback tier (ADP-002). */\nexport const DEFAULT_TIER: TierName = \"standard\"\n\n/** How an explicit tier request reached the plugin (provenance for receipts). */\nexport const TIER_REQUEST_VIAS = [\n \"manual-command\",\n \"session-setting\",\n] as const satisfies readonly string[]\n\nexport type TierRequestVia = (typeof TIER_REQUEST_VIAS)[number]\n\nexport interface TierRequest {\n readonly tier: TierName\n readonly via: TierRequestVia\n}\n\n/**\n * Everything the ladder may look at, all optional except the configuration\n * view. Every signal is a structured field supplied by the adapter — never\n * text mined from a prompt.\n */\nexport interface RoutingInput {\n /** Explicit user/model pin (manual control); always wins (ADP-002). */\n readonly pin?: ModelRef | undefined\n /** Explicit tier request (manual command or session setting). */\n readonly tierRequest?: TierRequest | undefined\n /** OpenCode agent name of the executing request, when the host supplies one. */\n readonly agent?: string | undefined\n /** Request kind from the `context` hook, when known. */\n readonly requestKind?: RequestKind | undefined\n /** User-configured agent-name → tier mapping (role hints are user policy). */\n readonly agentTiers?: Readonly<Record<string, TierName>> | undefined\n /**\n * Tiers that currently have at least one configured resource (ADP-005).\n * A tier without resources can never be honored — Adaptive does not\n * invent resources and never borrows across tiers (GOAL tenet 4).\n */\n readonly configuredTiers: readonly TierName[]\n}\n\n/** Why the ladder produced a tier decision (receipt provenance). */\nexport const ROUTING_SOURCES = [\n \"pin\",\n \"tier-request\",\n \"agent-role\",\n \"auxiliary-request\",\n \"ambiguity\",\n \"default\",\n] as const satisfies readonly string[]\n\nexport type RoutingSource = (typeof ROUTING_SOURCES)[number]\n\nexport const DEFER_REASONS = [\"tier-not-configured\"] as const satisfies readonly string[]\n\nexport type DeferReason = (typeof DEFER_REASONS)[number]\n\n/**\n * Routing outcome (ADP-002/003):\n * - `pin` — route to the pinned resource.\n * - `tier` — route within the named tier.\n * - `defer` — Adaptive has no reason to override ordinary OpenCode behavior;\n * the unit keeps OpenCode's model choice. `tier` records which tier the\n * ladder resolved to before the configuration gate blocked it.\n */\nexport type RoutingDecision =\n | { readonly kind: \"pin\"; readonly model: ModelRef }\n | { readonly kind: \"tier\"; readonly tier: TierName; readonly source: RoutingSource }\n | { readonly kind: \"defer\"; readonly tier: TierName; readonly reason: DeferReason }\n\n/** True only for the trivial auxiliary request kinds. */\nexport function isAuxiliary(kind: RequestKind): boolean {\n return kind !== \"primary\"\n}\n\n/**\n * Escalation ladder order (GOAL capability escalation): `quick` → `standard`\n * → `advanced`. `advanced` is the last tier — there is no next.\n */\nexport function nextTier(tier: TierName): TierName | undefined {\n const index = TIERS.indexOf(tier)\n return TIERS[index + 1]\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/core/work-unit/index.ts"],"names":[],"mappings":"AAAA,cAAc,mBAAmB,CAAA;AACjC,cAAc,YAAY,CAAA;AAC1B,cAAc,cAAc,CAAA","sourcesContent":["export * from \"./interruption.js\"\nexport * from \"./types.js\"\nexport * from \"./tracker.js\"\n"]}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Why an OpenCode execution was interrupted. Values mirror the verified
|
|
3
|
+
* `session.execution.interrupted` event payload (`reason`), which OpenCode
|
|
4
|
+
* guarantees to be one of these four.
|
|
5
|
+
*/
|
|
6
|
+
export declare const INTERRUPT_REASONS: readonly ["user", "shutdown", "superseded", "inactivity"];
|
|
7
|
+
export type InterruptReason = (typeof INTERRUPT_REASONS)[number];
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Why an OpenCode execution was interrupted. Values mirror the verified
|
|
3
|
+
* `session.execution.interrupted` event payload (`reason`), which OpenCode
|
|
4
|
+
* guarantees to be one of these four.
|
|
5
|
+
*/
|
|
6
|
+
export const INTERRUPT_REASONS = [
|
|
7
|
+
"user",
|
|
8
|
+
"shutdown",
|
|
9
|
+
"superseded",
|
|
10
|
+
"inactivity",
|
|
11
|
+
];
|
|
12
|
+
//# sourceMappingURL=interruption.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"interruption.js","sourceRoot":"","sources":["../../../src/core/work-unit/interruption.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG;IAC/B,MAAM;IACN,UAAU;IACV,YAAY;IACZ,YAAY;CACwB,CAAA","sourcesContent":["/**\n * Why an OpenCode execution was interrupted. Values mirror the verified\n * `session.execution.interrupted` event payload (`reason`), which OpenCode\n * guarantees to be one of these four.\n */\nexport const INTERRUPT_REASONS = [\n \"user\",\n \"shutdown\",\n \"superseded\",\n \"inactivity\",\n] as const satisfies readonly string[]\n\nexport type InterruptReason = (typeof INTERRUPT_REASONS)[number]\n"]}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Episode tracker (ADP-001): a deterministic, clock-free state machine over
|
|
3
|
+
* normalized work-unit events, keyed by session. All timestamps come from the
|
|
4
|
+
* events themselves, so behavior is fully reproducible.
|
|
5
|
+
*
|
|
6
|
+
* Self-switch attribution: `session.model.selected` fires for both manual and
|
|
7
|
+
* plugin-made switches. Before performing `session.switchModel` our adapter
|
|
8
|
+
* calls `noteOwnSwitch`; the next `model-selected` event for that session
|
|
9
|
+
* matching the noted reference is attributed `selected-adaptive` and consumes
|
|
10
|
+
* the note. A non-matching selection clears the note, so a stale note can
|
|
11
|
+
* never claim a later user pick — the failure direction is always
|
|
12
|
+
* "our switch looks external" (treated as a manual pick, left alone), never
|
|
13
|
+
* the reverse. At most one note per session is kept: a newer note replaces an
|
|
14
|
+
* unconsumed older one, and the older switch's event (if it ever arrives) is
|
|
15
|
+
* then attributed external — again the safe direction, since the adapter
|
|
16
|
+
* serializes per-session switches.
|
|
17
|
+
*/
|
|
18
|
+
import { type ActiveUnit, type ApplyResult, type ModelRef, type ModelTransition, type OpenedResult, type SelectionResult, type WorkUnitEvent } from "./types.js";
|
|
19
|
+
export declare class WorkUnitTracker {
|
|
20
|
+
#private;
|
|
21
|
+
/**
|
|
22
|
+
* Bounded per-session maps (last model, pending own-switch note): a
|
|
23
|
+
* long-lived host meets unbounded short-lived sessions, and the model
|
|
24
|
+
* knowledge of a long-gone session is worthless. Insertion order gives the
|
|
25
|
+
* eviction (oldest session first).
|
|
26
|
+
*/
|
|
27
|
+
static readonly MAX_TRACKED_SESSIONS = 1024;
|
|
28
|
+
/**
|
|
29
|
+
* Arms the pending own-switch note consumed by the next matching
|
|
30
|
+
* `model-selected` event for this session.
|
|
31
|
+
*/
|
|
32
|
+
noteOwnSwitch(sessionID: string, model: ModelRef): void;
|
|
33
|
+
/**
|
|
34
|
+
* Raises each session's episode floor. Used at startup: unit ids are
|
|
35
|
+
* durable keys (`${sessionID}#${episode}`) persisted in receipts and
|
|
36
|
+
* parked work, so after a restart the counters must never re-mint an id
|
|
37
|
+
* a persisted record already carries — a collision would replace parked
|
|
38
|
+
* work and misattribute receipt outcomes.
|
|
39
|
+
*/
|
|
40
|
+
seedEpisodes(seeds: ReadonlyMap<string, number>): void;
|
|
41
|
+
/** Apply an execution-started event; the tracker always opens a unit. */
|
|
42
|
+
apply(event: Extract<WorkUnitEvent, {
|
|
43
|
+
kind: "execution-started";
|
|
44
|
+
}>): OpenedResult;
|
|
45
|
+
apply(event: Extract<WorkUnitEvent, {
|
|
46
|
+
kind: "model-selected" | "model-request";
|
|
47
|
+
}>): SelectionResult;
|
|
48
|
+
apply(event: WorkUnitEvent): ApplyResult;
|
|
49
|
+
/** The open unit for a session, if any. */
|
|
50
|
+
active(sessionID: string): ActiveUnit | undefined;
|
|
51
|
+
/** Last model known to be in effect for a session, across episodes. */
|
|
52
|
+
lastModel(sessionID: string): ModelTransition | undefined;
|
|
53
|
+
}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
var _a;
|
|
2
|
+
/**
|
|
3
|
+
* Episode tracker (ADP-001): a deterministic, clock-free state machine over
|
|
4
|
+
* normalized work-unit events, keyed by session. All timestamps come from the
|
|
5
|
+
* events themselves, so behavior is fully reproducible.
|
|
6
|
+
*
|
|
7
|
+
* Self-switch attribution: `session.model.selected` fires for both manual and
|
|
8
|
+
* plugin-made switches. Before performing `session.switchModel` our adapter
|
|
9
|
+
* calls `noteOwnSwitch`; the next `model-selected` event for that session
|
|
10
|
+
* matching the noted reference is attributed `selected-adaptive` and consumes
|
|
11
|
+
* the note. A non-matching selection clears the note, so a stale note can
|
|
12
|
+
* never claim a later user pick — the failure direction is always
|
|
13
|
+
* "our switch looks external" (treated as a manual pick, left alone), never
|
|
14
|
+
* the reverse. At most one note per session is kept: a newer note replaces an
|
|
15
|
+
* unconsumed older one, and the older switch's event (if it ever arrives) is
|
|
16
|
+
* then attributed external — again the safe direction, since the adapter
|
|
17
|
+
* serializes per-session switches.
|
|
18
|
+
*/
|
|
19
|
+
import { sameModelRef, } from "./types.js";
|
|
20
|
+
/** Insert into a bounded map, evicting the oldest (first) entry past the cap. */
|
|
21
|
+
function setBounded(map, key, value, cap) {
|
|
22
|
+
if (!map.has(key) && map.size >= cap) {
|
|
23
|
+
for (const oldest of map.keys()) {
|
|
24
|
+
map.delete(oldest);
|
|
25
|
+
break;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
map.set(key, value);
|
|
29
|
+
}
|
|
30
|
+
export class WorkUnitTracker {
|
|
31
|
+
#active = new Map();
|
|
32
|
+
// Intentionally unbounded: evicting an episode counter could re-mint a unit
|
|
33
|
+
// id (`${sessionID}#${episode}`) that a persisted receipt or parked record
|
|
34
|
+
// already carries — a collision replaces parked work. The map holds one
|
|
35
|
+
// number per session ever seen; that growth is bounded by session churn,
|
|
36
|
+
// not by episodes.
|
|
37
|
+
#episodes = new Map();
|
|
38
|
+
/**
|
|
39
|
+
* Bounded per-session maps (last model, pending own-switch note): a
|
|
40
|
+
* long-lived host meets unbounded short-lived sessions, and the model
|
|
41
|
+
* knowledge of a long-gone session is worthless. Insertion order gives the
|
|
42
|
+
* eviction (oldest session first).
|
|
43
|
+
*/
|
|
44
|
+
static MAX_TRACKED_SESSIONS = 1024;
|
|
45
|
+
#lastModel = new Map();
|
|
46
|
+
#pendingOwn = new Map();
|
|
47
|
+
/**
|
|
48
|
+
* Arms the pending own-switch note consumed by the next matching
|
|
49
|
+
* `model-selected` event for this session.
|
|
50
|
+
*/
|
|
51
|
+
noteOwnSwitch(sessionID, model) {
|
|
52
|
+
setBounded(this.#pendingOwn, sessionID, model, _a.MAX_TRACKED_SESSIONS);
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Raises each session's episode floor. Used at startup: unit ids are
|
|
56
|
+
* durable keys (`${sessionID}#${episode}`) persisted in receipts and
|
|
57
|
+
* parked work, so after a restart the counters must never re-mint an id
|
|
58
|
+
* a persisted record already carries — a collision would replace parked
|
|
59
|
+
* work and misattribute receipt outcomes.
|
|
60
|
+
*/
|
|
61
|
+
seedEpisodes(seeds) {
|
|
62
|
+
for (const [sessionID, episode] of seeds) {
|
|
63
|
+
if (episode > (this.#episodes.get(sessionID) ?? 0))
|
|
64
|
+
this.#episodes.set(sessionID, episode);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
apply(event) {
|
|
68
|
+
switch (event.kind) {
|
|
69
|
+
case "execution-started": {
|
|
70
|
+
const previous = this.#active.get(event.sessionID);
|
|
71
|
+
if (previous !== undefined)
|
|
72
|
+
this.#active.delete(event.sessionID);
|
|
73
|
+
// A new execution on a session that still has an active unit means
|
|
74
|
+
// the previous execution was superseded (matches OpenCode's
|
|
75
|
+
// `interrupted{reason:"superseded"}` semantics).
|
|
76
|
+
const superseded = previous && {
|
|
77
|
+
...previous,
|
|
78
|
+
kind: "interrupted",
|
|
79
|
+
closedAt: event.at,
|
|
80
|
+
reason: "superseded",
|
|
81
|
+
};
|
|
82
|
+
const episode = (this.#episodes.get(event.sessionID) ?? 0) + 1;
|
|
83
|
+
this.#episodes.set(event.sessionID, episode);
|
|
84
|
+
const unit = {
|
|
85
|
+
kind: "active",
|
|
86
|
+
id: `${event.sessionID}#${String(episode)}`,
|
|
87
|
+
sessionID: event.sessionID,
|
|
88
|
+
episode,
|
|
89
|
+
startedAt: event.at,
|
|
90
|
+
model: this.#lastModel.get(event.sessionID)?.model,
|
|
91
|
+
transitions: [],
|
|
92
|
+
};
|
|
93
|
+
this.#active.set(event.sessionID, unit);
|
|
94
|
+
return superseded ? { kind: "opened", unit, superseded } : { kind: "opened", unit };
|
|
95
|
+
}
|
|
96
|
+
case "execution-succeeded":
|
|
97
|
+
case "execution-failed":
|
|
98
|
+
case "execution-interrupted": {
|
|
99
|
+
const current = this.#active.get(event.sessionID);
|
|
100
|
+
if (current === undefined)
|
|
101
|
+
return { kind: "ignored", reason: "terminal-without-active-unit" };
|
|
102
|
+
this.#active.delete(event.sessionID);
|
|
103
|
+
const base = { ...current, closedAt: event.at };
|
|
104
|
+
if (event.kind === "execution-succeeded") {
|
|
105
|
+
return { kind: "closed", unit: { ...base, kind: "succeeded" } };
|
|
106
|
+
}
|
|
107
|
+
if (event.kind === "execution-failed") {
|
|
108
|
+
return { kind: "closed", unit: { ...base, kind: "failed", error: event.error } };
|
|
109
|
+
}
|
|
110
|
+
return { kind: "closed", unit: { ...base, kind: "interrupted", reason: event.reason } };
|
|
111
|
+
}
|
|
112
|
+
case "model-selected": {
|
|
113
|
+
const adaptive = this.#consumePendingOwn(event.sessionID, event.model);
|
|
114
|
+
return this.#trackModel(event.sessionID, event.at, event.model, adaptive ? "selected-adaptive" : "selected-external");
|
|
115
|
+
}
|
|
116
|
+
case "model-request":
|
|
117
|
+
// Context-hook observations never consume the pending note: only
|
|
118
|
+
// actual selection events resolve it.
|
|
119
|
+
return this.#trackModel(event.sessionID, event.at, event.model, "request");
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
/** The open unit for a session, if any. */
|
|
123
|
+
active(sessionID) {
|
|
124
|
+
return this.#active.get(sessionID);
|
|
125
|
+
}
|
|
126
|
+
/** Last model known to be in effect for a session, across episodes. */
|
|
127
|
+
lastModel(sessionID) {
|
|
128
|
+
return this.#lastModel.get(sessionID);
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Returns whether the pending note matched. The note is consumed on any
|
|
132
|
+
* selection event: matched → `selected-adaptive`; unmatched (or absent) →
|
|
133
|
+
* `selected-external` with the note dropped.
|
|
134
|
+
*/
|
|
135
|
+
#consumePendingOwn(sessionID, model) {
|
|
136
|
+
const pending = this.#pendingOwn.get(sessionID);
|
|
137
|
+
this.#pendingOwn.delete(sessionID);
|
|
138
|
+
return pending !== undefined && sameModelRef(pending, model);
|
|
139
|
+
}
|
|
140
|
+
#trackModel(sessionID, at, model, source) {
|
|
141
|
+
const transition = { at, model, source };
|
|
142
|
+
setBounded(this.#lastModel, sessionID, transition, _a.MAX_TRACKED_SESSIONS);
|
|
143
|
+
const current = this.#active.get(sessionID);
|
|
144
|
+
if (current === undefined)
|
|
145
|
+
return { kind: "model-noted", transition };
|
|
146
|
+
const unit = {
|
|
147
|
+
...current,
|
|
148
|
+
model,
|
|
149
|
+
transitions: [...current.transitions, transition],
|
|
150
|
+
};
|
|
151
|
+
this.#active.set(sessionID, unit);
|
|
152
|
+
return { kind: "model-tracked", unit, transition };
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
_a = WorkUnitTracker;
|
|
156
|
+
//# sourceMappingURL=tracker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tracker.js","sourceRoot":"","sources":["../../../src/core/work-unit/tracker.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;GAgBG;AACH,OAAO,EACL,YAAY,GAUb,MAAM,YAAY,CAAA;AAEnB,iFAAiF;AACjF,SAAS,UAAU,CAAO,GAAc,EAAE,GAAM,EAAE,KAAQ,EAAE,GAAW;IACrE,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC,IAAI,IAAI,GAAG,EAAE,CAAC;QACrC,KAAK,MAAM,MAAM,IAAI,GAAG,CAAC,IAAI,EAAE,EAAE,CAAC;YAChC,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,CAAA;YAClB,MAAK;QACP,CAAC;IACH,CAAC;IACD,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAA;AACrB,CAAC;AAED,MAAM,OAAO,eAAe;IACjB,OAAO,GAAG,IAAI,GAAG,EAAsB,CAAA;IAChD,4EAA4E;IAC5E,2EAA2E;IAC3E,wEAAwE;IACxE,yEAAyE;IACzE,mBAAmB;IACV,SAAS,GAAG,IAAI,GAAG,EAAkB,CAAA;IAC9C;;;;;OAKG;IACH,MAAM,CAAU,oBAAoB,GAAG,IAAI,CAAA;IAClC,UAAU,GAAG,IAAI,GAAG,EAA2B,CAAA;IAC/C,WAAW,GAAG,IAAI,GAAG,EAAoB,CAAA;IAElD;;;OAGG;IACH,aAAa,CAAC,SAAiB,EAAE,KAAe;QAC9C,UAAU,CAAC,IAAI,CAAC,WAAW,EAAE,SAAS,EAAE,KAAK,EAAE,EAAe,CAAC,oBAAoB,CAAC,CAAA;IACtF,CAAC;IAED;;;;;;OAMG;IACH,YAAY,CAAC,KAAkC;QAC7C,KAAK,MAAM,CAAC,SAAS,EAAE,OAAO,CAAC,IAAI,KAAK,EAAE,CAAC;YACzC,IAAI,OAAO,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;gBAAE,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,SAAS,EAAE,OAAO,CAAC,CAAA;QAC5F,CAAC;IACH,CAAC;IAQD,KAAK,CAAC,KAAoB;QACxB,QAAQ,KAAK,CAAC,IAAI,EAAE,CAAC;YACnB,KAAK,mBAAmB,CAAC,CAAC,CAAC;gBACzB,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,SAAS,CAAC,CAAA;gBAClD,IAAI,QAAQ,KAAK,SAAS;oBAAE,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,SAAS,CAAC,CAAA;gBAChE,mEAAmE;gBACnE,4DAA4D;gBAC5D,iDAAiD;gBACjD,MAAM,UAAU,GAA2B,QAAQ,IAAI;oBACrD,GAAG,QAAQ;oBACX,IAAI,EAAE,aAAa;oBACnB,QAAQ,EAAE,KAAK,CAAC,EAAE;oBAClB,MAAM,EAAE,YAAY;iBACrB,CAAA;gBACD,MAAM,OAAO,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,KAAK,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAA;gBAC9D,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,KAAK,CAAC,SAAS,EAAE,OAAO,CAAC,CAAA;gBAC5C,MAAM,IAAI,GAAe;oBACvB,IAAI,EAAE,QAAQ;oBACd,EAAE,EAAE,GAAG,KAAK,CAAC,SAAS,IAAI,MAAM,CAAC,OAAO,CAAC,EAAE;oBAC3C,SAAS,EAAE,KAAK,CAAC,SAAS;oBAC1B,OAAO;oBACP,SAAS,EAAE,KAAK,CAAC,EAAE;oBACnB,KAAK,EAAE,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,KAAK,CAAC,SAAS,CAAC,EAAE,KAAK;oBAClD,WAAW,EAAE,EAAE;iBAChB,CAAA;gBACD,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,SAAS,EAAE,IAAI,CAAC,CAAA;gBACvC,OAAO,UAAU,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAA;YACrF,CAAC;YACD,KAAK,qBAAqB,CAAC;YAC3B,KAAK,kBAAkB,CAAC;YACxB,KAAK,uBAAuB,CAAC,CAAC,CAAC;gBAC7B,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,SAAS,CAAC,CAAA;gBACjD,IAAI,OAAO,KAAK,SAAS;oBACvB,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,8BAA8B,EAAE,CAAA;gBACpE,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,SAAS,CAAC,CAAA;gBACpC,MAAM,IAAI,GAAG,EAAE,GAAG,OAAO,EAAE,QAAQ,EAAE,KAAK,CAAC,EAAE,EAAE,CAAA;gBAC/C,IAAI,KAAK,CAAC,IAAI,KAAK,qBAAqB,EAAE,CAAC;oBACzC,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,EAAE,GAAG,IAAI,EAAE,IAAI,EAAE,WAAW,EAAE,EAAE,CAAA;gBACjE,CAAC;gBACD,IAAI,KAAK,CAAC,IAAI,KAAK,kBAAkB,EAAE,CAAC;oBACtC,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,EAAE,GAAG,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE,EAAE,CAAA;gBAClF,CAAC;gBACD,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,EAAE,GAAG,IAAI,EAAE,IAAI,EAAE,aAAa,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,EAAE,CAAA;YACzF,CAAC;YACD,KAAK,gBAAgB,CAAC,CAAC,CAAC;gBACtB,MAAM,QAAQ,GAAG,IAAI,CAAC,kBAAkB,CAAC,KAAK,CAAC,SAAS,EAAE,KAAK,CAAC,KAAK,CAAC,CAAA;gBACtE,OAAO,IAAI,CAAC,WAAW,CACrB,KAAK,CAAC,SAAS,EACf,KAAK,CAAC,EAAE,EACR,KAAK,CAAC,KAAK,EACX,QAAQ,CAAC,CAAC,CAAC,mBAAmB,CAAC,CAAC,CAAC,mBAAmB,CACrD,CAAA;YACH,CAAC;YACD,KAAK,eAAe;gBAClB,iEAAiE;gBACjE,sCAAsC;gBACtC,OAAO,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,SAAS,EAAE,KAAK,CAAC,EAAE,EAAE,KAAK,CAAC,KAAK,EAAE,SAAS,CAAC,CAAA;QAC9E,CAAC;IACH,CAAC;IAED,2CAA2C;IAC3C,MAAM,CAAC,SAAiB;QACtB,OAAO,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,CAAA;IACpC,CAAC;IAED,uEAAuE;IACvE,SAAS,CAAC,SAAiB;QACzB,OAAO,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,SAAS,CAAC,CAAA;IACvC,CAAC;IAED;;;;OAIG;IACH,kBAAkB,CAAC,SAAiB,EAAE,KAAe;QACnD,MAAM,OAAO,GAAG,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,SAAS,CAAC,CAAA;QAC/C,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,SAAS,CAAC,CAAA;QAClC,OAAO,OAAO,KAAK,SAAS,IAAI,YAAY,CAAC,OAAO,EAAE,KAAK,CAAC,CAAA;IAC9D,CAAC;IAED,WAAW,CAAC,SAAiB,EAAE,EAAU,EAAE,KAAe,EAAE,MAAmB;QAC7E,MAAM,UAAU,GAAoB,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,CAAA;QACzD,UAAU,CAAC,IAAI,CAAC,UAAU,EAAE,SAAS,EAAE,UAAU,EAAE,EAAe,CAAC,oBAAoB,CAAC,CAAA;QACxF,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,CAAA;QAC3C,IAAI,OAAO,KAAK,SAAS;YAAE,OAAO,EAAE,IAAI,EAAE,aAAa,EAAE,UAAU,EAAE,CAAA;QACrE,MAAM,IAAI,GAAe;YACvB,GAAG,OAAO;YACV,KAAK;YACL,WAAW,EAAE,CAAC,GAAG,OAAO,CAAC,WAAW,EAAE,UAAU,CAAC;SAClD,CAAA;QACD,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,SAAS,EAAE,IAAI,CAAC,CAAA;QACjC,OAAO,EAAE,IAAI,EAAE,eAAe,EAAE,IAAI,EAAE,UAAU,EAAE,CAAA;IACpD,CAAC","sourcesContent":["/**\n * Episode tracker (ADP-001): a deterministic, clock-free state machine over\n * normalized work-unit events, keyed by session. All timestamps come from the\n * events themselves, so behavior is fully reproducible.\n *\n * Self-switch attribution: `session.model.selected` fires for both manual and\n * plugin-made switches. Before performing `session.switchModel` our adapter\n * calls `noteOwnSwitch`; the next `model-selected` event for that session\n * matching the noted reference is attributed `selected-adaptive` and consumes\n * the note. A non-matching selection clears the note, so a stale note can\n * never claim a later user pick — the failure direction is always\n * \"our switch looks external\" (treated as a manual pick, left alone), never\n * the reverse. At most one note per session is kept: a newer note replaces an\n * unconsumed older one, and the older switch's event (if it ever arrives) is\n * then attributed external — again the safe direction, since the adapter\n * serializes per-session switches.\n */\nimport {\n sameModelRef,\n type ActiveUnit,\n type ApplyResult,\n type ClosedUnit,\n type ModelRef,\n type ModelSource,\n type ModelTransition,\n type OpenedResult,\n type SelectionResult,\n type WorkUnitEvent,\n} from \"./types.js\"\n\n/** Insert into a bounded map, evicting the oldest (first) entry past the cap. */\nfunction setBounded<K, V>(map: Map<K, V>, key: K, value: V, cap: number): void {\n if (!map.has(key) && map.size >= cap) {\n for (const oldest of map.keys()) {\n map.delete(oldest)\n break\n }\n }\n map.set(key, value)\n}\n\nexport class WorkUnitTracker {\n readonly #active = new Map<string, ActiveUnit>()\n // Intentionally unbounded: evicting an episode counter could re-mint a unit\n // id (`${sessionID}#${episode}`) that a persisted receipt or parked record\n // already carries — a collision replaces parked work. The map holds one\n // number per session ever seen; that growth is bounded by session churn,\n // not by episodes.\n readonly #episodes = new Map<string, number>()\n /**\n * Bounded per-session maps (last model, pending own-switch note): a\n * long-lived host meets unbounded short-lived sessions, and the model\n * knowledge of a long-gone session is worthless. Insertion order gives the\n * eviction (oldest session first).\n */\n static readonly MAX_TRACKED_SESSIONS = 1024\n readonly #lastModel = new Map<string, ModelTransition>()\n readonly #pendingOwn = new Map<string, ModelRef>()\n\n /**\n * Arms the pending own-switch note consumed by the next matching\n * `model-selected` event for this session.\n */\n noteOwnSwitch(sessionID: string, model: ModelRef): void {\n setBounded(this.#pendingOwn, sessionID, model, WorkUnitTracker.MAX_TRACKED_SESSIONS)\n }\n\n /**\n * Raises each session's episode floor. Used at startup: unit ids are\n * durable keys (`${sessionID}#${episode}`) persisted in receipts and\n * parked work, so after a restart the counters must never re-mint an id\n * a persisted record already carries — a collision would replace parked\n * work and misattribute receipt outcomes.\n */\n seedEpisodes(seeds: ReadonlyMap<string, number>): void {\n for (const [sessionID, episode] of seeds) {\n if (episode > (this.#episodes.get(sessionID) ?? 0)) this.#episodes.set(sessionID, episode)\n }\n }\n\n /** Apply an execution-started event; the tracker always opens a unit. */\n apply(event: Extract<WorkUnitEvent, { kind: \"execution-started\" }>): OpenedResult\n apply(\n event: Extract<WorkUnitEvent, { kind: \"model-selected\" | \"model-request\" }>,\n ): SelectionResult\n apply(event: WorkUnitEvent): ApplyResult\n apply(event: WorkUnitEvent): ApplyResult {\n switch (event.kind) {\n case \"execution-started\": {\n const previous = this.#active.get(event.sessionID)\n if (previous !== undefined) this.#active.delete(event.sessionID)\n // A new execution on a session that still has an active unit means\n // the previous execution was superseded (matches OpenCode's\n // `interrupted{reason:\"superseded\"}` semantics).\n const superseded: ClosedUnit | undefined = previous && {\n ...previous,\n kind: \"interrupted\",\n closedAt: event.at,\n reason: \"superseded\",\n }\n const episode = (this.#episodes.get(event.sessionID) ?? 0) + 1\n this.#episodes.set(event.sessionID, episode)\n const unit: ActiveUnit = {\n kind: \"active\",\n id: `${event.sessionID}#${String(episode)}`,\n sessionID: event.sessionID,\n episode,\n startedAt: event.at,\n model: this.#lastModel.get(event.sessionID)?.model,\n transitions: [],\n }\n this.#active.set(event.sessionID, unit)\n return superseded ? { kind: \"opened\", unit, superseded } : { kind: \"opened\", unit }\n }\n case \"execution-succeeded\":\n case \"execution-failed\":\n case \"execution-interrupted\": {\n const current = this.#active.get(event.sessionID)\n if (current === undefined)\n return { kind: \"ignored\", reason: \"terminal-without-active-unit\" }\n this.#active.delete(event.sessionID)\n const base = { ...current, closedAt: event.at }\n if (event.kind === \"execution-succeeded\") {\n return { kind: \"closed\", unit: { ...base, kind: \"succeeded\" } }\n }\n if (event.kind === \"execution-failed\") {\n return { kind: \"closed\", unit: { ...base, kind: \"failed\", error: event.error } }\n }\n return { kind: \"closed\", unit: { ...base, kind: \"interrupted\", reason: event.reason } }\n }\n case \"model-selected\": {\n const adaptive = this.#consumePendingOwn(event.sessionID, event.model)\n return this.#trackModel(\n event.sessionID,\n event.at,\n event.model,\n adaptive ? \"selected-adaptive\" : \"selected-external\",\n )\n }\n case \"model-request\":\n // Context-hook observations never consume the pending note: only\n // actual selection events resolve it.\n return this.#trackModel(event.sessionID, event.at, event.model, \"request\")\n }\n }\n\n /** The open unit for a session, if any. */\n active(sessionID: string): ActiveUnit | undefined {\n return this.#active.get(sessionID)\n }\n\n /** Last model known to be in effect for a session, across episodes. */\n lastModel(sessionID: string): ModelTransition | undefined {\n return this.#lastModel.get(sessionID)\n }\n\n /**\n * Returns whether the pending note matched. The note is consumed on any\n * selection event: matched → `selected-adaptive`; unmatched (or absent) →\n * `selected-external` with the note dropped.\n */\n #consumePendingOwn(sessionID: string, model: ModelRef): boolean {\n const pending = this.#pendingOwn.get(sessionID)\n this.#pendingOwn.delete(sessionID)\n return pending !== undefined && sameModelRef(pending, model)\n }\n\n #trackModel(sessionID: string, at: number, model: ModelRef, source: ModelSource): ApplyResult {\n const transition: ModelTransition = { at, model, source }\n setBounded(this.#lastModel, sessionID, transition, WorkUnitTracker.MAX_TRACKED_SESSIONS)\n const current = this.#active.get(sessionID)\n if (current === undefined) return { kind: \"model-noted\", transition }\n const unit: ActiveUnit = {\n ...current,\n model,\n transitions: [...current.transitions, transition],\n }\n this.#active.set(sessionID, unit)\n return { kind: \"model-tracked\", unit, transition }\n }\n}\n"]}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Work-unit model (ADP-001): one unit is exactly one OpenCode execution
|
|
3
|
+
* episode — `session.execution.started` → `succeeded`/`failed`/`interrupted`.
|
|
4
|
+
* Tool/step/text continuations never open, close, or split a unit (only the
|
|
5
|
+
* execution events do); a subagent runs in its own session and therefore gets
|
|
6
|
+
* its own units; a later continuation on the same session (e.g. a new /goal
|
|
7
|
+
* round) is a new episode of that session.
|
|
8
|
+
*/
|
|
9
|
+
import type { InterruptReason } from "./interruption.js";
|
|
10
|
+
/** OpenCode model reference (`Model.Ref`): provider, model, optional variant. */
|
|
11
|
+
export interface ModelRef {
|
|
12
|
+
readonly providerID: string;
|
|
13
|
+
readonly id: string;
|
|
14
|
+
readonly variant?: string | undefined;
|
|
15
|
+
}
|
|
16
|
+
/** Full-field equality; a variant change is a different reference. */
|
|
17
|
+
export declare function sameModelRef(a: ModelRef, b: ModelRef): boolean;
|
|
18
|
+
/**
|
|
19
|
+
* How the model in effect became known. `selected-*` comes from the
|
|
20
|
+
* `session.model.selected` event (which fires for both manual and
|
|
21
|
+
* plugin-made switches — the tracker resolves which via pending own-switch
|
|
22
|
+
* notes, see `WorkUnitTracker.noteOwnSwitch`); `request` comes from the
|
|
23
|
+
* `context` hook and reports the model actually executing.
|
|
24
|
+
*/
|
|
25
|
+
export declare const MODEL_SOURCES: readonly ["selected-adaptive", "selected-external", "request"];
|
|
26
|
+
export type ModelSource = (typeof MODEL_SOURCES)[number];
|
|
27
|
+
export interface ModelTransition {
|
|
28
|
+
readonly at: number;
|
|
29
|
+
readonly model: ModelRef;
|
|
30
|
+
readonly source: ModelSource;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Minimal shape of the wire `Session.StructuredError` carried by
|
|
34
|
+
* `session.execution.failed` (`response.body` is deliberately dropped here —
|
|
35
|
+
* the adapter folds it into bounded `ErrorEvidence` for the classifier).
|
|
36
|
+
*/
|
|
37
|
+
export interface ExecutionError {
|
|
38
|
+
readonly type: string;
|
|
39
|
+
readonly message: string;
|
|
40
|
+
readonly status?: number | undefined;
|
|
41
|
+
readonly retryAfterMs?: number | undefined;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* The episode lifecycle. `kind` doubles as status: `"active"` while the
|
|
45
|
+
* execution runs, then the terminal kind it closed with.
|
|
46
|
+
*/
|
|
47
|
+
interface UnitBase {
|
|
48
|
+
/** Stable per-episode identifier: `${sessionID}#${episode}`. */
|
|
49
|
+
readonly id: string;
|
|
50
|
+
readonly sessionID: string;
|
|
51
|
+
/** 1-based count of episodes started for this session (tracker lifetime). */
|
|
52
|
+
readonly episode: number;
|
|
53
|
+
readonly startedAt: number;
|
|
54
|
+
/**
|
|
55
|
+
* Model in effect for the unit: seeded from the last known selection when
|
|
56
|
+
* the unit opens, then updated by transitions.
|
|
57
|
+
*/
|
|
58
|
+
readonly model?: ModelRef | undefined;
|
|
59
|
+
readonly transitions: readonly ModelTransition[];
|
|
60
|
+
}
|
|
61
|
+
export interface ActiveUnit extends UnitBase {
|
|
62
|
+
readonly kind: "active";
|
|
63
|
+
}
|
|
64
|
+
export interface SucceededUnit extends UnitBase {
|
|
65
|
+
readonly kind: "succeeded";
|
|
66
|
+
readonly closedAt: number;
|
|
67
|
+
}
|
|
68
|
+
export interface FailedUnit extends UnitBase {
|
|
69
|
+
readonly kind: "failed";
|
|
70
|
+
readonly closedAt: number;
|
|
71
|
+
readonly error: ExecutionError;
|
|
72
|
+
}
|
|
73
|
+
export interface InterruptedUnit extends UnitBase {
|
|
74
|
+
readonly kind: "interrupted";
|
|
75
|
+
readonly closedAt: number;
|
|
76
|
+
readonly reason: InterruptReason;
|
|
77
|
+
}
|
|
78
|
+
export type ClosedUnit = SucceededUnit | FailedUnit | InterruptedUnit;
|
|
79
|
+
export type WorkUnit = ActiveUnit | ClosedUnit;
|
|
80
|
+
/**
|
|
81
|
+
* Normalized episode events. The OpenCode adapter unwraps host event payloads
|
|
82
|
+
* (`event.data.*`) and forwards them here; `model-request` is fed from the
|
|
83
|
+
* `context` session hook.
|
|
84
|
+
*/
|
|
85
|
+
export type WorkUnitEvent = {
|
|
86
|
+
readonly kind: "execution-started";
|
|
87
|
+
readonly sessionID: string;
|
|
88
|
+
readonly at: number;
|
|
89
|
+
} | {
|
|
90
|
+
readonly kind: "execution-succeeded";
|
|
91
|
+
readonly sessionID: string;
|
|
92
|
+
readonly at: number;
|
|
93
|
+
} | {
|
|
94
|
+
readonly kind: "execution-failed";
|
|
95
|
+
readonly sessionID: string;
|
|
96
|
+
readonly at: number;
|
|
97
|
+
readonly error: ExecutionError;
|
|
98
|
+
} | {
|
|
99
|
+
readonly kind: "execution-interrupted";
|
|
100
|
+
readonly sessionID: string;
|
|
101
|
+
readonly at: number;
|
|
102
|
+
readonly reason: InterruptReason;
|
|
103
|
+
} | {
|
|
104
|
+
readonly kind: "model-selected";
|
|
105
|
+
readonly sessionID: string;
|
|
106
|
+
readonly at: number;
|
|
107
|
+
readonly model: ModelRef;
|
|
108
|
+
} | {
|
|
109
|
+
readonly kind: "model-request";
|
|
110
|
+
readonly sessionID: string;
|
|
111
|
+
readonly at: number;
|
|
112
|
+
readonly model: ModelRef;
|
|
113
|
+
};
|
|
114
|
+
/**
|
|
115
|
+
* Why an event produced no state change. Only terminal events can be ignored
|
|
116
|
+
* — they arrive without an active unit when the terminal was already handled
|
|
117
|
+
* (duplicate delivery) or never preceded by `execution-started`.
|
|
118
|
+
*/
|
|
119
|
+
export declare const IGNORED_REASONS: readonly ["terminal-without-active-unit"];
|
|
120
|
+
export type IgnoredReason = (typeof IGNORED_REASONS)[number];
|
|
121
|
+
/** Result of an execution-started event: the tracker always opens (possibly superseding). */
|
|
122
|
+
export type OpenedResult = Extract<ApplyResult, {
|
|
123
|
+
kind: "opened";
|
|
124
|
+
}>;
|
|
125
|
+
/** Result of a selection event: the tracker always records model knowledge. */
|
|
126
|
+
export type SelectionResult = Extract<ApplyResult, {
|
|
127
|
+
kind: "model-noted";
|
|
128
|
+
} | {
|
|
129
|
+
kind: "model-tracked";
|
|
130
|
+
}>;
|
|
131
|
+
export type ApplyResult = {
|
|
132
|
+
readonly kind: "opened";
|
|
133
|
+
readonly unit: ActiveUnit;
|
|
134
|
+
readonly superseded?: ClosedUnit | undefined;
|
|
135
|
+
} | {
|
|
136
|
+
readonly kind: "closed";
|
|
137
|
+
readonly unit: ClosedUnit;
|
|
138
|
+
} | {
|
|
139
|
+
readonly kind: "model-tracked";
|
|
140
|
+
readonly unit: ActiveUnit;
|
|
141
|
+
readonly transition: ModelTransition;
|
|
142
|
+
}
|
|
143
|
+
/** Model knowledge recorded with no active unit; remembered for the next admission. */
|
|
144
|
+
| {
|
|
145
|
+
readonly kind: "model-noted";
|
|
146
|
+
readonly transition: ModelTransition;
|
|
147
|
+
} | {
|
|
148
|
+
readonly kind: "ignored";
|
|
149
|
+
readonly reason: IgnoredReason;
|
|
150
|
+
};
|
|
151
|
+
export {};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/** Full-field equality; a variant change is a different reference. */
|
|
2
|
+
export function sameModelRef(a, b) {
|
|
3
|
+
return a.providerID === b.providerID && a.id === b.id && a.variant === b.variant;
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* How the model in effect became known. `selected-*` comes from the
|
|
7
|
+
* `session.model.selected` event (which fires for both manual and
|
|
8
|
+
* plugin-made switches — the tracker resolves which via pending own-switch
|
|
9
|
+
* notes, see `WorkUnitTracker.noteOwnSwitch`); `request` comes from the
|
|
10
|
+
* `context` hook and reports the model actually executing.
|
|
11
|
+
*/
|
|
12
|
+
export const MODEL_SOURCES = [
|
|
13
|
+
"selected-adaptive",
|
|
14
|
+
"selected-external",
|
|
15
|
+
"request",
|
|
16
|
+
];
|
|
17
|
+
/**
|
|
18
|
+
* Why an event produced no state change. Only terminal events can be ignored
|
|
19
|
+
* — they arrive without an active unit when the terminal was already handled
|
|
20
|
+
* (duplicate delivery) or never preceded by `execution-started`.
|
|
21
|
+
*/
|
|
22
|
+
export const IGNORED_REASONS = ["terminal-without-active-unit"];
|
|
23
|
+
//# sourceMappingURL=types.js.map
|