@openclawbrain/cli 0.4.14 → 0.4.15
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/dist/extension/index.js +30 -4
- package/dist/extension/runtime-guard.d.ts +6 -0
- package/dist/extension/runtime-guard.js +71 -15
- package/dist/src/cli.js +157 -19
- package/dist/src/index.d.ts +242 -3
- package/dist/src/index.js +1029 -38
- package/dist/src/learning-spine.js +43 -1
- package/dist/src/local-learner.d.ts +6 -0
- package/dist/src/local-learner.js +84 -152
- package/dist/src/local-session-passive-learning.js +28 -2
- package/dist/src/materialization-embedder.js +11 -0
- package/dist/src/openclaw-hook-truth.d.ts +6 -0
- package/dist/src/openclaw-hook-truth.js +27 -0
- package/dist/src/proof-command.js +196 -1
- package/dist/src/runtime-core.js +87 -7
- package/dist/src/status-learning-path.js +32 -2
- package/dist/src/teacher-decision-match.js +47 -10
- package/dist/src/traced-learning-bridge.js +17 -1
- package/extension/index.ts +35 -4
- package/extension/runtime-guard.ts +92 -14
- package/package.json +1 -1
|
@@ -255,6 +255,134 @@ function extractStatusSignals(statusText) {
|
|
|
255
255
|
proofError: statusText.match(/proofError=([^\s]+)/)?.[1] ?? null,
|
|
256
256
|
};
|
|
257
257
|
}
|
|
258
|
+
function extractDetailedStatusLine(statusText, prefix) {
|
|
259
|
+
const normalizedPrefix = `${prefix} `;
|
|
260
|
+
return statusText.split(/\r?\n/).find((line) => line.startsWith(normalizedPrefix)) ?? null;
|
|
261
|
+
}
|
|
262
|
+
function extractKeyValuePairs(line) {
|
|
263
|
+
if (typeof line !== "string") {
|
|
264
|
+
return {};
|
|
265
|
+
}
|
|
266
|
+
const pairs = {};
|
|
267
|
+
for (const match of line.matchAll(/([A-Za-z][A-Za-z0-9]*)=([^\s]+)/g)) {
|
|
268
|
+
pairs[match[1]] = match[2];
|
|
269
|
+
}
|
|
270
|
+
return pairs;
|
|
271
|
+
}
|
|
272
|
+
function extractAttachedProfileCoverageEntries(line) {
|
|
273
|
+
if (typeof line !== "string") {
|
|
274
|
+
return [];
|
|
275
|
+
}
|
|
276
|
+
const normalized = line.replace(/^attachedSet\s+/, "");
|
|
277
|
+
const proofPathIndex = normalized.indexOf(" proofPath=");
|
|
278
|
+
const entriesText = (proofPathIndex === -1 ? normalized : normalized.slice(0, proofPathIndex)).trim();
|
|
279
|
+
if (entriesText.length === 0 || entriesText === "none") {
|
|
280
|
+
return [];
|
|
281
|
+
}
|
|
282
|
+
const entries = [];
|
|
283
|
+
let index = 0;
|
|
284
|
+
while (index < entriesText.length) {
|
|
285
|
+
while (index < entriesText.length && entriesText[index] === " ") {
|
|
286
|
+
index += 1;
|
|
287
|
+
}
|
|
288
|
+
if (index >= entriesText.length) {
|
|
289
|
+
break;
|
|
290
|
+
}
|
|
291
|
+
const bracketStart = entriesText.indexOf("[", index);
|
|
292
|
+
if (bracketStart === -1) {
|
|
293
|
+
break;
|
|
294
|
+
}
|
|
295
|
+
const bracketEnd = entriesText.indexOf("]", bracketStart + 1);
|
|
296
|
+
if (bracketEnd === -1) {
|
|
297
|
+
break;
|
|
298
|
+
}
|
|
299
|
+
const rawLabel = entriesText.slice(index, bracketStart).trim();
|
|
300
|
+
const fields = extractKeyValuePairs(entriesText.slice(bracketStart + 1, bracketEnd));
|
|
301
|
+
entries.push({
|
|
302
|
+
label: rawLabel.replace(/^\*/, "").trim(),
|
|
303
|
+
current: rawLabel.startsWith("*"),
|
|
304
|
+
hookFiles: fields.hook ?? "unknown",
|
|
305
|
+
configLoad: fields.config ?? "unknown",
|
|
306
|
+
runtimeLoad: fields.runtime ?? "unknown",
|
|
307
|
+
loadedAt: fields.loadedAt ?? null,
|
|
308
|
+
coverageState: fields.hook === "present" && fields.config === "allows_load" && fields.runtime === "proven"
|
|
309
|
+
? "covered"
|
|
310
|
+
: "attention"
|
|
311
|
+
});
|
|
312
|
+
index = bracketEnd + 1;
|
|
313
|
+
}
|
|
314
|
+
return entries;
|
|
315
|
+
}
|
|
316
|
+
function buildCoverageSnapshot({ attachedSetLine, runtimeLoadProofSnapshot, openclawHome }) {
|
|
317
|
+
const parsedEntries = extractAttachedProfileCoverageEntries(attachedSetLine);
|
|
318
|
+
const proofProfiles = Array.isArray(runtimeLoadProofSnapshot?.value?.profiles)
|
|
319
|
+
? runtimeLoadProofSnapshot.value.profiles
|
|
320
|
+
: [];
|
|
321
|
+
const profiles = parsedEntries.length > 0
|
|
322
|
+
? parsedEntries
|
|
323
|
+
: proofProfiles.map((profile) => ({
|
|
324
|
+
label: `${profile?.profileId ?? "current_profile"}@${canonicalizeExistingProofPath(profile?.openclawHome ?? "")}`,
|
|
325
|
+
current: canonicalizeExistingProofPath(profile?.openclawHome ?? "") === canonicalizeExistingProofPath(openclawHome),
|
|
326
|
+
hookFiles: "unknown",
|
|
327
|
+
configLoad: "unknown",
|
|
328
|
+
runtimeLoad: "proven",
|
|
329
|
+
loadedAt: profile?.loadedAt ?? null,
|
|
330
|
+
coverageState: "covered"
|
|
331
|
+
}));
|
|
332
|
+
const runtimeProvenCount = profiles.filter((entry) => entry.runtimeLoad === "proven").length;
|
|
333
|
+
return {
|
|
334
|
+
contract: "openclaw_operator_profile_coverage_snapshot.v1",
|
|
335
|
+
generatedAt: new Date().toISOString(),
|
|
336
|
+
openclawHome: canonicalizeExistingProofPath(openclawHome),
|
|
337
|
+
attachedProfileCount: profiles.length,
|
|
338
|
+
runtimeProofProfileCount: proofProfiles.length,
|
|
339
|
+
hookReadyCount: profiles.filter((entry) => entry.hookFiles === "present").length,
|
|
340
|
+
configReadyCount: profiles.filter((entry) => entry.configLoad === "allows_load").length,
|
|
341
|
+
runtimeProvenCount,
|
|
342
|
+
coverageRate: profiles.length === 0 ? null : runtimeProvenCount / profiles.length,
|
|
343
|
+
profiles
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
function buildHardeningSnapshot({ attachTruthLine, serveLine, routeFnLine, verdict, statusSignals }) {
|
|
347
|
+
const attachTruth = extractKeyValuePairs(attachTruthLine);
|
|
348
|
+
const serve = extractKeyValuePairs(serveLine);
|
|
349
|
+
const routeFn = extractKeyValuePairs(routeFnLine);
|
|
350
|
+
return {
|
|
351
|
+
contract: "openclaw_operator_hardening_snapshot.v1",
|
|
352
|
+
generatedAt: new Date().toISOString(),
|
|
353
|
+
statusSignals: {
|
|
354
|
+
statusOk: statusSignals.statusOk,
|
|
355
|
+
loadProofReady: statusSignals.loadProofReady,
|
|
356
|
+
runtimeProven: statusSignals.runtimeProven,
|
|
357
|
+
serveActivePack: statusSignals.serveActivePack,
|
|
358
|
+
routeFnAvailable: statusSignals.routeFnAvailable,
|
|
359
|
+
},
|
|
360
|
+
attachTruth: {
|
|
361
|
+
current: attachTruth.current ?? null,
|
|
362
|
+
hook: attachTruth.hook ?? null,
|
|
363
|
+
config: attachTruth.config ?? null,
|
|
364
|
+
runtime: attachTruth.runtime ?? null,
|
|
365
|
+
watcher: attachTruth.watcher ?? null,
|
|
366
|
+
},
|
|
367
|
+
serve: {
|
|
368
|
+
state: serve.state ?? null,
|
|
369
|
+
failOpen: serve.failOpen ?? null,
|
|
370
|
+
hardFail: serve.hardFail ?? null,
|
|
371
|
+
usedRouteFn: serve.usedRouteFn ?? null,
|
|
372
|
+
awaitingFirstExport: serve.awaitingFirstExport ?? null,
|
|
373
|
+
},
|
|
374
|
+
routeFn: {
|
|
375
|
+
available: routeFn.available ?? null,
|
|
376
|
+
freshness: routeFn.freshness ?? null,
|
|
377
|
+
},
|
|
378
|
+
verdict: {
|
|
379
|
+
verdict: verdict.verdict,
|
|
380
|
+
severity: verdict.severity,
|
|
381
|
+
missingProofCount: Array.isArray(verdict.missingProofs) ? verdict.missingProofs.length : 0,
|
|
382
|
+
warningCount: Array.isArray(verdict.warnings) ? verdict.warnings.length : 0,
|
|
383
|
+
}
|
|
384
|
+
};
|
|
385
|
+
}
|
|
258
386
|
|
|
259
387
|
function hasPackagedHookSource(pluginInspectText) {
|
|
260
388
|
return /Source:\s+.*(?:@openclawbrain[\\/]+openclaw|openclawbrain)[\\/]+dist[\\/]+extension[\\/]+index\.js/m.test(pluginInspectText);
|
|
@@ -357,7 +485,7 @@ function buildVerdict({ steps, gatewayStatus, pluginInspect, statusSignals, brea
|
|
|
357
485
|
};
|
|
358
486
|
}
|
|
359
487
|
|
|
360
|
-
function buildSummary({ options, steps, verdict, gatewayStatusText, pluginInspectText, statusSignals, breadcrumbs, runtimeLoadProofSnapshot }) {
|
|
488
|
+
function buildSummary({ options, steps, verdict, gatewayStatusText, pluginInspectText, statusSignals, breadcrumbs, runtimeLoadProofSnapshot, guardLine, attributionLine, learningPathLine, coverageSnapshot, hardeningSnapshot }) {
|
|
361
489
|
const passed = [];
|
|
362
490
|
const missing = [];
|
|
363
491
|
const warnings = Array.isArray(verdict.warnings) ? verdict.warnings : [];
|
|
@@ -413,6 +541,33 @@ function buildSummary({ options, steps, verdict, gatewayStatusText, pluginInspec
|
|
|
413
541
|
"## Warnings",
|
|
414
542
|
...(warnings.length === 0 ? ["- none"] : warnings.map((item) => `- ${item}`)),
|
|
415
543
|
"",
|
|
544
|
+
"## Runtime Guard",
|
|
545
|
+
...(guardLine === null
|
|
546
|
+
? ["- runtime guard line not reported by detailed status"]
|
|
547
|
+
: [`- ${guardLine}`]),
|
|
548
|
+
"",
|
|
549
|
+
"## Learning Attribution",
|
|
550
|
+
...(attributionLine === null
|
|
551
|
+
? ["- attribution line not reported by detailed status"]
|
|
552
|
+
: [`- ${attributionLine}`]),
|
|
553
|
+
...(learningPathLine === null
|
|
554
|
+
? []
|
|
555
|
+
: [`- ${learningPathLine}`]),
|
|
556
|
+
"",
|
|
557
|
+
"## Coverage snapshot",
|
|
558
|
+
`- attached profiles: ${coverageSnapshot.attachedProfileCount}`,
|
|
559
|
+
`- runtime-proven profiles: ${coverageSnapshot.runtimeProvenCount}/${coverageSnapshot.attachedProfileCount}`,
|
|
560
|
+
`- coverage rate: ${coverageSnapshot.coverageRate === null ? "none" : coverageSnapshot.coverageRate.toFixed(3)}`,
|
|
561
|
+
...(coverageSnapshot.profiles.length === 0
|
|
562
|
+
? ["- per-profile: none"]
|
|
563
|
+
: coverageSnapshot.profiles.map((entry) => `- ${entry.current ? "*" : ""}${entry.label} coverage=${entry.coverageState} hook=${entry.hookFiles} config=${entry.configLoad} runtime=${entry.runtimeLoad} loadedAt=${entry.loadedAt ?? "none"}`)),
|
|
564
|
+
"",
|
|
565
|
+
"## Hardening snapshot",
|
|
566
|
+
`- status signals: statusOk=${hardeningSnapshot.statusSignals.statusOk} loadProofReady=${hardeningSnapshot.statusSignals.loadProofReady} runtimeProven=${hardeningSnapshot.statusSignals.runtimeProven} serveActivePack=${hardeningSnapshot.statusSignals.serveActivePack} routeFnAvailable=${hardeningSnapshot.statusSignals.routeFnAvailable}`,
|
|
567
|
+
`- serve: state=${hardeningSnapshot.serve.state ?? "none"} failOpen=${hardeningSnapshot.serve.failOpen ?? "none"} hardFail=${hardeningSnapshot.serve.hardFail ?? "none"} usedRouteFn=${hardeningSnapshot.serve.usedRouteFn ?? "none"}`,
|
|
568
|
+
`- attachTruth: current=${hardeningSnapshot.attachTruth.current ?? "none"} hook=${hardeningSnapshot.attachTruth.hook ?? "none"} config=${hardeningSnapshot.attachTruth.config ?? "none"} runtime=${hardeningSnapshot.attachTruth.runtime ?? "none"}`,
|
|
569
|
+
`- proof verdict: ${hardeningSnapshot.verdict.verdict} severity=${hardeningSnapshot.verdict.severity} warnings=${hardeningSnapshot.verdict.warningCount}`,
|
|
570
|
+
"",
|
|
416
571
|
"## Step ledger",
|
|
417
572
|
...steps.map((step) => `- ${step.stepId}: ${step.skipped ? "skipped" : `${step.resultClass} (${step.captureState})`} - ${step.summary}`),
|
|
418
573
|
];
|
|
@@ -634,15 +789,28 @@ export function captureOperatorProofBundle(options) {
|
|
|
634
789
|
const gatewayLogPath = extractGatewayLogPath(gatewayStatusCapture.stdout);
|
|
635
790
|
const activationRoot = extractActivationRoot(statusCapture.stdout, options.activationRoot ?? null);
|
|
636
791
|
const statusSignals = extractStatusSignals(statusCapture.stdout);
|
|
792
|
+
const attachTruthLine = extractDetailedStatusLine(statusCapture.stdout, "attachTruth");
|
|
793
|
+
const attachedSetLine = extractDetailedStatusLine(statusCapture.stdout, "attachedSet");
|
|
794
|
+
const serveLine = extractDetailedStatusLine(statusCapture.stdout, "serve");
|
|
795
|
+
const routeFnLine = extractDetailedStatusLine(statusCapture.stdout, "routeFn");
|
|
796
|
+
const guardLine = extractDetailedStatusLine(statusCapture.stdout, "guard");
|
|
797
|
+
const attributionLine = extractDetailedStatusLine(statusCapture.stdout, "attribution");
|
|
798
|
+
const learningPathLine = extractDetailedStatusLine(statusCapture.stdout, "path");
|
|
637
799
|
const runtimeLoadProofPath = normalizeReportedProofPath(statusSignals.proofPath)
|
|
638
800
|
?? path.join(activationRoot, "attachment-truth", "runtime-load-proofs.json");
|
|
639
801
|
const runtimeLoadProofSnapshot = readJsonSnapshot(runtimeLoadProofPath);
|
|
640
802
|
const gatewayLogText = readTextIfExists(gatewayLogPath);
|
|
641
803
|
const breadcrumbs = extractStartupBreadcrumbs(gatewayLogText, bundleStartedAt);
|
|
804
|
+
const coverageSnapshot = buildCoverageSnapshot({
|
|
805
|
+
attachedSetLine,
|
|
806
|
+
runtimeLoadProofSnapshot,
|
|
807
|
+
openclawHome: options.openclawHome,
|
|
808
|
+
});
|
|
642
809
|
writeText(path.join(bundleDir, "extracted-startup-breadcrumbs.log"), breadcrumbs.all.length === 0
|
|
643
810
|
? "<no matching breadcrumbs found>\n"
|
|
644
811
|
: `${breadcrumbs.all.map((entry) => entry.line).join("\n")}\n`);
|
|
645
812
|
writeJson(path.join(bundleDir, "runtime-load-proof.json"), runtimeLoadProofSnapshot);
|
|
813
|
+
writeJson(path.join(bundleDir, "coverage-snapshot.json"), coverageSnapshot);
|
|
646
814
|
const verdict = buildVerdict({
|
|
647
815
|
steps,
|
|
648
816
|
gatewayStatus: gatewayStatusCapture.stdout,
|
|
@@ -652,6 +820,13 @@ export function captureOperatorProofBundle(options) {
|
|
|
652
820
|
runtimeLoadProofSnapshot,
|
|
653
821
|
openclawHome: options.openclawHome,
|
|
654
822
|
});
|
|
823
|
+
const hardeningSnapshot = buildHardeningSnapshot({
|
|
824
|
+
attachTruthLine,
|
|
825
|
+
serveLine,
|
|
826
|
+
routeFnLine,
|
|
827
|
+
verdict,
|
|
828
|
+
statusSignals,
|
|
829
|
+
});
|
|
655
830
|
writeJson(path.join(bundleDir, "steps.json"), {
|
|
656
831
|
bundleStartedAt,
|
|
657
832
|
openclawHome: canonicalizeExistingProofPath(options.openclawHome),
|
|
@@ -664,6 +839,8 @@ export function captureOperatorProofBundle(options) {
|
|
|
664
839
|
bundleStartedAt,
|
|
665
840
|
verdict,
|
|
666
841
|
statusSignals,
|
|
842
|
+
coverageSnapshot,
|
|
843
|
+
hardeningSnapshot,
|
|
667
844
|
breadcrumbs: {
|
|
668
845
|
allCount: breadcrumbs.all.length,
|
|
669
846
|
postBundleCount: breadcrumbs.afterBundleStart.length,
|
|
@@ -671,7 +848,11 @@ export function captureOperatorProofBundle(options) {
|
|
|
671
848
|
},
|
|
672
849
|
runtimeLoadProofPath,
|
|
673
850
|
runtimeLoadProofError: runtimeLoadProofSnapshot.error,
|
|
851
|
+
guardLine,
|
|
852
|
+
attributionLine,
|
|
853
|
+
learningPathLine,
|
|
674
854
|
});
|
|
855
|
+
writeJson(path.join(bundleDir, "hardening-snapshot.json"), hardeningSnapshot);
|
|
675
856
|
writeText(path.join(bundleDir, "summary.md"), buildSummary({
|
|
676
857
|
options,
|
|
677
858
|
steps,
|
|
@@ -681,6 +862,11 @@ export function captureOperatorProofBundle(options) {
|
|
|
681
862
|
statusSignals,
|
|
682
863
|
breadcrumbs,
|
|
683
864
|
runtimeLoadProofSnapshot,
|
|
865
|
+
guardLine,
|
|
866
|
+
attributionLine,
|
|
867
|
+
learningPathLine,
|
|
868
|
+
coverageSnapshot,
|
|
869
|
+
hardeningSnapshot,
|
|
684
870
|
}));
|
|
685
871
|
return {
|
|
686
872
|
ok: true,
|
|
@@ -691,14 +877,21 @@ export function captureOperatorProofBundle(options) {
|
|
|
691
877
|
gatewayLogPath,
|
|
692
878
|
runtimeLoadProofPath,
|
|
693
879
|
runtimeLoadProofSnapshot,
|
|
880
|
+
coverageSnapshot,
|
|
881
|
+
hardeningSnapshot,
|
|
694
882
|
verdict,
|
|
695
883
|
statusSignals,
|
|
884
|
+
guardLine,
|
|
885
|
+
attributionLine,
|
|
886
|
+
learningPathLine,
|
|
696
887
|
steps,
|
|
697
888
|
summaryPath: path.join(bundleDir, "summary.md"),
|
|
698
889
|
stepsPath: path.join(bundleDir, "steps.json"),
|
|
699
890
|
verdictPath: path.join(bundleDir, "verdict.json"),
|
|
700
891
|
breadcrumbPath: path.join(bundleDir, "extracted-startup-breadcrumbs.log"),
|
|
701
892
|
runtimeLoadProofSnapshotPath: path.join(bundleDir, "runtime-load-proof.json"),
|
|
893
|
+
coverageSnapshotPath: path.join(bundleDir, "coverage-snapshot.json"),
|
|
894
|
+
hardeningSnapshotPath: path.join(bundleDir, "hardening-snapshot.json"),
|
|
702
895
|
};
|
|
703
896
|
}
|
|
704
897
|
|
|
@@ -713,6 +906,8 @@ export function formatOperatorProofResult(result) {
|
|
|
713
906
|
` Verdict: ${result.verdictPath}`,
|
|
714
907
|
` Breadcrumbs: ${result.breadcrumbPath}`,
|
|
715
908
|
` Runtime proof: ${result.runtimeLoadProofSnapshotPath}`,
|
|
909
|
+
` Coverage snapshot: ${result.coverageSnapshotPath}`,
|
|
910
|
+
` Hardening snapshot: ${result.hardeningSnapshotPath}`,
|
|
716
911
|
];
|
|
717
912
|
return lines.join("\n");
|
|
718
913
|
}
|
package/dist/src/runtime-core.js
CHANGED
|
@@ -39,8 +39,75 @@ function normalizeNonNegativeInteger(value, fieldName, fallbackValue) {
|
|
|
39
39
|
return value;
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
-
|
|
43
|
-
|
|
42
|
+
const RUNTIME_COMPARATIVE_REPLAY_MODE_CONFIG = {
|
|
43
|
+
vector_only: {
|
|
44
|
+
routeMode: "heuristic",
|
|
45
|
+
selectionMode: "flat_rank_v1",
|
|
46
|
+
},
|
|
47
|
+
graph_prior_only: {
|
|
48
|
+
routeMode: "heuristic",
|
|
49
|
+
selectionMode: "graph_walk_v1",
|
|
50
|
+
},
|
|
51
|
+
learned_route: {
|
|
52
|
+
routeMode: "learned",
|
|
53
|
+
selectionMode: "graph_walk_v1",
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
function resolveRuntimeComparativeReplayMode(value) {
|
|
58
|
+
return Object.prototype.hasOwnProperty.call(RUNTIME_COMPARATIVE_REPLAY_MODE_CONFIG, value)
|
|
59
|
+
? value
|
|
60
|
+
: null;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function resolveCompileModePlan(modeValue, selectionModeValue) {
|
|
64
|
+
const requestedSelectionMode = normalizeCompileSelectionMode(selectionModeValue);
|
|
65
|
+
const comparativeMode = resolveRuntimeComparativeReplayMode(modeValue);
|
|
66
|
+
|
|
67
|
+
if (comparativeMode === null) {
|
|
68
|
+
if (modeValue === undefined) {
|
|
69
|
+
return {
|
|
70
|
+
comparativeMode: null,
|
|
71
|
+
routeMode: "heuristic",
|
|
72
|
+
selectionMode: requestedSelectionMode,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (modeValue === "heuristic" || modeValue === "learned") {
|
|
77
|
+
return {
|
|
78
|
+
comparativeMode: null,
|
|
79
|
+
routeMode: modeValue,
|
|
80
|
+
selectionMode: requestedSelectionMode,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
throw new Error(
|
|
85
|
+
"mode must be heuristic, learned, vector_only, graph_prior_only, or learned_route",
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const plan = RUNTIME_COMPARATIVE_REPLAY_MODE_CONFIG[comparativeMode];
|
|
90
|
+
|
|
91
|
+
if (requestedSelectionMode !== undefined && requestedSelectionMode !== plan.selectionMode) {
|
|
92
|
+
throw new Error(
|
|
93
|
+
`selectionMode ${requestedSelectionMode} conflicts with comparative mode ${comparativeMode}, expected ${plan.selectionMode}`,
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return {
|
|
98
|
+
comparativeMode,
|
|
99
|
+
routeMode: plan.routeMode,
|
|
100
|
+
selectionMode: plan.selectionMode,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function resolveSyntheticTurnMode(value) {
|
|
105
|
+
const comparativeMode = resolveRuntimeComparativeReplayMode(value);
|
|
106
|
+
if (comparativeMode !== null) {
|
|
107
|
+
return RUNTIME_COMPARATIVE_REPLAY_MODE_CONFIG[comparativeMode].routeMode;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return value === "heuristic" || value === "learned" ? value : undefined;
|
|
44
111
|
}
|
|
45
112
|
|
|
46
113
|
function normalizeCompileSelectionMode(value) {
|
|
@@ -83,7 +150,7 @@ function formatPromptContext(compileResponse) {
|
|
|
83
150
|
}
|
|
84
151
|
|
|
85
152
|
for (const block of compileResponse.selectedContext) {
|
|
86
|
-
lines.push(`
|
|
153
|
+
lines.push(`PROVENANCE_REF: ctx_${block.id}`);
|
|
87
154
|
lines.push(`BLOCK_ID: ${block.id}`);
|
|
88
155
|
lines.push(block.text.trim());
|
|
89
156
|
lines.push("");
|
|
@@ -273,8 +340,10 @@ function appendCompileServeRouteDecisionLog(input) {
|
|
|
273
340
|
syntheticTurn.budgetStrategy = input.compileInput.budgetStrategy;
|
|
274
341
|
}
|
|
275
342
|
|
|
276
|
-
|
|
277
|
-
|
|
343
|
+
const syntheticTurnMode = resolveSyntheticTurnMode(input.compileInput.mode);
|
|
344
|
+
|
|
345
|
+
if (syntheticTurnMode !== undefined) {
|
|
346
|
+
syntheticTurn.mode = syntheticTurnMode;
|
|
278
347
|
}
|
|
279
348
|
|
|
280
349
|
if (input.compileInput.runtimeHints !== undefined) {
|
|
@@ -456,6 +525,7 @@ export function compileRuntimeContext(input) {
|
|
|
456
525
|
let activationRoot = fallbackActivationRoot;
|
|
457
526
|
let agentId = process.env.OPENCLAWBRAIN_AGENT_ID ?? DEFAULT_AGENT_ID;
|
|
458
527
|
let runtimeHints = [];
|
|
528
|
+
let comparativeMode = null;
|
|
459
529
|
let selectionMode;
|
|
460
530
|
let userMessage = "";
|
|
461
531
|
let maxContextChars;
|
|
@@ -470,13 +540,15 @@ export function compileRuntimeContext(input) {
|
|
|
470
540
|
activationRoot = path.resolve(normalizeNonEmptyString(input.activationRoot, "activationRoot"));
|
|
471
541
|
agentId = normalizeOptionalString(input.agentId) ?? process.env.OPENCLAWBRAIN_AGENT_ID ?? DEFAULT_AGENT_ID;
|
|
472
542
|
runtimeHints = normalizeRuntimeHints(input.runtimeHints);
|
|
473
|
-
selectionMode = normalizeCompileSelectionMode(input.selectionMode);
|
|
474
543
|
userMessage = normalizeNonEmptyString(input.message, "message");
|
|
475
544
|
maxContextChars =
|
|
476
545
|
input.maxContextChars !== undefined
|
|
477
546
|
? normalizeNonNegativeInteger(input.maxContextChars, "maxContextChars", input.maxContextChars)
|
|
478
547
|
: undefined;
|
|
479
|
-
|
|
548
|
+
const compileModePlan = resolveCompileModePlan(input.mode, input.selectionMode);
|
|
549
|
+
comparativeMode = compileModePlan.comparativeMode;
|
|
550
|
+
mode = compileModePlan.routeMode;
|
|
551
|
+
selectionMode = compileModePlan.selectionMode;
|
|
480
552
|
} catch (error) {
|
|
481
553
|
result = failOpenCompileResult(
|
|
482
554
|
error,
|
|
@@ -518,6 +590,7 @@ export function compileRuntimeContext(input) {
|
|
|
518
590
|
},
|
|
519
591
|
);
|
|
520
592
|
routeSelectionMs = elapsedMsFrom(routeSelectionStartedAtNs);
|
|
593
|
+
const selectionEngine = selectionMode ?? "flat_rank_v1";
|
|
521
594
|
const compileResponse = {
|
|
522
595
|
...compile.response,
|
|
523
596
|
diagnostics: {
|
|
@@ -525,6 +598,13 @@ export function compileRuntimeContext(input) {
|
|
|
525
598
|
notes: uniqueNotes([
|
|
526
599
|
...compile.response.diagnostics.notes,
|
|
527
600
|
...resolvedBudget.notes,
|
|
601
|
+
`selection_engine=${selectionEngine}`,
|
|
602
|
+
...(comparativeMode === null
|
|
603
|
+
? []
|
|
604
|
+
: [
|
|
605
|
+
`comparative_mode=${comparativeMode}`,
|
|
606
|
+
`comparative_mode_plan=${mode}+${selectionEngine}`,
|
|
607
|
+
]),
|
|
528
608
|
"OpenClaw remains the runtime owner",
|
|
529
609
|
]),
|
|
530
610
|
},
|
|
@@ -7,14 +7,43 @@ function isSeedAwaitingFirstPromotion(status) {
|
|
|
7
7
|
function normalizeOptionalString(value) {
|
|
8
8
|
return typeof value === "string" && value.trim().length > 0 ? value : null;
|
|
9
9
|
}
|
|
10
|
+
function formatOperatorLearningAttributionSummary({ status }) {
|
|
11
|
+
const attribution = status?.learningAttribution ?? null;
|
|
12
|
+
if (!attribution) {
|
|
13
|
+
return "quality=unavailable source=unavailable detail=no_learning_attribution_surface";
|
|
14
|
+
}
|
|
15
|
+
const source = [normalizeOptionalString(attribution.source), normalizeOptionalString(attribution.snapshotKind)]
|
|
16
|
+
.filter((value) => value !== null)
|
|
17
|
+
.join("/");
|
|
18
|
+
if (attribution.available !== true) {
|
|
19
|
+
return `quality=${normalizeOptionalString(attribution.quality) ?? "unavailable"} source=${source || "unavailable"} detail=${normalizeOptionalString(attribution.detail) ?? "unavailable"}`;
|
|
20
|
+
}
|
|
21
|
+
const matchedByMode = attribution.matchedByMode ?? {};
|
|
22
|
+
return [
|
|
23
|
+
`quality=${normalizeOptionalString(attribution.quality) ?? "unavailable"}`,
|
|
24
|
+
`source=${source || "latest_materialization"}`,
|
|
25
|
+
`nonZero=${attribution.nonZeroObservationCount ?? 0}`,
|
|
26
|
+
`exact=${attribution.exactMatchCount ?? 0}`,
|
|
27
|
+
`heuristic=${attribution.heuristicMatchCount ?? 0}`,
|
|
28
|
+
`unmatched=${attribution.unmatchedCount ?? 0}`,
|
|
29
|
+
`ambiguous=${attribution.ambiguousCount ?? 0}`,
|
|
30
|
+
`modes=decision:${matchedByMode.exactDecisionId ?? 0}|digest:${matchedByMode.exactSelectionDigest ?? 0}|compile:${matchedByMode.turnCompileEventId ?? 0}|heuristic:${matchedByMode.legacyHeuristic ?? 0}`
|
|
31
|
+
].join(" ");
|
|
32
|
+
}
|
|
10
33
|
export function formatOperatorLearningPathSummary({ status, learningPath, tracedLearning }) {
|
|
34
|
+
const attribution = status?.learningAttribution ?? null;
|
|
11
35
|
if (!isSeedAwaitingFirstPromotion(status)) {
|
|
12
|
-
|
|
36
|
+
const rawSummary = formatRawLearningPathSummary(learningPath);
|
|
37
|
+
const bindingQuality = normalizeOptionalString(attribution?.quality);
|
|
38
|
+
return bindingQuality === null || bindingQuality === "unavailable"
|
|
39
|
+
? rawSummary
|
|
40
|
+
: `${rawSummary} bindingQuality=${bindingQuality}`;
|
|
13
41
|
}
|
|
14
42
|
const detailParts = [
|
|
15
43
|
"detail=seed_state_awaiting_first_promotion",
|
|
16
44
|
`tracedPg=${normalizeOptionalString(tracedLearning?.pgVersionUsed) ?? "none"}`,
|
|
17
|
-
`tracedPack=${normalizeOptionalString(tracedLearning?.materializedPackId) ?? "none"}
|
|
45
|
+
`tracedPack=${normalizeOptionalString(tracedLearning?.materializedPackId) ?? "none"}`,
|
|
46
|
+
`bindingQuality=${normalizeOptionalString(attribution?.quality) ?? "unavailable"}`
|
|
18
47
|
];
|
|
19
48
|
return [
|
|
20
49
|
"source=seed_state",
|
|
@@ -26,3 +55,4 @@ export function formatOperatorLearningPathSummary({ status, learningPath, traced
|
|
|
26
55
|
...detailParts
|
|
27
56
|
].join(" ");
|
|
28
57
|
}
|
|
58
|
+
export { formatOperatorLearningAttributionSummary };
|
|
@@ -80,6 +80,14 @@ function readInteractionActivePackGraphChecksum(interaction) {
|
|
|
80
80
|
?? undefined;
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
+
function readInteractionExplicitTurnCompileEventId(interaction) {
|
|
84
|
+
return normalizeOptionalString(interaction?.turnCompileEventId)
|
|
85
|
+
?? normalizeOptionalString(toRecord(interaction?.routeMetadata)?.turnCompileEventId)
|
|
86
|
+
?? normalizeOptionalString(toRecord(interaction?.decisionProvenance)?.turnCompileEventId)
|
|
87
|
+
?? normalizeOptionalString(toRecord(interaction?.metadata)?.turnCompileEventId)
|
|
88
|
+
?? undefined;
|
|
89
|
+
}
|
|
90
|
+
|
|
83
91
|
function buildDecisionTimestamps(decision) {
|
|
84
92
|
const timestamps = [];
|
|
85
93
|
const turnCreatedAt = toTimestamp(decision.turnCreatedAt);
|
|
@@ -139,8 +147,10 @@ export function createServeTimeDecisionMatcher(decisions, options = {}) {
|
|
|
139
147
|
const decisionsByRecordId = new Map();
|
|
140
148
|
const decisionsBySelectionDigest = new Map();
|
|
141
149
|
const ambiguousSelectionDigests = new Set();
|
|
142
|
-
const
|
|
150
|
+
const decisionsByTurnCompileEventId = new Map();
|
|
151
|
+
const ambiguousTurnCompileEventIds = new Set();
|
|
143
152
|
const fallbackDecisions = new Map();
|
|
153
|
+
const ambiguousFallbackDecisionKeys = new Set();
|
|
144
154
|
const decisionsBySessionChannel = new Map();
|
|
145
155
|
const globalFallbackDecisions = [];
|
|
146
156
|
|
|
@@ -164,15 +174,27 @@ export function createServeTimeDecisionMatcher(decisions, options = {}) {
|
|
|
164
174
|
}
|
|
165
175
|
}
|
|
166
176
|
const turnCompileEventId = normalizeOptionalString(decision.turnCompileEventId);
|
|
167
|
-
if (turnCompileEventId !== undefined
|
|
168
|
-
|
|
177
|
+
if (turnCompileEventId !== undefined) {
|
|
178
|
+
if (decisionsByTurnCompileEventId.has(turnCompileEventId)) {
|
|
179
|
+
decisionsByTurnCompileEventId.delete(turnCompileEventId);
|
|
180
|
+
ambiguousTurnCompileEventIds.add(turnCompileEventId);
|
|
181
|
+
}
|
|
182
|
+
else if (!ambiguousTurnCompileEventIds.has(turnCompileEventId)) {
|
|
183
|
+
decisionsByTurnCompileEventId.set(turnCompileEventId, decision);
|
|
184
|
+
}
|
|
169
185
|
}
|
|
170
186
|
for (const candidateKey of [
|
|
171
187
|
buildCandidateKey(decision.sessionId, decision.channel, decision.turnCreatedAt),
|
|
172
188
|
buildCandidateKey(decision.sessionId, decision.channel, decision.recordedAt),
|
|
173
189
|
]) {
|
|
174
|
-
if (candidateKey !== null
|
|
175
|
-
fallbackDecisions.
|
|
190
|
+
if (candidateKey !== null) {
|
|
191
|
+
if (fallbackDecisions.has(candidateKey)) {
|
|
192
|
+
fallbackDecisions.delete(candidateKey);
|
|
193
|
+
ambiguousFallbackDecisionKeys.add(candidateKey);
|
|
194
|
+
}
|
|
195
|
+
else if (!ambiguousFallbackDecisionKeys.has(candidateKey)) {
|
|
196
|
+
fallbackDecisions.set(candidateKey, decision);
|
|
197
|
+
}
|
|
176
198
|
}
|
|
177
199
|
}
|
|
178
200
|
const sessionChannelKey = buildSessionChannelKey(decision.sessionId, decision.channel);
|
|
@@ -203,22 +225,37 @@ export function createServeTimeDecisionMatcher(decisions, options = {}) {
|
|
|
203
225
|
if (decisionRecordId !== undefined) {
|
|
204
226
|
return decisionsByRecordId.get(decisionRecordId) ?? null;
|
|
205
227
|
}
|
|
206
|
-
const
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
);
|
|
228
|
+
const interactionSelectionDigest = readInteractionSelectionDigest(interaction);
|
|
229
|
+
const interactionGraphChecksum = readInteractionActivePackGraphChecksum(interaction);
|
|
230
|
+
const selectionDigestKey = buildSelectionDigestKey(interactionSelectionDigest, interactionGraphChecksum);
|
|
210
231
|
if (selectionDigestKey !== null) {
|
|
211
232
|
if (ambiguousSelectionDigests.has(selectionDigestKey)) {
|
|
212
233
|
return null;
|
|
213
234
|
}
|
|
214
235
|
return decisionsBySelectionDigest.get(selectionDigestKey) ?? null;
|
|
215
236
|
}
|
|
216
|
-
|
|
237
|
+
if (interactionSelectionDigest !== undefined || interactionGraphChecksum !== undefined) {
|
|
238
|
+
return null;
|
|
239
|
+
}
|
|
240
|
+
const explicitTurnCompileEventId = readInteractionExplicitTurnCompileEventId(interaction);
|
|
241
|
+
if (explicitTurnCompileEventId !== undefined) {
|
|
242
|
+
if (ambiguousTurnCompileEventIds.has(explicitTurnCompileEventId)) {
|
|
243
|
+
return null;
|
|
244
|
+
}
|
|
245
|
+
return decisionsByTurnCompileEventId.get(explicitTurnCompileEventId) ?? null;
|
|
246
|
+
}
|
|
247
|
+
const softTurnCompileEventId = normalizeOptionalString(interaction.eventId);
|
|
248
|
+
const exact = softTurnCompileEventId === undefined || ambiguousTurnCompileEventIds.has(softTurnCompileEventId)
|
|
249
|
+
? undefined
|
|
250
|
+
: decisionsByTurnCompileEventId.get(softTurnCompileEventId);
|
|
217
251
|
if (exact !== undefined) {
|
|
218
252
|
return exact;
|
|
219
253
|
}
|
|
220
254
|
const exactFallbackKey = buildCandidateKey(interaction.sessionId, interaction.channel, interaction.createdAt);
|
|
221
255
|
if (exactFallbackKey !== null) {
|
|
256
|
+
if (ambiguousFallbackDecisionKeys.has(exactFallbackKey)) {
|
|
257
|
+
return null;
|
|
258
|
+
}
|
|
222
259
|
const fallback = fallbackDecisions.get(exactFallbackKey);
|
|
223
260
|
if (fallback !== undefined) {
|
|
224
261
|
return fallback;
|
|
@@ -18,6 +18,22 @@ function normalizeOptionalString(value) {
|
|
|
18
18
|
function normalizeSource(value) {
|
|
19
19
|
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
20
20
|
}
|
|
21
|
+
function summarizeBridgeSource(value) {
|
|
22
|
+
const source = normalizeSource(value);
|
|
23
|
+
if (source === null) {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
const summarized = {
|
|
27
|
+
command: normalizeOptionalString(source.command),
|
|
28
|
+
bridge: normalizeOptionalString(source.bridge),
|
|
29
|
+
brainRoot: normalizeOptionalString(source.brainRoot),
|
|
30
|
+
stateDbPath: normalizeOptionalString(source.stateDbPath),
|
|
31
|
+
persistedKey: normalizeOptionalString(source.persistedKey),
|
|
32
|
+
candidatePackVersion: Number.isFinite(source.candidatePackVersion) ? Math.trunc(source.candidatePackVersion) : undefined,
|
|
33
|
+
candidateUpdateCount: normalizeCount(source.candidateUpdateCount)
|
|
34
|
+
};
|
|
35
|
+
return Object.fromEntries(Object.entries(summarized).filter(([, candidate]) => candidate !== null && candidate !== undefined));
|
|
36
|
+
}
|
|
21
37
|
function normalizeBridgePayload(payload) {
|
|
22
38
|
if (payload === null || typeof payload !== "object" || Array.isArray(payload)) {
|
|
23
39
|
throw new Error("expected traced-learning bridge payload object");
|
|
@@ -579,7 +595,7 @@ export function mergeTracedLearningBridgePayload(payload, persisted) {
|
|
|
579
595
|
supervisionCount: persistedBridge.supervisionCount,
|
|
580
596
|
routerUpdateCount: persistedBridge.routerUpdateCount,
|
|
581
597
|
teacherArtifactCount: persistedBridge.teacherArtifactCount,
|
|
582
|
-
source: persistedBridge.source
|
|
598
|
+
source: summarizeBridgeSource(persistedBridge.source)
|
|
583
599
|
}
|
|
584
600
|
}
|
|
585
601
|
});
|