open-multi-agent-kit 0.79.3 → 0.80.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +38 -0
- package/MATURITY.md +2 -2
- package/README.md +86 -39
- package/ROADMAP.md +20 -17
- package/dist/brand/night-city.theme.json +122 -24
- package/dist/brand/rust-forge.theme.json +1 -1
- package/dist/commands/chat/native-root-loop.d.ts +27 -3
- package/dist/commands/chat/native-root-loop.js +370 -49
- package/dist/commands/dag.js +45 -0
- package/dist/commands/init/content.d.ts +1 -1
- package/dist/commands/init/content.js +3 -3
- package/dist/commands/init.js +3 -3
- package/dist/commands/verify.js +59 -1
- package/dist/contracts/dag.d.ts +9 -1
- package/dist/evidence/graph-ontology-relation-gate.d.ts +10 -0
- package/dist/evidence/graph-ontology-relation-gate.js +46 -0
- package/dist/evidence/headroom-replay-verifier.d.ts +19 -0
- package/dist/evidence/headroom-replay-verifier.js +86 -0
- package/dist/memory/local-graph-memory-store.d.ts +75 -0
- package/dist/memory/local-graph-memory-store.js +570 -0
- package/dist/orchestration/dag.d.ts +22 -1
- package/dist/orchestration/evidence-gate.js +12 -0
- package/dist/orchestration/executor.js +111 -15
- package/dist/orchestration/loop-controller-headroom.d.ts +6 -0
- package/dist/orchestration/loop-controller-headroom.js +33 -0
- package/dist/orchestration/scheduler.d.ts +2 -0
- package/dist/orchestration/scheduler.js +3 -0
- package/dist/orchestration/task-graph.d.ts +17 -0
- package/dist/orchestration/task-graph.js +38 -0
- package/dist/providers/codex-cli-runner.js +17 -2
- package/dist/runtime/agent-runtime.d.ts +32 -3
- package/dist/runtime/agent-runtime.js +20 -7
- package/dist/runtime/authority-matrix.d.ts +23 -0
- package/dist/runtime/authority-matrix.js +171 -0
- package/dist/runtime/chat-advisory-runtime.js +3 -0
- package/dist/runtime/codex-cli-runtime.js +2 -0
- package/dist/runtime/codex-runtime.d.ts +4 -1
- package/dist/runtime/codex-runtime.js +34 -12
- package/dist/runtime/context-broker-converter.js +32 -2
- package/dist/runtime/context-broker.js +18 -0
- package/dist/runtime/contracts/evidence.d.ts +52 -0
- package/dist/runtime/contracts/evidence.js +138 -1
- package/dist/runtime/contracts/shared.d.ts +27 -0
- package/dist/runtime/data-retention-gate.d.ts +42 -0
- package/dist/runtime/data-retention-gate.js +123 -0
- package/dist/runtime/deepseek-runtime.d.ts +5 -1
- package/dist/runtime/deepseek-runtime.js +13 -7
- package/dist/runtime/external-cli-adapter.js +20 -7
- package/dist/runtime/freedomd-evidence-envelope.d.ts +57 -0
- package/dist/runtime/freedomd-evidence-envelope.js +110 -0
- package/dist/runtime/freedomd-exception.d.ts +32 -0
- package/dist/runtime/freedomd-exception.js +95 -0
- package/dist/runtime/freedomd-incidents.d.ts +23 -0
- package/dist/runtime/freedomd-incidents.js +103 -0
- package/dist/runtime/freedomd-policy.d.ts +35 -0
- package/dist/runtime/freedomd-policy.js +114 -0
- package/dist/runtime/freedomd-router.d.ts +66 -0
- package/dist/runtime/freedomd-router.js +433 -0
- package/dist/runtime/headroom-aware-loop-decision.d.ts +21 -0
- package/dist/runtime/headroom-aware-loop-decision.js +67 -0
- package/dist/runtime/headroom-loop-risk.d.ts +19 -0
- package/dist/runtime/headroom-loop-risk.js +43 -0
- package/dist/runtime/headroom-policy.d.ts +14 -1
- package/dist/runtime/headroom-policy.js +74 -12
- package/dist/runtime/kimi-api-runtime.d.ts +4 -1
- package/dist/runtime/kimi-api-runtime.js +35 -11
- package/dist/runtime/kimi-print-runtime.js +21 -0
- package/dist/runtime/kimi-wire-protocol-runtime.js +17 -1
- package/dist/runtime/local-llm-runtime.d.ts +5 -1
- package/dist/runtime/local-llm-runtime.js +14 -23
- package/dist/runtime/private-stderr.d.ts +13 -0
- package/dist/runtime/private-stderr.js +69 -0
- package/dist/runtime/prompt-envelope.d.ts +1 -1
- package/dist/runtime/provider-sovereignty.d.ts +81 -0
- package/dist/runtime/provider-sovereignty.js +285 -0
- package/dist/runtime/runtime-backed-task-runner.d.ts +1 -0
- package/dist/runtime/runtime-backed-task-runner.js +282 -10
- package/dist/runtime/runtime-bootstrap.d.ts +12 -0
- package/dist/runtime/runtime-bootstrap.js +54 -11
- package/dist/runtime/runtime-failure-classifier.d.ts +15 -0
- package/dist/runtime/runtime-failure-classifier.js +107 -0
- package/dist/runtime/runtime-health-probes.d.ts +30 -0
- package/dist/runtime/runtime-health-probes.js +102 -0
- package/dist/runtime/runtime-router.d.ts +6 -2
- package/dist/runtime/runtime-router.js +575 -53
- package/dist/runtime/structured-compaction.d.ts +107 -0
- package/dist/runtime/structured-compaction.js +414 -0
- package/dist/runtime/tool-dispatch-contracts.d.ts +7 -5
- package/dist/runtime/tool-dispatch-contracts.js +15 -4
- package/dist/runtime/tool-plane.d.ts +1 -1
- package/dist/runtime/tool-plane.js +49 -1
- package/dist/runtime/worker-manifest.js +1 -1
- package/dist/schema/evidence-bundle.schema.d.ts +8 -8
- package/dist/schema/proof-bundle.schema.d.ts +2 -2
- package/dist/util/hash.d.ts +2 -0
- package/dist/util/hash.js +13 -0
- package/dist/util/session.js +5 -4
- package/docs/2026-06-16/critical-issues.md +19 -0
- package/docs/2026-06-16/improvements.md +15 -0
- package/docs/2026-06-16/init-checklist.md +25 -0
- package/docs/2026-06-16/plan.md +20 -0
- package/docs/2026-06-17/critical-issues.md +19 -0
- package/docs/2026-06-17/improvements.md +15 -0
- package/docs/2026-06-17/init-checklist.md +25 -0
- package/docs/2026-06-17/plan.md +20 -0
- package/docs/ARCHITECTURE_ANALYSIS_CODEGRAPH.md +2 -1
- package/docs/GSTACK_MIGRATION.md +1 -1
- package/docs/algorithm-hardening-playbook.md +219 -0
- package/docs/claims.md +1 -1
- package/docs/getting-started.md +1 -1
- package/docs/native-root-runtime-hardening.md +42 -32
- package/docs/post-0793-hardening-roadmap.md +123 -0
- package/docs/provider-maturity.md +18 -16
- package/docs/versioning.md +3 -3
- package/docs/what-is-omk.md +1 -1
- package/package.json +8 -4
- package/readmeasset/.npmignore +2 -0
- package/readmeasset/ASSET_INDEX.md +2 -0
- package/readmeasset/ASSET_PROVENANCE.md +40 -20
- package/readmeasset/omk-adaptorch-ouroboros-supermemory.svg +1 -1
- package/readmeasset/omk-control-surfaces.svg +1 -1
- package/readmeasset/omk-core-loop.svg +1 -1
- package/readmeasset/omk-freedomd-control-plane.svg +59 -0
- package/readmeasset/omk-freedomd-control-plane.webp +0 -0
- package/readmeasset/omk-init-control-loop.svg +1 -1
- package/readmeasset/omk-logo-mark.svg +1 -1
- package/readmeasset/omk-parallel-subagents.svg +1 -1
- package/readmeasset/omk-release-assertions.svg +1 -1
|
@@ -7,9 +7,19 @@
|
|
|
7
7
|
* 3. Historical evidence pass rates from graph-state memory
|
|
8
8
|
* 4. Fallback chain with runtime.supports() check
|
|
9
9
|
*/
|
|
10
|
+
import { createHash } from "crypto";
|
|
10
11
|
import { readFile } from "fs/promises";
|
|
11
12
|
import { join } from "path";
|
|
13
|
+
import { maskSensitiveText } from "../util/secret-mask.js";
|
|
12
14
|
import { createDecisionTraceStore } from "../evidence/decision-trace.js";
|
|
15
|
+
import { runtimeIsAdvisory, runtimeSatisfiesAuthority } from "./authority-matrix.js";
|
|
16
|
+
function isStrictGuardrailMode() {
|
|
17
|
+
const raw = process.env.OMK_STRICT_GUARDRAIL ?? "";
|
|
18
|
+
const normalized = raw.trim().toLowerCase();
|
|
19
|
+
return normalized === "1" || normalized === "true" || normalized === "on";
|
|
20
|
+
}
|
|
21
|
+
import { sanitizeRuntimeStderrResult } from "./private-stderr.js";
|
|
22
|
+
import { classifyRuntimeFailure } from "./runtime-failure-classifier.js";
|
|
13
23
|
class UnsupportedRuntimeError extends Error {
|
|
14
24
|
code = "RUNTIME_UNSUPPORTED_TASK";
|
|
15
25
|
nodeId;
|
|
@@ -43,6 +53,8 @@ export function createRuntimeRouter(options = {}) {
|
|
|
43
53
|
let runtimes = options.runtimes ?? [];
|
|
44
54
|
const memoryPath = options.memoryPath;
|
|
45
55
|
let evidenceCache;
|
|
56
|
+
const healthCache = new Map();
|
|
57
|
+
const circuitBreakers = new Map();
|
|
46
58
|
function classifyIntent(capsule) {
|
|
47
59
|
const text = `${capsule.nodeId} ${capsule.goal} ${capsule.task} ${capsule.system}`.toLowerCase();
|
|
48
60
|
const role = capsule.node?.role?.toLowerCase() ?? "";
|
|
@@ -72,6 +84,8 @@ export function createRuntimeRouter(options = {}) {
|
|
|
72
84
|
const raw = await readFile(filePath, "utf-8");
|
|
73
85
|
const data = JSON.parse(raw);
|
|
74
86
|
const nodes = (data.nodes ?? []);
|
|
87
|
+
const edges = (data.edges ?? []);
|
|
88
|
+
const nodeById = new Map(nodes.map((node) => [String(node.id ?? ""), node]));
|
|
75
89
|
const entries = [];
|
|
76
90
|
for (const n of nodes) {
|
|
77
91
|
if (n.type !== "Evidence")
|
|
@@ -80,7 +94,7 @@ export function createRuntimeRouter(options = {}) {
|
|
|
80
94
|
const kind = String(props.kind ?? "");
|
|
81
95
|
if (kind !== "failure_pattern" && kind !== "successful_fix")
|
|
82
96
|
continue;
|
|
83
|
-
entries
|
|
97
|
+
pushEvidenceHistory(entries, {
|
|
84
98
|
runtime: String(props.runtime ?? "unknown"),
|
|
85
99
|
intent: String(props.intent ?? "coding"),
|
|
86
100
|
passed: kind === "successful_fix",
|
|
@@ -88,6 +102,44 @@ export function createRuntimeRouter(options = {}) {
|
|
|
88
102
|
nodeId: String(props.sourceNodeId ?? ""),
|
|
89
103
|
});
|
|
90
104
|
}
|
|
105
|
+
// Audit graph v2 materializes ProviderRoute -> EVIDENCED_BY -> Evidence
|
|
106
|
+
// with turn-result-pass/fail evidence. Feed those observations back into
|
|
107
|
+
// routing scores so successful recent routes become preferred and failed
|
|
108
|
+
// runtime paths decay without relying on raw logs.
|
|
109
|
+
const evidenceEdgesByRoute = new Map();
|
|
110
|
+
for (const edge of edges) {
|
|
111
|
+
if (edge.type !== "EVIDENCED_BY")
|
|
112
|
+
continue;
|
|
113
|
+
const from = String(edge.from ?? "");
|
|
114
|
+
const to = String(edge.to ?? "");
|
|
115
|
+
const evidence = nodeById.get(to);
|
|
116
|
+
if (!evidence || evidence.type !== "Evidence")
|
|
117
|
+
continue;
|
|
118
|
+
const list = evidenceEdgesByRoute.get(from) ?? [];
|
|
119
|
+
list.push(evidence);
|
|
120
|
+
evidenceEdgesByRoute.set(from, list);
|
|
121
|
+
}
|
|
122
|
+
for (const route of nodes) {
|
|
123
|
+
if (route.type !== "ProviderRoute")
|
|
124
|
+
continue;
|
|
125
|
+
const props = (route.properties ?? {});
|
|
126
|
+
const runtime = routeRuntimeId(props);
|
|
127
|
+
if (!runtime)
|
|
128
|
+
continue;
|
|
129
|
+
for (const evidence of evidenceEdgesByRoute.get(String(route.id ?? "")) ?? []) {
|
|
130
|
+
const evidenceProps = (evidence.properties ?? {});
|
|
131
|
+
const pass = auditEvidencePassState(String(evidenceProps.kind ?? ""));
|
|
132
|
+
if (pass === undefined)
|
|
133
|
+
continue;
|
|
134
|
+
pushEvidenceHistory(entries, {
|
|
135
|
+
runtime,
|
|
136
|
+
intent: String(props.intent ?? props.role ?? "coding"),
|
|
137
|
+
passed: pass,
|
|
138
|
+
timestamp: String(evidence.updatedAt ?? evidence.createdAt ?? route.updatedAt ?? route.createdAt ?? ""),
|
|
139
|
+
nodeId: String(props.nodeId ?? evidenceProps.nodeId ?? ""),
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
}
|
|
91
143
|
evidenceCache = entries;
|
|
92
144
|
return entries;
|
|
93
145
|
}
|
|
@@ -95,7 +147,7 @@ export function createRuntimeRouter(options = {}) {
|
|
|
95
147
|
return [];
|
|
96
148
|
}
|
|
97
149
|
}
|
|
98
|
-
function computeScores(runtime, intent, history) {
|
|
150
|
+
function computeScores(runtime, intent, history, health) {
|
|
99
151
|
const runtimeHistory = history.filter((e) => e.runtime === runtime.id);
|
|
100
152
|
const intentHistory = runtimeHistory.filter((e) => e.intent === intent);
|
|
101
153
|
const totalAttempts = runtimeHistory.length;
|
|
@@ -114,24 +166,221 @@ export function createRuntimeRouter(options = {}) {
|
|
|
114
166
|
const latencyScore = runtime.capabilities?.supportsStreaming === true || runtime.capabilities?.streaming === true
|
|
115
167
|
? 0.7
|
|
116
168
|
: 0.6;
|
|
169
|
+
const healthScore = runtimeHealthScore(health);
|
|
117
170
|
return {
|
|
118
171
|
runtime: runtime.id,
|
|
119
172
|
qualityScore,
|
|
120
173
|
costScore,
|
|
121
174
|
latencyScore,
|
|
175
|
+
healthScore,
|
|
122
176
|
evidencePassRate,
|
|
123
177
|
recentFailurePenalty,
|
|
178
|
+
...(health && { healthAvailable: health.available }),
|
|
179
|
+
...(health?.reason && { healthReason: health.reason }),
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
async function collectRuntimeHealth(candidates, probeKind = "static", taskRisk) {
|
|
183
|
+
const nowMs = Date.now();
|
|
184
|
+
const entries = await Promise.all(candidates.map(async (runtime) => {
|
|
185
|
+
const cached = healthCache.get(runtime.id);
|
|
186
|
+
if (cached?.vector?.expiresAt && Date.parse(cached.vector.expiresAt) > nowMs && probeRank(cached.vector.lastProbeKind) >= probeRank(probeKind)) {
|
|
187
|
+
return [runtime.id, cached];
|
|
188
|
+
}
|
|
189
|
+
if (!runtime.health) {
|
|
190
|
+
const now = new Date();
|
|
191
|
+
const health = {
|
|
192
|
+
runtimeId: runtime.id,
|
|
193
|
+
available: true,
|
|
194
|
+
checkedAt: now.toISOString(),
|
|
195
|
+
vector: {
|
|
196
|
+
runtimeOk: true,
|
|
197
|
+
authOk: true,
|
|
198
|
+
modelOk: true,
|
|
199
|
+
quotaOk: true,
|
|
200
|
+
rateLimitOk: true,
|
|
201
|
+
runtime: "pass",
|
|
202
|
+
auth: "pass",
|
|
203
|
+
model: "pass",
|
|
204
|
+
quota: "unknown",
|
|
205
|
+
rateLimit: "unknown",
|
|
206
|
+
lastProbeKind: "none",
|
|
207
|
+
checkedAt: now.toISOString(),
|
|
208
|
+
expiresAt: new Date(now.getTime() + 30_000).toISOString(),
|
|
209
|
+
},
|
|
210
|
+
};
|
|
211
|
+
healthCache.set(runtime.id, health);
|
|
212
|
+
return [runtime.id, health];
|
|
213
|
+
}
|
|
214
|
+
try {
|
|
215
|
+
const health = normalizeRuntimeHealth(await runtime.health({ probeKind, taskRisk, highRisk: probeKind !== "static" }));
|
|
216
|
+
healthCache.set(runtime.id, health);
|
|
217
|
+
return [runtime.id, health];
|
|
218
|
+
}
|
|
219
|
+
catch (err) {
|
|
220
|
+
const now = new Date();
|
|
221
|
+
const reason = maskSensitiveText(err instanceof Error ? err.message : String(err));
|
|
222
|
+
const health = {
|
|
223
|
+
runtimeId: runtime.id,
|
|
224
|
+
available: false,
|
|
225
|
+
reason,
|
|
226
|
+
checkedAt: now.toISOString(),
|
|
227
|
+
vector: {
|
|
228
|
+
runtimeOk: false,
|
|
229
|
+
authOk: true,
|
|
230
|
+
modelOk: true,
|
|
231
|
+
quotaOk: true,
|
|
232
|
+
rateLimitOk: true,
|
|
233
|
+
runtime: "fail",
|
|
234
|
+
auth: "unknown",
|
|
235
|
+
model: "unknown",
|
|
236
|
+
quota: "unknown",
|
|
237
|
+
rateLimit: "unknown",
|
|
238
|
+
lastProbeKind: probeKind,
|
|
239
|
+
checkedAt: now.toISOString(),
|
|
240
|
+
expiresAt: new Date(now.getTime() + 30_000).toISOString(),
|
|
241
|
+
},
|
|
242
|
+
};
|
|
243
|
+
healthCache.set(runtime.id, health);
|
|
244
|
+
return [runtime.id, health];
|
|
245
|
+
}
|
|
246
|
+
}));
|
|
247
|
+
return new Map(entries);
|
|
248
|
+
}
|
|
249
|
+
function requiredProbeForTask(task) {
|
|
250
|
+
const risk = task.safety?.risk;
|
|
251
|
+
if (risk === "merge")
|
|
252
|
+
return "live-call";
|
|
253
|
+
if (risk === "write" || risk === "shell" || task.capabilities.write || task.capabilities.patch || task.capabilities.shell || task.capabilities.merge) {
|
|
254
|
+
return "cheap-call";
|
|
255
|
+
}
|
|
256
|
+
return "static";
|
|
257
|
+
}
|
|
258
|
+
function requiredProbeForCapsule(capsule) {
|
|
259
|
+
const risk = capsule.node.routing?.risk;
|
|
260
|
+
if (risk === "merge")
|
|
261
|
+
return "live-call";
|
|
262
|
+
if (risk === "write" || risk === "shell")
|
|
263
|
+
return "cheap-call";
|
|
264
|
+
return "static";
|
|
265
|
+
}
|
|
266
|
+
function runtimeHealthy(runtime, healthMap, probeKind = "static") {
|
|
267
|
+
const health = healthMap.get(runtime.id);
|
|
268
|
+
if (!health)
|
|
269
|
+
return true;
|
|
270
|
+
if (health.available === false)
|
|
271
|
+
return false;
|
|
272
|
+
const vector = health.vector;
|
|
273
|
+
if (!vector)
|
|
274
|
+
return true;
|
|
275
|
+
const highRiskProbe = probeRank(probeKind) >= probeRank("cheap-call");
|
|
276
|
+
const hardDimensionsOk = highRiskProbe
|
|
277
|
+
? statePassStrict(vector.runtime, vector.runtimeOk)
|
|
278
|
+
&& statePassStrict(vector.auth, vector.authOk)
|
|
279
|
+
&& statePassStrict(vector.model, vector.modelOk)
|
|
280
|
+
: statePassOrUnknown(vector.runtime, vector.runtimeOk)
|
|
281
|
+
&& statePassOrUnknown(vector.auth, vector.authOk)
|
|
282
|
+
&& statePassOrUnknown(vector.model, vector.modelOk);
|
|
283
|
+
return hardDimensionsOk
|
|
284
|
+
&& statePassOrUnknown(vector.quota, vector.quotaOk)
|
|
285
|
+
&& statePassOrUnknown(vector.rateLimit, vector.rateLimitOk);
|
|
286
|
+
}
|
|
287
|
+
function runtimeCircuitOpen(runtime) {
|
|
288
|
+
const state = circuitBreakers.get(runtime.id);
|
|
289
|
+
if (!state)
|
|
290
|
+
return undefined;
|
|
291
|
+
if (state.expiresAt <= Date.now()) {
|
|
292
|
+
circuitBreakers.delete(runtime.id);
|
|
293
|
+
return undefined;
|
|
294
|
+
}
|
|
295
|
+
return state;
|
|
296
|
+
}
|
|
297
|
+
function clearRuntimeCircuit(runtime) {
|
|
298
|
+
circuitBreakers.delete(runtime.id);
|
|
299
|
+
}
|
|
300
|
+
function recordRuntimeFailure(runtime, result) {
|
|
301
|
+
const classification = classifyRuntimeFailure({
|
|
302
|
+
exitCode: result.exitCode,
|
|
303
|
+
stdout: result.stdout,
|
|
304
|
+
stderr: result.stderr,
|
|
305
|
+
metadata: result.metadata,
|
|
306
|
+
});
|
|
307
|
+
if (classification.failureClass === "none")
|
|
308
|
+
return result;
|
|
309
|
+
const opened = maybeOpenRuntimeCircuit(runtime.id, classification);
|
|
310
|
+
return {
|
|
311
|
+
...result,
|
|
312
|
+
metadata: {
|
|
313
|
+
...(result.metadata ?? {}),
|
|
314
|
+
failureClass: classification.failureClass,
|
|
315
|
+
failureRetryable: classification.retryable,
|
|
316
|
+
failureReason: classification.reason,
|
|
317
|
+
...(opened && {
|
|
318
|
+
circuitBreaker: {
|
|
319
|
+
runtimeId: opened.runtimeId,
|
|
320
|
+
failureClass: opened.failureClass,
|
|
321
|
+
retryable: opened.retryable,
|
|
322
|
+
failureCount: opened.failureCount,
|
|
323
|
+
openedAt: new Date(opened.openedAt).toISOString(),
|
|
324
|
+
expiresAt: new Date(opened.expiresAt).toISOString(),
|
|
325
|
+
reason: opened.reason,
|
|
326
|
+
},
|
|
327
|
+
}),
|
|
328
|
+
},
|
|
124
329
|
};
|
|
125
330
|
}
|
|
126
|
-
function
|
|
331
|
+
function maybeOpenRuntimeCircuit(runtimeId, classification) {
|
|
332
|
+
if (!classification.circuitBreaker || classification.cooldownMs <= 0)
|
|
333
|
+
return undefined;
|
|
334
|
+
const now = Date.now();
|
|
335
|
+
const previous = circuitBreakers.get(runtimeId);
|
|
336
|
+
const state = {
|
|
337
|
+
runtimeId,
|
|
338
|
+
failureClass: classification.failureClass,
|
|
339
|
+
retryable: classification.retryable,
|
|
340
|
+
openedAt: now,
|
|
341
|
+
expiresAt: now + classification.cooldownMs,
|
|
342
|
+
failureCount: (previous?.failureCount ?? 0) + 1,
|
|
343
|
+
reason: classification.reason,
|
|
344
|
+
};
|
|
345
|
+
circuitBreakers.set(runtimeId, state);
|
|
346
|
+
return state;
|
|
347
|
+
}
|
|
348
|
+
function circuitOpenResult(runtime, state, fallbackChain, intent) {
|
|
349
|
+
return {
|
|
350
|
+
success: false,
|
|
351
|
+
exitCode: 78,
|
|
352
|
+
stdout: "",
|
|
353
|
+
stderr: `Runtime ${runtime.id} skipped by circuit breaker: ${state.failureClass}`,
|
|
354
|
+
metadata: {
|
|
355
|
+
runtime: runtime.id,
|
|
356
|
+
selectedRuntime: runtime.id,
|
|
357
|
+
intent,
|
|
358
|
+
fallbackChain: fallbackChain.map((candidate) => candidate.id),
|
|
359
|
+
failureClass: state.failureClass,
|
|
360
|
+
failureRetryable: state.retryable,
|
|
361
|
+
circuitBreakerOpen: true,
|
|
362
|
+
circuitBreaker: {
|
|
363
|
+
runtimeId: state.runtimeId,
|
|
364
|
+
failureClass: state.failureClass,
|
|
365
|
+
retryable: state.retryable,
|
|
366
|
+
failureCount: state.failureCount,
|
|
367
|
+
openedAt: new Date(state.openedAt).toISOString(),
|
|
368
|
+
expiresAt: new Date(state.expiresAt).toISOString(),
|
|
369
|
+
reason: state.reason,
|
|
370
|
+
},
|
|
371
|
+
},
|
|
372
|
+
};
|
|
373
|
+
}
|
|
374
|
+
function selectByIntent(capsule, history, healthMap, probeKind = "static") {
|
|
127
375
|
const intent = classifyIntent(capsule);
|
|
128
376
|
const sorted = [...runtimes].sort((a, b) => b.priority - a.priority);
|
|
129
377
|
const supporting = sorted.filter((runtime) => runtime.supports(capsule) &&
|
|
130
|
-
runtimeAllowedByLegacyPolicy(runtime, { preferredProviders: [], fallbackChain: options.fallbackChain })
|
|
378
|
+
runtimeAllowedByLegacyPolicy(runtime, { preferredProviders: [], fallbackChain: options.fallbackChain }) &&
|
|
379
|
+
(healthMap ? runtimeHealthy(runtime, healthMap, probeKind) : true));
|
|
131
380
|
if (supporting.length === 0) {
|
|
132
381
|
throw new UnsupportedRuntimeError(capsule, detectedRuntimeLabels(sorted));
|
|
133
382
|
}
|
|
134
|
-
const scores = supporting.map((r) => computeScores(r, intent, history));
|
|
383
|
+
const scores = supporting.map((r) => computeScores(r, intent, history, healthMap?.get(r.id)));
|
|
135
384
|
const scored = supporting.map((r, i) => ({
|
|
136
385
|
runtime: r,
|
|
137
386
|
score: scores[i],
|
|
@@ -146,6 +395,7 @@ export function createRuntimeRouter(options = {}) {
|
|
|
146
395
|
`intent=${intent}`,
|
|
147
396
|
`quality=${bestScore.qualityScore.toFixed(2)}`,
|
|
148
397
|
`evidencePassRate=${bestScore.evidencePassRate.toFixed(2)}`,
|
|
398
|
+
`health=${bestScore.healthScore.toFixed(2)}`,
|
|
149
399
|
`recentPenalty=${bestScore.recentFailurePenalty.toFixed(2)}`,
|
|
150
400
|
].join("; ");
|
|
151
401
|
return { runtime: primary, reason, fallbacks, intent, scores };
|
|
@@ -178,9 +428,11 @@ export function createRuntimeRouter(options = {}) {
|
|
|
178
428
|
}
|
|
179
429
|
async function runNode(capsule, signal) {
|
|
180
430
|
const history = await loadEvidenceHistory();
|
|
431
|
+
const probeKind = requiredProbeForCapsule(capsule);
|
|
432
|
+
const healthMap = await collectRuntimeHealth(runtimes, probeKind, capsule.node.routing?.risk);
|
|
181
433
|
let decision;
|
|
182
434
|
try {
|
|
183
|
-
decision = selectByIntent(capsule, history);
|
|
435
|
+
decision = selectByIntent(capsule, history, healthMap, probeKind);
|
|
184
436
|
}
|
|
185
437
|
catch (err) {
|
|
186
438
|
if (err instanceof UnsupportedRuntimeError) {
|
|
@@ -219,30 +471,38 @@ export function createRuntimeRouter(options = {}) {
|
|
|
219
471
|
metadata: { runtime: runtime.id, aborted: true },
|
|
220
472
|
};
|
|
221
473
|
}
|
|
474
|
+
const openCircuit = runtimeCircuitOpen(runtime);
|
|
475
|
+
if (openCircuit) {
|
|
476
|
+
lastError = circuitOpenResult(runtime, openCircuit, allCandidates, decision.intent);
|
|
477
|
+
continue;
|
|
478
|
+
}
|
|
222
479
|
try {
|
|
223
480
|
const result = await runtime.runNode(capsule, signal);
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
metadata
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
481
|
+
const routedResult = sanitizeAgentRunResult({
|
|
482
|
+
...result,
|
|
483
|
+
metadata: {
|
|
484
|
+
...result.metadata,
|
|
485
|
+
selectedRuntime: runtime.id,
|
|
486
|
+
intent: decision.intent,
|
|
487
|
+
fallbackChain: allCandidates.map((r) => r.id),
|
|
488
|
+
scores: decision.scores,
|
|
489
|
+
},
|
|
490
|
+
}, stderrArtifactOptions({ runId: capsule.runId, nodeId: capsule.nodeId, runtimeId: runtime.id, root: process.cwd(), env: process.env }));
|
|
491
|
+
if (routedResult.success) {
|
|
492
|
+
clearRuntimeCircuit(runtime);
|
|
493
|
+
return routedResult;
|
|
235
494
|
}
|
|
236
|
-
lastError =
|
|
495
|
+
lastError = recordRuntimeFailure(runtime, routedResult);
|
|
237
496
|
}
|
|
238
497
|
catch (err) {
|
|
239
|
-
|
|
498
|
+
const error = maskSensitiveText(String(err));
|
|
499
|
+
lastError = recordRuntimeFailure(runtime, sanitizeAgentRunResult({
|
|
240
500
|
success: false,
|
|
241
501
|
exitCode: 1,
|
|
242
502
|
stdout: "",
|
|
243
|
-
stderr:
|
|
244
|
-
metadata: { runtime: runtime.id, error
|
|
245
|
-
};
|
|
503
|
+
stderr: error,
|
|
504
|
+
metadata: { runtime: runtime.id, error },
|
|
505
|
+
}, stderrArtifactOptions({ runId: capsule.runId, nodeId: capsule.nodeId, runtimeId: runtime.id, root: process.cwd(), env: process.env })));
|
|
246
506
|
}
|
|
247
507
|
}
|
|
248
508
|
return (lastError ?? {
|
|
@@ -255,9 +515,11 @@ export function createRuntimeRouter(options = {}) {
|
|
|
255
515
|
}
|
|
256
516
|
async function executeTask(task, capsule, signal) {
|
|
257
517
|
const history = await loadEvidenceHistory();
|
|
518
|
+
const probeKind = requiredProbeForTask(task);
|
|
519
|
+
const healthMap = await collectRuntimeHealth(runtimes, probeKind, task.safety?.risk);
|
|
258
520
|
let decision;
|
|
259
521
|
try {
|
|
260
|
-
decision = selectByIntent(capsule, history);
|
|
522
|
+
decision = selectByIntent(capsule, history, healthMap, probeKind);
|
|
261
523
|
}
|
|
262
524
|
catch (err) {
|
|
263
525
|
if (err instanceof UnsupportedRuntimeError) {
|
|
@@ -266,8 +528,39 @@ export function createRuntimeRouter(options = {}) {
|
|
|
266
528
|
throw err;
|
|
267
529
|
}
|
|
268
530
|
const allCandidates = [decision.runtime, ...decision.fallbacks];
|
|
531
|
+
// Record runtime-router decision trace for executeTask, the native runtime pipeline path.
|
|
532
|
+
const runId = capsule.runId;
|
|
533
|
+
const attemptNumber = (capsule.node?.attempts?.length ?? 0) + 1;
|
|
534
|
+
const attemptId = `${capsule.nodeId}__${attemptNumber}`;
|
|
535
|
+
if (runId && !runId.startsWith("local-")) {
|
|
536
|
+
const traceStore = createDecisionTraceStore();
|
|
537
|
+
traceStore.record(runId, {
|
|
538
|
+
component: "runtime-router",
|
|
539
|
+
inputSummary: `node=${capsule.nodeId} intent=${decision.intent} path=executeTask`,
|
|
540
|
+
outputDecision: `runtime=${decision.runtime.id} fallbacks=${decision.fallbacks.map((r) => r.id).join(",")}`,
|
|
541
|
+
reason: decision.reason,
|
|
542
|
+
scores: decision.scores.reduce((acc, s) => {
|
|
543
|
+
acc[s.runtime] = computeComposite(s, allCandidates.find((candidate) => candidate.id === s.runtime) ?? decision.runtime, decision.intent);
|
|
544
|
+
return acc;
|
|
545
|
+
}, {}),
|
|
546
|
+
nodeId: capsule.nodeId,
|
|
547
|
+
attemptId,
|
|
548
|
+
});
|
|
549
|
+
}
|
|
550
|
+
const executionCandidates = allCandidates.filter((runtime) => {
|
|
551
|
+
const boundary = runtimeSatisfiesAdvisoryBoundary(runtime, task);
|
|
552
|
+
if (!boundary.ok) {
|
|
553
|
+
if (decision.reason && !decision.reason.includes("advisory boundary")) {
|
|
554
|
+
decision = {
|
|
555
|
+
...decision,
|
|
556
|
+
reason: `${decision.reason}; advisory boundary excluded ${runtime.id}`,
|
|
557
|
+
};
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
return boundary.ok;
|
|
561
|
+
});
|
|
269
562
|
let lastError;
|
|
270
|
-
for (const runtime of
|
|
563
|
+
for (const runtime of executionCandidates) {
|
|
271
564
|
if (signal.aborted) {
|
|
272
565
|
return {
|
|
273
566
|
success: false,
|
|
@@ -277,34 +570,54 @@ export function createRuntimeRouter(options = {}) {
|
|
|
277
570
|
metadata: { runtime: runtime.id, selectedRuntime: runtime.id, aborted: true },
|
|
278
571
|
};
|
|
279
572
|
}
|
|
573
|
+
const openCircuit = runtimeCircuitOpen(runtime);
|
|
574
|
+
if (openCircuit) {
|
|
575
|
+
lastError = circuitOpenResult(runtime, openCircuit, executionCandidates, decision.intent);
|
|
576
|
+
continue;
|
|
577
|
+
}
|
|
280
578
|
try {
|
|
281
579
|
const result = runtime.execute
|
|
282
580
|
? agentResultToRunResult(await runtime.execute(task), runtime.id)
|
|
283
581
|
: await runtime.runNode(capsule, signal);
|
|
284
|
-
const routedResult = {
|
|
582
|
+
const routedResult = sanitizeAgentRunResult({
|
|
285
583
|
...result,
|
|
286
584
|
metadata: {
|
|
287
585
|
...result.metadata,
|
|
288
586
|
selectedRuntime: runtime.id,
|
|
289
587
|
intent: decision.intent,
|
|
290
|
-
fallbackChain:
|
|
588
|
+
fallbackChain: executionCandidates.map((r) => r.id),
|
|
291
589
|
scores: decision.scores,
|
|
292
590
|
},
|
|
293
|
-
};
|
|
294
|
-
if (routedResult.success)
|
|
591
|
+
}, stderrArtifactOptions({ runId: task.context.runId, nodeId: task.context.nodeId, runtimeId: runtime.id, root: task.context.cwd, env: task.context.env ?? process.env }));
|
|
592
|
+
if (routedResult.success) {
|
|
593
|
+
clearRuntimeCircuit(runtime);
|
|
295
594
|
return routedResult;
|
|
296
|
-
|
|
595
|
+
}
|
|
596
|
+
lastError = recordRuntimeFailure(runtime, routedResult);
|
|
297
597
|
}
|
|
298
598
|
catch (err) {
|
|
299
|
-
|
|
599
|
+
const error = maskSensitiveText(String(err));
|
|
600
|
+
lastError = recordRuntimeFailure(runtime, sanitizeAgentRunResult({
|
|
300
601
|
success: false,
|
|
301
602
|
exitCode: 1,
|
|
302
603
|
stdout: "",
|
|
303
|
-
stderr:
|
|
304
|
-
metadata: { runtime: runtime.id, selectedRuntime: runtime.id, error
|
|
305
|
-
};
|
|
604
|
+
stderr: error,
|
|
605
|
+
metadata: { runtime: runtime.id, selectedRuntime: runtime.id, error },
|
|
606
|
+
}, stderrArtifactOptions({ runId: task.context.runId, nodeId: task.context.nodeId, runtimeId: runtime.id, root: task.context.cwd, env: task.context.env ?? process.env })));
|
|
306
607
|
}
|
|
307
608
|
}
|
|
609
|
+
if (executionCandidates.length === 0 && allCandidates.length > 0) {
|
|
610
|
+
const advisoryFailures = allCandidates
|
|
611
|
+
.map((runtime) => runtimeSatisfiesAdvisoryBoundary(runtime, task))
|
|
612
|
+
.filter((boundary) => !boundary.ok);
|
|
613
|
+
return {
|
|
614
|
+
success: false,
|
|
615
|
+
exitCode: 78,
|
|
616
|
+
stdout: "",
|
|
617
|
+
stderr: advisoryFailures.map((boundary) => boundary.reason).join("\n") || "All candidate runtimes are advisory-only for this task",
|
|
618
|
+
metadata: { authorityMode: "advisory", advisoryBoundaryFailures: advisoryFailures.map((boundary) => boundary.reason) },
|
|
619
|
+
};
|
|
620
|
+
}
|
|
308
621
|
return (lastError ?? {
|
|
309
622
|
success: false,
|
|
310
623
|
exitCode: 1,
|
|
@@ -332,15 +645,48 @@ export function createRuntimeRouter(options = {}) {
|
|
|
332
645
|
if (preferredProviders.length > 0) {
|
|
333
646
|
candidates = candidates.filter((runtime) => preferredProviders.some((provider) => runtimeMatchesProvider(runtime, provider)));
|
|
334
647
|
}
|
|
648
|
+
const probeKind = requiredProbeForTask(task);
|
|
649
|
+
const healthMap = await collectRuntimeHealth(candidates, probeKind, task.safety?.risk);
|
|
650
|
+
candidates = candidates.filter((runtime) => runtimeHealthy(runtime, healthMap, probeKind));
|
|
651
|
+
const advisoryBoundaryFailures = [];
|
|
652
|
+
const nonAdvisoryCandidates = candidates.filter((runtime) => {
|
|
653
|
+
const boundary = runtimeSatisfiesAdvisoryBoundary(runtime, task);
|
|
654
|
+
if (!boundary.ok)
|
|
655
|
+
advisoryBoundaryFailures.push(`${runtime.id}: ${boundary.reason}`);
|
|
656
|
+
return boundary.ok;
|
|
657
|
+
});
|
|
658
|
+
if (nonAdvisoryCandidates.length > 0) {
|
|
659
|
+
candidates = nonAdvisoryCandidates;
|
|
660
|
+
}
|
|
661
|
+
else if (advisoryBoundaryFailures.length > 0) {
|
|
662
|
+
return {
|
|
663
|
+
success: false,
|
|
664
|
+
exitCode: 78,
|
|
665
|
+
stdout: "",
|
|
666
|
+
stderr: `Advisory runtime boundary blocked execution:\n${advisoryBoundaryFailures.join("\n")}`,
|
|
667
|
+
metadata: { authorityMode: "advisory", advisoryBoundaryFailures },
|
|
668
|
+
};
|
|
669
|
+
}
|
|
335
670
|
if (candidates.length === 0) {
|
|
336
671
|
const mcpBlocked = task.capabilities.mcp || task.capabilities.toolCalling;
|
|
337
672
|
if (mcpBlocked && runtimes.length > 0) {
|
|
338
673
|
const runtime = runtimes[0];
|
|
339
674
|
throw new Error(`Node requires MCP authority. ${runtimeDisplayName(runtime)} runtime does not receive OMK MCP authority.`);
|
|
340
675
|
}
|
|
676
|
+
if (advisoryBoundaryFailures.length > 0) {
|
|
677
|
+
return {
|
|
678
|
+
success: false,
|
|
679
|
+
exitCode: 78,
|
|
680
|
+
stdout: "",
|
|
681
|
+
stderr: `Advisory runtime boundary blocked execution:\n${advisoryBoundaryFailures.join("\n")}`,
|
|
682
|
+
metadata: { authorityMode: "advisory", advisoryBoundaryFailures },
|
|
683
|
+
};
|
|
684
|
+
}
|
|
341
685
|
throw new UnsupportedRuntimeError(capsule, detectedRuntimeLabels(runtimes));
|
|
342
686
|
}
|
|
343
687
|
const preferredRuntimeIds = task.providerPolicy?.fallbackChain ?? options.fallbackChain ?? [];
|
|
688
|
+
const history = await loadEvidenceHistory();
|
|
689
|
+
const scoreByRuntime = new Map(candidates.map((runtime) => [runtime, computeScores(runtime, intent, history, healthMap.get(runtime.id))]));
|
|
344
690
|
const capabilityScoreCache = buildCapabilityScoreCache(candidates, intent);
|
|
345
691
|
candidates.sort((a, b) => {
|
|
346
692
|
const runtimeDelta = runtimePreferenceIndex(a.id, preferredRuntimeIds)
|
|
@@ -353,7 +699,9 @@ export function createRuntimeRouter(options = {}) {
|
|
|
353
699
|
if (providerDelta !== 0)
|
|
354
700
|
return providerDelta;
|
|
355
701
|
}
|
|
356
|
-
|
|
702
|
+
const aScore = scoreByRuntime.get(a) ?? computeScores(a, intent, history, healthMap.get(a.id));
|
|
703
|
+
const bScore = scoreByRuntime.get(b) ?? computeScores(b, intent, history, healthMap.get(b.id));
|
|
704
|
+
return compareScoredRuntimes({ runtime: a, composite: computeComposite(aScore, a, intent) }, { runtime: b, composite: computeComposite(bScore, b, intent) }, intent, capabilityScoreCache);
|
|
357
705
|
});
|
|
358
706
|
let lastError;
|
|
359
707
|
for (const runtime of candidates) {
|
|
@@ -366,31 +714,40 @@ export function createRuntimeRouter(options = {}) {
|
|
|
366
714
|
metadata: { runtime: runtime.id, selectedRuntime: runtime.id, aborted: true },
|
|
367
715
|
};
|
|
368
716
|
}
|
|
717
|
+
const openCircuit = runtimeCircuitOpen(runtime);
|
|
718
|
+
if (openCircuit) {
|
|
719
|
+
lastError = circuitOpenResult(runtime, openCircuit, candidates, intent);
|
|
720
|
+
continue;
|
|
721
|
+
}
|
|
369
722
|
try {
|
|
370
723
|
const result = runtime.execute
|
|
371
724
|
? agentResultToRunResult(await runtime.execute(task), runtime.id)
|
|
372
725
|
: await runtime.runNode(capsule, signal);
|
|
373
|
-
const routedResult = {
|
|
726
|
+
const routedResult = sanitizeAgentRunResult({
|
|
374
727
|
...result,
|
|
375
728
|
metadata: {
|
|
376
729
|
...result.metadata,
|
|
377
730
|
selectedRuntime: runtime.id,
|
|
378
731
|
intent,
|
|
379
732
|
fallbackChain: candidates.map((candidate) => candidate.id),
|
|
733
|
+
scores: candidates.map((candidate) => scoreByRuntime.get(candidate)).filter((score) => score !== undefined),
|
|
380
734
|
},
|
|
381
|
-
};
|
|
382
|
-
if (routedResult.success)
|
|
735
|
+
}, stderrArtifactOptions({ runId: task.context.runId, nodeId: task.context.nodeId, runtimeId: runtime.id, root: task.context.cwd, env: task.context.env ?? process.env }));
|
|
736
|
+
if (routedResult.success) {
|
|
737
|
+
clearRuntimeCircuit(runtime);
|
|
383
738
|
return routedResult;
|
|
384
|
-
|
|
739
|
+
}
|
|
740
|
+
lastError = recordRuntimeFailure(runtime, routedResult);
|
|
385
741
|
}
|
|
386
742
|
catch (err) {
|
|
387
|
-
|
|
743
|
+
const error = maskSensitiveText(String(err));
|
|
744
|
+
lastError = recordRuntimeFailure(runtime, sanitizeAgentRunResult({
|
|
388
745
|
success: false,
|
|
389
746
|
exitCode: 1,
|
|
390
747
|
stdout: "",
|
|
391
|
-
stderr:
|
|
392
|
-
metadata: { runtime: runtime.id, selectedRuntime: runtime.id, error
|
|
393
|
-
};
|
|
748
|
+
stderr: error,
|
|
749
|
+
metadata: { runtime: runtime.id, selectedRuntime: runtime.id, error },
|
|
750
|
+
}, stderrArtifactOptions({ runId: task.context.runId, nodeId: task.context.nodeId, runtimeId: runtime.id, root: task.context.cwd, env: task.context.env ?? process.env })));
|
|
394
751
|
}
|
|
395
752
|
}
|
|
396
753
|
return lastError ?? {
|
|
@@ -423,20 +780,30 @@ export function createRuntimeRouter(options = {}) {
|
|
|
423
780
|
}
|
|
424
781
|
function capsuleFromTask(task) {
|
|
425
782
|
const nodeId = task.context.nodeId || "runtime-task";
|
|
783
|
+
const promptHash = createHash("sha256").update(task.prompt).digest("hex");
|
|
784
|
+
const publicLabel = `runtime task:${promptHash.slice(0, 12)}`;
|
|
426
785
|
return {
|
|
427
786
|
runId: task.context.runId || "local-runtime-router",
|
|
428
787
|
nodeId,
|
|
429
|
-
goal: task.context.goal ||
|
|
788
|
+
goal: task.context.goal || publicLabel,
|
|
430
789
|
task: task.prompt,
|
|
431
790
|
system: task.context.system || "",
|
|
432
791
|
node: {
|
|
433
792
|
id: nodeId,
|
|
434
|
-
name:
|
|
793
|
+
name: publicLabel,
|
|
435
794
|
role: task.context.role || "coder",
|
|
436
795
|
dependsOn: [],
|
|
437
796
|
status: "running",
|
|
438
797
|
retries: 0,
|
|
439
798
|
maxRetries: 1,
|
|
799
|
+
routing: {
|
|
800
|
+
promptHash,
|
|
801
|
+
promptMode: "synthetic-private",
|
|
802
|
+
risk: task.safety?.risk,
|
|
803
|
+
approvalPolicy: task.safety?.approvalPolicy,
|
|
804
|
+
sandboxMode: task.safety?.sandboxMode,
|
|
805
|
+
evidenceRequired: task.safety?.evidenceRequired,
|
|
806
|
+
},
|
|
440
807
|
},
|
|
441
808
|
dependencySummaries: [],
|
|
442
809
|
evidenceRequirements: [],
|
|
@@ -446,6 +813,25 @@ function capsuleFromTask(task) {
|
|
|
446
813
|
budget: { maxInputTokens: 0, compression: "small" },
|
|
447
814
|
};
|
|
448
815
|
}
|
|
816
|
+
function pushEvidenceHistory(entries, entry) {
|
|
817
|
+
if (!entry.runtime || entry.runtime === "unknown")
|
|
818
|
+
return;
|
|
819
|
+
entries.push(entry);
|
|
820
|
+
}
|
|
821
|
+
function routeRuntimeId(props) {
|
|
822
|
+
const selectedRuntime = String(props.selectedRuntime ?? "").trim();
|
|
823
|
+
if (selectedRuntime && selectedRuntime !== "unknown")
|
|
824
|
+
return selectedRuntime;
|
|
825
|
+
const provider = String(props.provider ?? "").trim();
|
|
826
|
+
return provider && provider !== "unknown" ? provider : undefined;
|
|
827
|
+
}
|
|
828
|
+
function auditEvidencePassState(kind) {
|
|
829
|
+
if (kind === "turn-result-pass" || kind === "command-pass" || kind === "test-pass")
|
|
830
|
+
return true;
|
|
831
|
+
if (kind === "turn-result-fail" || kind === "command-fail" || kind === "test-fail")
|
|
832
|
+
return false;
|
|
833
|
+
return undefined;
|
|
834
|
+
}
|
|
449
835
|
function detectedRuntimeLabels(runtimes) {
|
|
450
836
|
return [...new Set(runtimes.map((runtime) => isLegacyRuntime(runtime) ? "legacy-external-runtime" : runtime.id))];
|
|
451
837
|
}
|
|
@@ -472,6 +858,37 @@ function runtimeSatisfiesTask(runtime, task) {
|
|
|
472
858
|
return false;
|
|
473
859
|
return true;
|
|
474
860
|
}
|
|
861
|
+
/**
|
|
862
|
+
* Detect advisory-only runtimes that claim broad capabilities but are not allowed
|
|
863
|
+
* to execute write/shell/merge/patch/tool-calling authority. If a non-read-only
|
|
864
|
+
* task reaches such a runtime, reroute it to the first non-advisory fallback.
|
|
865
|
+
*/
|
|
866
|
+
function runtimeSatisfiesAdvisoryBoundary(runtime, task) {
|
|
867
|
+
const authority = runtimeSatisfiesAuthority(runtime, task);
|
|
868
|
+
if (!authority.ok) {
|
|
869
|
+
return { ok: false, reason: authority.reason };
|
|
870
|
+
}
|
|
871
|
+
if (!runtimeIsAdvisory(runtime))
|
|
872
|
+
return { ok: true };
|
|
873
|
+
const required = task.capabilities;
|
|
874
|
+
if (required.write ||
|
|
875
|
+
required.patch ||
|
|
876
|
+
required.shell ||
|
|
877
|
+
required.merge ||
|
|
878
|
+
required.mcp) {
|
|
879
|
+
return {
|
|
880
|
+
ok: false,
|
|
881
|
+
reason: `advisory runtime ${runtime.id} cannot execute write/shell/merge/patch/MCP authority`,
|
|
882
|
+
};
|
|
883
|
+
}
|
|
884
|
+
if (required.toolCalling && isStrictGuardrailMode()) {
|
|
885
|
+
return {
|
|
886
|
+
ok: false,
|
|
887
|
+
reason: `advisory runtime ${runtime.id} cannot execute tool-calling authority unless agent-freedom mode is enabled`,
|
|
888
|
+
};
|
|
889
|
+
}
|
|
890
|
+
return { ok: true };
|
|
891
|
+
}
|
|
475
892
|
function runtimeMatchesProvider(runtime, provider) {
|
|
476
893
|
const normalizedProvider = normalizeRuntimeToken(provider);
|
|
477
894
|
if (normalizedProvider.length === 0)
|
|
@@ -556,7 +973,7 @@ function unsupportedRuntimeResult(err) {
|
|
|
556
973
|
}
|
|
557
974
|
function agentResultToRunResult(result, runtimeId) {
|
|
558
975
|
const success = result.exitCode === 0;
|
|
559
|
-
return {
|
|
976
|
+
return sanitizeAgentRunResult({
|
|
560
977
|
success,
|
|
561
978
|
exitCode: result.exitCode,
|
|
562
979
|
stdout: result.output,
|
|
@@ -568,16 +985,121 @@ function agentResultToRunResult(result, runtimeId) {
|
|
|
568
985
|
},
|
|
569
986
|
tokenUsage: result.tokenUsage,
|
|
570
987
|
toolCalls: result.toolCalls,
|
|
571
|
-
};
|
|
988
|
+
});
|
|
989
|
+
}
|
|
990
|
+
function stderrArtifactOptions(input) {
|
|
991
|
+
return input;
|
|
992
|
+
}
|
|
993
|
+
function sanitizeAgentRunResult(result, options = {}) {
|
|
994
|
+
return sanitizeRuntimeStderrResult(result, options);
|
|
572
995
|
}
|
|
996
|
+
const NORMALIZED_COMPOSITE_WEIGHTS = {
|
|
997
|
+
quality: 0.28,
|
|
998
|
+
evidence: 0.18,
|
|
999
|
+
health: 0.16,
|
|
1000
|
+
cost: 0.10,
|
|
1001
|
+
latency: 0.08,
|
|
1002
|
+
recentSuccess: 0.10,
|
|
1003
|
+
capability: 0.07,
|
|
1004
|
+
priority: 0.03,
|
|
1005
|
+
};
|
|
573
1006
|
function computeComposite(score, runtime, intent) {
|
|
574
|
-
return (
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
1007
|
+
return (NORMALIZED_COMPOSITE_WEIGHTS.quality * score.qualityScore +
|
|
1008
|
+
NORMALIZED_COMPOSITE_WEIGHTS.evidence * score.evidencePassRate +
|
|
1009
|
+
NORMALIZED_COMPOSITE_WEIGHTS.health * score.healthScore +
|
|
1010
|
+
NORMALIZED_COMPOSITE_WEIGHTS.cost * score.costScore +
|
|
1011
|
+
NORMALIZED_COMPOSITE_WEIGHTS.latency * score.latencyScore +
|
|
1012
|
+
NORMALIZED_COMPOSITE_WEIGHTS.recentSuccess * (1 - score.recentFailurePenalty) +
|
|
1013
|
+
NORMALIZED_COMPOSITE_WEIGHTS.capability * computeRuntimeCapabilityScore(runtime, intent) +
|
|
1014
|
+
NORMALIZED_COMPOSITE_WEIGHTS.priority * runtimePriorityScore(runtime));
|
|
1015
|
+
}
|
|
1016
|
+
function normalizeRuntimeHealth(health) {
|
|
1017
|
+
const now = new Date();
|
|
1018
|
+
const checkedAt = health.checkedAt ?? now.toISOString();
|
|
1019
|
+
const vector = health.vector;
|
|
1020
|
+
if (!vector) {
|
|
1021
|
+
return {
|
|
1022
|
+
...health,
|
|
1023
|
+
vector: {
|
|
1024
|
+
runtimeOk: health.available,
|
|
1025
|
+
authOk: health.available,
|
|
1026
|
+
modelOk: health.available,
|
|
1027
|
+
quotaOk: true,
|
|
1028
|
+
rateLimitOk: true,
|
|
1029
|
+
runtime: health.available ? "pass" : "fail",
|
|
1030
|
+
auth: health.available ? "pass" : "fail",
|
|
1031
|
+
model: health.available ? "pass" : "fail",
|
|
1032
|
+
quota: "unknown",
|
|
1033
|
+
rateLimit: "unknown",
|
|
1034
|
+
lastProbeKind: "none",
|
|
1035
|
+
checkedAt,
|
|
1036
|
+
expiresAt: new Date(Date.parse(checkedAt) + 30_000).toISOString(),
|
|
1037
|
+
},
|
|
1038
|
+
};
|
|
1039
|
+
}
|
|
1040
|
+
return {
|
|
1041
|
+
...health,
|
|
1042
|
+
vector: {
|
|
1043
|
+
...vector,
|
|
1044
|
+
runtime: vector.runtime ?? legacyBoolToState(vector.runtimeOk),
|
|
1045
|
+
auth: vector.auth ?? legacyBoolToState(vector.authOk),
|
|
1046
|
+
model: vector.model ?? legacyBoolToState(vector.modelOk),
|
|
1047
|
+
quota: vector.quota ?? legacyBoolToState(vector.quotaOk),
|
|
1048
|
+
rateLimit: vector.rateLimit ?? legacyBoolToState(vector.rateLimitOk),
|
|
1049
|
+
lastProbeKind: vector.lastProbeKind ?? "static",
|
|
1050
|
+
checkedAt: vector.checkedAt ?? checkedAt,
|
|
1051
|
+
expiresAt: vector.expiresAt ?? new Date(Date.parse(checkedAt) + 60_000).toISOString(),
|
|
1052
|
+
},
|
|
1053
|
+
};
|
|
1054
|
+
}
|
|
1055
|
+
function probeRank(kind) {
|
|
1056
|
+
switch (kind) {
|
|
1057
|
+
case "live-call":
|
|
1058
|
+
return 3;
|
|
1059
|
+
case "cheap-call":
|
|
1060
|
+
return 2;
|
|
1061
|
+
case "static":
|
|
1062
|
+
return 1;
|
|
1063
|
+
default:
|
|
1064
|
+
return 0;
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1067
|
+
function legacyBoolToState(value) {
|
|
1068
|
+
if (value === true)
|
|
1069
|
+
return "pass";
|
|
1070
|
+
if (value === false)
|
|
1071
|
+
return "fail";
|
|
1072
|
+
return "unknown";
|
|
1073
|
+
}
|
|
1074
|
+
function statePassOrUnknown(state, legacy) {
|
|
1075
|
+
const normalized = state ?? legacyBoolToState(legacy);
|
|
1076
|
+
return normalized !== "fail";
|
|
1077
|
+
}
|
|
1078
|
+
function statePassStrict(state, legacy) {
|
|
1079
|
+
const normalized = state ?? legacyBoolToState(legacy);
|
|
1080
|
+
return normalized === "pass";
|
|
1081
|
+
}
|
|
1082
|
+
function healthStatePenalty(state, legacy, failWeight, unknownWeight) {
|
|
1083
|
+
const normalized = state ?? legacyBoolToState(legacy);
|
|
1084
|
+
if (normalized === "fail")
|
|
1085
|
+
return failWeight;
|
|
1086
|
+
if (normalized === "unknown")
|
|
1087
|
+
return unknownWeight;
|
|
1088
|
+
return 0;
|
|
1089
|
+
}
|
|
1090
|
+
function runtimeHealthScore(health) {
|
|
1091
|
+
if (!health)
|
|
1092
|
+
return 1;
|
|
1093
|
+
const vector = health.vector;
|
|
1094
|
+
if (!vector)
|
|
1095
|
+
return health.available ? 1 : 0;
|
|
1096
|
+
let score = 1;
|
|
1097
|
+
score -= healthStatePenalty(vector.runtime, vector.runtimeOk, 0.25, 0.08);
|
|
1098
|
+
score -= healthStatePenalty(vector.auth, vector.authOk, 0.25, 0.08);
|
|
1099
|
+
score -= healthStatePenalty(vector.model, vector.modelOk, 0.2, 0.05);
|
|
1100
|
+
score -= healthStatePenalty(vector.quota, vector.quotaOk, 0.2, 0.05);
|
|
1101
|
+
score -= healthStatePenalty(vector.rateLimit, vector.rateLimitOk, 0.1, 0.03);
|
|
1102
|
+
return Math.max(0, score);
|
|
581
1103
|
}
|
|
582
1104
|
function compareScoredRuntimes(a, b, intent, scoreCache) {
|
|
583
1105
|
const compositeDelta = b.composite - a.composite;
|