@openclawbrain/openclaw 0.1.11 → 0.2.0
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/README.md +13 -6
- package/dist/src/cli.d.ts +85 -1
- package/dist/src/cli.js +1500 -27
- package/dist/src/cli.js.map +1 -1
- package/dist/src/daemon.d.ts +26 -0
- package/dist/src/daemon.js +362 -0
- package/dist/src/daemon.js.map +1 -0
- package/dist/src/import-export.d.ts +36 -0
- package/dist/src/import-export.js +171 -0
- package/dist/src/import-export.js.map +1 -0
- package/dist/src/index.d.ts +361 -4
- package/dist/src/index.js +1629 -77
- package/dist/src/index.js.map +1 -1
- package/dist/src/local-session-passive-learning.d.ts +60 -0
- package/dist/src/local-session-passive-learning.js +359 -0
- package/dist/src/local-session-passive-learning.js.map +1 -0
- package/dist/src/resolve-activation-root.d.ts +27 -0
- package/dist/src/resolve-activation-root.js +120 -0
- package/dist/src/resolve-activation-root.js.map +1 -0
- package/dist/src/session-store.d.ts +150 -0
- package/dist/src/session-store.js +199 -0
- package/dist/src/session-store.js.map +1 -0
- package/dist/src/session-tail.d.ts +68 -0
- package/dist/src/session-tail.js +519 -0
- package/dist/src/session-tail.js.map +1 -0
- package/extension/index.ts +50 -0
- package/package.json +16 -13
- package/LICENSE +0 -201
package/dist/src/index.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
-
import { existsSync, mkdirSync, readFileSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import process from "node:process";
|
|
5
5
|
import { compileRuntimeFromActivation } from "@openclawbrain/compiler";
|
|
6
6
|
import { CONTRACT_IDS, buildNormalizedEventExport, canonicalJson, checksumJsonPayload, createFeedbackEvent, createInteractionEvent, sortNormalizedEvents, validateKernelSurface, validateNormalizedEventExport } from "@openclawbrain/contracts";
|
|
7
|
-
import { describeNormalizedEventExportObservability } from "@openclawbrain/event-export";
|
|
7
|
+
import { classifyFeedbackSignalContent, describeNormalizedEventExportObservability } from "@openclawbrain/event-export";
|
|
8
8
|
import { DEFAULT_TEACHER_SUPERVISION_STALE_AFTER_MS, advanceAlwaysOnLearningRuntime, buildTeacherSupervisionArtifactsFromNormalizedEventExport, createAlwaysOnLearningRuntimeState, describeAlwaysOnLearningRuntimeState, materializeAlwaysOnLearningCandidatePack, materializeCandidatePackFromNormalizedEventExport } from "@openclawbrain/learner";
|
|
9
9
|
import { LEARNING_SPINE_LOG_LAYOUT, activatePack, describeActivationObservability, describeActivationTarget, describePackCompileTarget, inspectActivationState, loadPackFromActivation, promoteCandidatePack, readLearningSpineLogEntries, rollbackActivePack, stageCandidatePack } from "@openclawbrain/pack-format";
|
|
10
10
|
import { appendLearningUpdateLogs, appendServeTimeRouteDecisionLog } from "./learning-spine.js";
|
|
@@ -21,11 +21,14 @@ export const RUNTIME_EVENT_EXPORT_BUNDLE_LAYOUT = {
|
|
|
21
21
|
manifest: "manifest.json",
|
|
22
22
|
payload: "normalized-event-export.json"
|
|
23
23
|
};
|
|
24
|
-
function
|
|
25
|
-
if (value === undefined || value === null
|
|
26
|
-
return
|
|
24
|
+
function normalizeRuntimeProfileSelector(value, fieldName, fallback = "current_profile") {
|
|
25
|
+
if (value === undefined || value === null) {
|
|
26
|
+
return fallback;
|
|
27
27
|
}
|
|
28
|
-
|
|
28
|
+
return normalizeNonEmptyString(value, fieldName);
|
|
29
|
+
}
|
|
30
|
+
function normalizeBootstrapProfileSelector(value) {
|
|
31
|
+
return normalizeRuntimeProfileSelector(value, "profileSelector");
|
|
29
32
|
}
|
|
30
33
|
function quoteShellArg(value) {
|
|
31
34
|
return `'${value.replace(/'/g, `'"'"'`)}'`;
|
|
@@ -57,10 +60,13 @@ function buildBootstrapAttachRollbackDryRunCommand(activationRoot) {
|
|
|
57
60
|
return `${detectBootstrapOperatorCliPrefix()} rollback --activation-root ${quoteShellArg(activationRoot)} --dry-run`;
|
|
58
61
|
}
|
|
59
62
|
function buildBootstrapRuntimeAttachNextSteps(input) {
|
|
63
|
+
const profileSpecific = input.profileSelector !== "current_profile";
|
|
60
64
|
const nextSteps = [
|
|
61
65
|
{
|
|
62
66
|
id: "inspect_current_profile_status",
|
|
63
|
-
detail:
|
|
67
|
+
detail: profileSpecific
|
|
68
|
+
? `Inspect the canonical current-profile status for this activation root; explicit attach/export attribution for profileSelector="${input.profileSelector}" stays on the runtime path.`
|
|
69
|
+
: 'Ask the attached gateway "How\'s the brain?" with the compact current-profile status summary; add --json for the canonical object.',
|
|
64
70
|
command: buildBootstrapAttachStatusCommand(input.activationRoot)
|
|
65
71
|
},
|
|
66
72
|
{
|
|
@@ -72,14 +78,18 @@ function buildBootstrapRuntimeAttachNextSteps(input) {
|
|
|
72
78
|
if (input.currentProfile.brainStatus.awaitingFirstExport) {
|
|
73
79
|
nextSteps.push({
|
|
74
80
|
id: "record_next_current_profile_turn",
|
|
75
|
-
detail:
|
|
81
|
+
detail: profileSpecific
|
|
82
|
+
? `Export the next live turn with profileSelector="${input.profileSelector}" so refresh/promote can advance beyond seed_state_authoritative without widening the operator read scope.`
|
|
83
|
+
: "Export the next live current-profile turn so refresh/promote can advance beyond seed_state_authoritative.",
|
|
76
84
|
command: null
|
|
77
85
|
});
|
|
78
86
|
}
|
|
79
87
|
else {
|
|
80
88
|
nextSteps.push({
|
|
81
89
|
id: "continue_current_profile_learning_loop",
|
|
82
|
-
detail:
|
|
90
|
+
detail: profileSpecific
|
|
91
|
+
? `Keep exporting turns with profileSelector="${input.profileSelector}" and refresh/promote newer activation-ready packs off the hot path.`
|
|
92
|
+
: "Keep exporting current-profile turns and refresh/promote newer activation-ready packs off the hot path.",
|
|
83
93
|
command: null
|
|
84
94
|
});
|
|
85
95
|
}
|
|
@@ -87,12 +97,16 @@ function buildBootstrapRuntimeAttachNextSteps(input) {
|
|
|
87
97
|
}
|
|
88
98
|
export function formatBootstrapRuntimeAttachReport(result) {
|
|
89
99
|
const [nextStep, ...followUps] = result.nextSteps;
|
|
100
|
+
const profileIdSuffix = result.currentProfile.profile.profileId === null ? "" : ` id=${result.currentProfile.profile.profileId}`;
|
|
90
101
|
const lines = [
|
|
91
102
|
`ATTACH ${result.currentProfile.brainStatus.status}`,
|
|
92
|
-
`profile selector=${result.profileSelector} attachment=${result.currentProfile.attachment.state}`,
|
|
103
|
+
`profile selector=${result.profileSelector}${profileIdSuffix} attachment=${result.currentProfile.attachment.state} policy=${result.currentProfile.attachment.policyMode} operator_scope=${result.operatorReadScope}`,
|
|
93
104
|
`activation root=${result.activationRoot}`,
|
|
94
105
|
`brain pack=${result.packId} state=${result.currentProfile.brain.state} serve=${result.currentProfile.brainStatus.serveState}`
|
|
95
106
|
];
|
|
107
|
+
if (result.profileSelector !== "current_profile") {
|
|
108
|
+
lines.push("boundary canonical status remains current_profile-only; explicit attached profile attribution stays on the runtime attach/export path");
|
|
109
|
+
}
|
|
96
110
|
if (nextStep !== undefined) {
|
|
97
111
|
lines.push(`next ${nextStep.detail}`);
|
|
98
112
|
if (nextStep.command !== null) {
|
|
@@ -267,6 +281,7 @@ function buildContinuousTurnExport(turn, loopRoot) {
|
|
|
267
281
|
const exportSeed = checksumJsonPayload({
|
|
268
282
|
sessionId: turn.sessionId,
|
|
269
283
|
channel: turn.channel,
|
|
284
|
+
userId: turn.userId ?? null,
|
|
270
285
|
sourceStream: turn.sourceStream ?? null,
|
|
271
286
|
userMessage: turn.userMessage,
|
|
272
287
|
createdAt: turn.createdAt ?? null,
|
|
@@ -287,7 +302,9 @@ function buildContinuousTurnExport(turn, loopRoot) {
|
|
|
287
302
|
.filter((item) => item !== null)
|
|
288
303
|
.map((item) => ({
|
|
289
304
|
content: item.content,
|
|
305
|
+
actorName: item.actorName ?? null,
|
|
290
306
|
createdAt: item.createdAt ?? null,
|
|
307
|
+
priorityHint: item.priorityHint ?? null,
|
|
291
308
|
sequence: item.sequence ?? null,
|
|
292
309
|
kind: item.kind ?? null,
|
|
293
310
|
messageId: item.messageId ?? null,
|
|
@@ -581,6 +598,129 @@ export class AsyncTeacherLiveLoop {
|
|
|
581
598
|
reason: null
|
|
582
599
|
};
|
|
583
600
|
}
|
|
601
|
+
enqueueScannedEventExport(scannedEventExport, options = {}) {
|
|
602
|
+
const built = buildNormalizedEventExportFromScannedEvents(scannedEventExport);
|
|
603
|
+
if (!built.ok) {
|
|
604
|
+
return {
|
|
605
|
+
accepted: false,
|
|
606
|
+
exportDigest: null,
|
|
607
|
+
queueDepth: this.queue.length,
|
|
608
|
+
notes: [...this.diagnostics.notes],
|
|
609
|
+
reason: built.reason,
|
|
610
|
+
warnings: [...built.warnings],
|
|
611
|
+
error: built.error,
|
|
612
|
+
scanner: cloneScannerExportManifest(built.scanner)
|
|
613
|
+
};
|
|
614
|
+
}
|
|
615
|
+
const enqueue = this.enqueueNormalizedEventExport(built.normalizedEventExport, {
|
|
616
|
+
observedAt: options.observedAt ?? built.scanner.producedAt
|
|
617
|
+
});
|
|
618
|
+
return {
|
|
619
|
+
accepted: enqueue.accepted,
|
|
620
|
+
exportDigest: enqueue.exportDigest,
|
|
621
|
+
queueDepth: enqueue.queueDepth,
|
|
622
|
+
notes: [...enqueue.notes],
|
|
623
|
+
reason: enqueue.reason,
|
|
624
|
+
warnings: [...built.warnings],
|
|
625
|
+
error: null,
|
|
626
|
+
scanner: cloneScannerExportManifest(built.scanner)
|
|
627
|
+
};
|
|
628
|
+
}
|
|
629
|
+
async ingestRuntimeEventExportScannerScan(scan) {
|
|
630
|
+
if (scan.selected.length === 0) {
|
|
631
|
+
this.diagnostics.lastNoOpReason = "empty_scan";
|
|
632
|
+
this.refreshNotes();
|
|
633
|
+
const snapshot = this.snapshot();
|
|
634
|
+
return {
|
|
635
|
+
runtimeOwner: "openclaw",
|
|
636
|
+
scanRoot: scan.scanRoot,
|
|
637
|
+
scannedAt: scan.scannedAt,
|
|
638
|
+
selectedCount: 0,
|
|
639
|
+
acceptedCount: 0,
|
|
640
|
+
duplicateCount: 0,
|
|
641
|
+
droppedCount: 0,
|
|
642
|
+
liveAcceptedCount: 0,
|
|
643
|
+
backfillAcceptedCount: 0,
|
|
644
|
+
duplicateScannerDigestCount: scan.duplicateExportDigests.length,
|
|
645
|
+
staleSkippedCount: scan.staleSkippedExportDigests.length,
|
|
646
|
+
invalidBundleCount: scan.invalidBundles.length,
|
|
647
|
+
noOpReason: "empty_scan",
|
|
648
|
+
notes: [...snapshot.diagnostics.notes],
|
|
649
|
+
results: [],
|
|
650
|
+
snapshot
|
|
651
|
+
};
|
|
652
|
+
}
|
|
653
|
+
const results = [];
|
|
654
|
+
let acceptedCount = 0;
|
|
655
|
+
let duplicateCount = 0;
|
|
656
|
+
let droppedCount = 0;
|
|
657
|
+
let liveAcceptedCount = 0;
|
|
658
|
+
let backfillAcceptedCount = 0;
|
|
659
|
+
for (const hit of scan.selected) {
|
|
660
|
+
let enqueue = this.enqueueNormalizedEventExport(hit.normalizedEventExport, {
|
|
661
|
+
observedAt: hit.exportedAt
|
|
662
|
+
});
|
|
663
|
+
if (!enqueue.accepted && enqueue.reason === "queue_full") {
|
|
664
|
+
await this.flush();
|
|
665
|
+
enqueue = this.enqueueNormalizedEventExport(hit.normalizedEventExport, {
|
|
666
|
+
observedAt: hit.exportedAt
|
|
667
|
+
});
|
|
668
|
+
}
|
|
669
|
+
if (enqueue.accepted) {
|
|
670
|
+
acceptedCount += 1;
|
|
671
|
+
if (hit.lane === "live") {
|
|
672
|
+
liveAcceptedCount += 1;
|
|
673
|
+
}
|
|
674
|
+
else {
|
|
675
|
+
backfillAcceptedCount += 1;
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
else if (enqueue.reason === "duplicate_export") {
|
|
679
|
+
duplicateCount += 1;
|
|
680
|
+
}
|
|
681
|
+
else if (enqueue.reason === "queue_full") {
|
|
682
|
+
droppedCount += 1;
|
|
683
|
+
}
|
|
684
|
+
results.push({
|
|
685
|
+
lane: hit.lane,
|
|
686
|
+
exportDigest: hit.exportDigest,
|
|
687
|
+
exportName: hit.exportName,
|
|
688
|
+
exportedAt: hit.exportedAt,
|
|
689
|
+
eventRange: {
|
|
690
|
+
start: hit.eventRange.start,
|
|
691
|
+
end: hit.eventRange.end,
|
|
692
|
+
count: hit.eventRange.count
|
|
693
|
+
},
|
|
694
|
+
accepted: enqueue.accepted,
|
|
695
|
+
queueDepth: enqueue.queueDepth,
|
|
696
|
+
reason: enqueue.reason
|
|
697
|
+
});
|
|
698
|
+
}
|
|
699
|
+
const snapshot = acceptedCount > 0 ? await this.flush() : this.snapshot();
|
|
700
|
+
const noOpReason = acceptedCount > 0
|
|
701
|
+
? "none"
|
|
702
|
+
: duplicateCount === scan.selected.length
|
|
703
|
+
? "duplicate_exports"
|
|
704
|
+
: "queue_full";
|
|
705
|
+
return {
|
|
706
|
+
runtimeOwner: "openclaw",
|
|
707
|
+
scanRoot: scan.scanRoot,
|
|
708
|
+
scannedAt: scan.scannedAt,
|
|
709
|
+
selectedCount: scan.selected.length,
|
|
710
|
+
acceptedCount,
|
|
711
|
+
duplicateCount,
|
|
712
|
+
droppedCount,
|
|
713
|
+
liveAcceptedCount,
|
|
714
|
+
backfillAcceptedCount,
|
|
715
|
+
duplicateScannerDigestCount: scan.duplicateExportDigests.length,
|
|
716
|
+
staleSkippedCount: scan.staleSkippedExportDigests.length,
|
|
717
|
+
invalidBundleCount: scan.invalidBundles.length,
|
|
718
|
+
noOpReason,
|
|
719
|
+
notes: [...snapshot.diagnostics.notes],
|
|
720
|
+
results,
|
|
721
|
+
snapshot
|
|
722
|
+
};
|
|
723
|
+
}
|
|
584
724
|
async flush() {
|
|
585
725
|
await this.ensureDrain();
|
|
586
726
|
return this.snapshot();
|
|
@@ -684,6 +824,118 @@ export class AsyncTeacherLiveLoop {
|
|
|
684
824
|
export function createAsyncTeacherLiveLoop(input) {
|
|
685
825
|
return new AsyncTeacherLiveLoop(input);
|
|
686
826
|
}
|
|
827
|
+
function normalizeScannerStringArray(value, fieldName) {
|
|
828
|
+
if (!Array.isArray(value)) {
|
|
829
|
+
throw new Error(`${fieldName} must be an array of strings`);
|
|
830
|
+
}
|
|
831
|
+
return value.map((entry, index) => normalizeNonEmptyString(entry, `${fieldName}[${index}]`));
|
|
832
|
+
}
|
|
833
|
+
function normalizeScannerLiveWorkspaceInput(workspace) {
|
|
834
|
+
const normalized = {
|
|
835
|
+
workspaceId: normalizeNonEmptyString(workspace.workspaceId, "workspace.workspaceId"),
|
|
836
|
+
snapshotId: normalizeNonEmptyString(workspace.snapshotId, "workspace.snapshotId"),
|
|
837
|
+
capturedAt: normalizeIsoTimestamp(workspace.capturedAt, "workspace.capturedAt"),
|
|
838
|
+
rootDir: path.resolve(normalizeNonEmptyString(workspace.rootDir, "workspace.rootDir")),
|
|
839
|
+
branch: normalizeOptionalString(workspace.branch) ?? null,
|
|
840
|
+
revision: normalizeOptionalString(workspace.revision) ?? null,
|
|
841
|
+
dirty: workspace.dirty === true,
|
|
842
|
+
manifestDigest: normalizeOptionalString(workspace.manifestDigest) ?? null
|
|
843
|
+
};
|
|
844
|
+
if (workspace.labels !== undefined) {
|
|
845
|
+
normalized.labels = normalizeScannerStringArray(workspace.labels, "workspace.labels");
|
|
846
|
+
}
|
|
847
|
+
if (workspace.files !== undefined) {
|
|
848
|
+
normalized.files = normalizeScannerStringArray(workspace.files, "workspace.files");
|
|
849
|
+
}
|
|
850
|
+
return normalized;
|
|
851
|
+
}
|
|
852
|
+
export function scanRecordedSession(input) {
|
|
853
|
+
const rootDir = path.resolve(normalizeNonEmptyString(input.rootDir, "rootDir"));
|
|
854
|
+
const fixture = buildRecordedSessionReplayFixture(structuredClone(input.trace));
|
|
855
|
+
const bundle = runRecordedSessionReplay(rootDir, fixture);
|
|
856
|
+
return {
|
|
857
|
+
runtimeOwner: "openclaw",
|
|
858
|
+
scanMode: "session",
|
|
859
|
+
rootDir,
|
|
860
|
+
fixtureHash: fixture.fixtureHash,
|
|
861
|
+
bundle
|
|
862
|
+
};
|
|
863
|
+
}
|
|
864
|
+
export function scanLiveEventExport(input) {
|
|
865
|
+
const normalizedEventExport = structuredClone(input.normalizedEventExport);
|
|
866
|
+
const validationErrors = validateNormalizedEventExport(normalizedEventExport);
|
|
867
|
+
if (validationErrors.length > 0) {
|
|
868
|
+
throw new Error(`normalized event export is invalid: ${validationErrors.join("; ")}`);
|
|
869
|
+
}
|
|
870
|
+
const workspace = normalizeScannerLiveWorkspaceInput(input.workspace);
|
|
871
|
+
const packLabel = normalizeOptionalString(input.packLabel) ?? "scanner-live-cli";
|
|
872
|
+
const observedAt = normalizeIsoTimestamp(input.observedAt, "observedAt", normalizedEventExport.range.lastCreatedAt ?? normalizedEventExport.range.firstCreatedAt ?? new Date().toISOString());
|
|
873
|
+
const teacherArtifacts = buildTeacherSupervisionArtifactsFromNormalizedEventExport({
|
|
874
|
+
normalizedEventExport,
|
|
875
|
+
observedAt,
|
|
876
|
+
staleAfterMs: input.staleAfterMs ?? DEFAULT_TEACHER_SUPERVISION_STALE_AFTER_MS
|
|
877
|
+
});
|
|
878
|
+
const learnerResult = advanceAlwaysOnLearningRuntime({
|
|
879
|
+
packLabel,
|
|
880
|
+
workspace,
|
|
881
|
+
interactionEvents: normalizedEventExport.interactionEvents,
|
|
882
|
+
feedbackEvents: normalizedEventExport.feedbackEvents,
|
|
883
|
+
teacherSupervisionArtifacts: teacherArtifacts,
|
|
884
|
+
learnedRouting: input.learnedRouting ?? true,
|
|
885
|
+
state: createAlwaysOnLearningRuntimeState(),
|
|
886
|
+
builtAt: normalizeIsoTimestamp(input.builtAt, "builtAt", observedAt),
|
|
887
|
+
...(input.liveSliceSize !== undefined ? { liveSliceSize: input.liveSliceSize } : {}),
|
|
888
|
+
...(input.backfillSliceSize !== undefined ? { backfillSliceSize: input.backfillSliceSize } : {})
|
|
889
|
+
});
|
|
890
|
+
const latestFreshness = latestTeacherFreshness(teacherArtifacts);
|
|
891
|
+
const lastNoOpReason = teacherArtifacts.length === 0 ? "no_teacher_artifacts" : "none";
|
|
892
|
+
const snapshot = {
|
|
893
|
+
runtimeOwner: "openclaw",
|
|
894
|
+
queue: {
|
|
895
|
+
capacity: 1,
|
|
896
|
+
depth: 0,
|
|
897
|
+
running: false
|
|
898
|
+
},
|
|
899
|
+
teacher: {
|
|
900
|
+
artifactCount: teacherArtifacts.length,
|
|
901
|
+
artifacts: cloneTeacherSupervisionArtifacts(teacherArtifacts),
|
|
902
|
+
latestFreshness
|
|
903
|
+
},
|
|
904
|
+
learner: {
|
|
905
|
+
state: structuredClone(learnerResult.state),
|
|
906
|
+
lastMaterialization: cloneAlwaysOnLearningMaterializationJobOrNull(learnerResult.materialization)
|
|
907
|
+
},
|
|
908
|
+
diagnostics: {
|
|
909
|
+
acceptedExportCount: 1,
|
|
910
|
+
processedExportCount: 1,
|
|
911
|
+
duplicateExportCount: 0,
|
|
912
|
+
droppedExportCount: 0,
|
|
913
|
+
emittedArtifactCount: teacherArtifacts.length,
|
|
914
|
+
dedupedArtifactCount: 0,
|
|
915
|
+
lastProcessedAt: observedAt,
|
|
916
|
+
latestFreshness,
|
|
917
|
+
lastNoOpReason,
|
|
918
|
+
notes: buildAsyncTeacherLoopNotes({
|
|
919
|
+
queueDepth: 0,
|
|
920
|
+
latestFreshness,
|
|
921
|
+
artifactCount: teacherArtifacts.length,
|
|
922
|
+
emittedArtifactCount: teacherArtifacts.length,
|
|
923
|
+
dedupedArtifactCount: 0,
|
|
924
|
+
sparseFeedback: learnerResult.state.sparseFeedback,
|
|
925
|
+
noOpReason: lastNoOpReason,
|
|
926
|
+
materialization: learnerResult.materialization
|
|
927
|
+
})
|
|
928
|
+
}
|
|
929
|
+
};
|
|
930
|
+
return {
|
|
931
|
+
runtimeOwner: "openclaw",
|
|
932
|
+
scanMode: "live",
|
|
933
|
+
observedAt,
|
|
934
|
+
packLabel,
|
|
935
|
+
supervision: buildCanonicalSupervision(normalizedEventExport),
|
|
936
|
+
snapshot
|
|
937
|
+
};
|
|
938
|
+
}
|
|
687
939
|
function readJsonFile(filePath) {
|
|
688
940
|
return JSON.parse(readFileSync(filePath, "utf8"));
|
|
689
941
|
}
|
|
@@ -715,7 +967,8 @@ export function buildRuntimeEventExportBundleManifest(input) {
|
|
|
715
967
|
feedbackCount: input.normalizedEventExport.provenance.feedbackCount,
|
|
716
968
|
sourceStreams: [...input.normalizedEventExport.provenance.sourceStreams],
|
|
717
969
|
contracts: [...input.normalizedEventExport.provenance.contracts]
|
|
718
|
-
}
|
|
970
|
+
},
|
|
971
|
+
...(input.scanner !== undefined ? { scanner: cloneScannerExportManifestOrNull(input.scanner) } : {})
|
|
719
972
|
};
|
|
720
973
|
}
|
|
721
974
|
export function validateRuntimeEventExportBundleManifest(value, normalizedEventExport) {
|
|
@@ -735,6 +988,12 @@ export function validateRuntimeEventExportBundleManifest(value, normalizedEventE
|
|
|
735
988
|
if (value.payloadDigest.length === 0) {
|
|
736
989
|
errors.push("payloadDigest is required");
|
|
737
990
|
}
|
|
991
|
+
if (value.scanner !== undefined && value.scanner !== null) {
|
|
992
|
+
errors.push(...validateScannerExportManifest(value.scanner).map((message) => `scanner ${message}`));
|
|
993
|
+
if (normalizedEventExport !== undefined && normalizedEventExport.range.count === 0) {
|
|
994
|
+
errors.push("scanner bundles require at least one exported event");
|
|
995
|
+
}
|
|
996
|
+
}
|
|
738
997
|
if (normalizedEventExport !== undefined) {
|
|
739
998
|
const exportErrors = validateNormalizedEventExport(normalizedEventExport);
|
|
740
999
|
if (exportErrors.length > 0) {
|
|
@@ -744,7 +1003,8 @@ export function validateRuntimeEventExportBundleManifest(value, normalizedEventE
|
|
|
744
1003
|
exportName: value.exportName,
|
|
745
1004
|
exportedAt: value.exportedAt,
|
|
746
1005
|
payloadPath: value.payloadPath,
|
|
747
|
-
normalizedEventExport
|
|
1006
|
+
normalizedEventExport,
|
|
1007
|
+
...(value.scanner !== undefined ? { scanner: value.scanner } : {})
|
|
748
1008
|
});
|
|
749
1009
|
if (rebuilt.payloadDigest !== value.payloadDigest) {
|
|
750
1010
|
errors.push("event export bundle payloadDigest does not match the supplied normalized event export");
|
|
@@ -752,6 +1012,9 @@ export function validateRuntimeEventExportBundleManifest(value, normalizedEventE
|
|
|
752
1012
|
if (canonicalJson(rebuilt.summary) !== canonicalJson(value.summary)) {
|
|
753
1013
|
errors.push("event export bundle summary does not match the supplied normalized event export");
|
|
754
1014
|
}
|
|
1015
|
+
if (canonicalJson(rebuilt.scanner ?? null) !== canonicalJson(value.scanner ?? null)) {
|
|
1016
|
+
errors.push("event export bundle scanner metadata does not match the supplied normalized event export");
|
|
1017
|
+
}
|
|
755
1018
|
}
|
|
756
1019
|
return errors;
|
|
757
1020
|
}
|
|
@@ -773,6 +1036,709 @@ export function loadRuntimeEventExportBundle(rootDir) {
|
|
|
773
1036
|
normalizedEventExport
|
|
774
1037
|
};
|
|
775
1038
|
}
|
|
1039
|
+
function cloneScannerExportManifest(value) {
|
|
1040
|
+
return {
|
|
1041
|
+
scannerId: value.scannerId,
|
|
1042
|
+
lane: value.lane,
|
|
1043
|
+
status: value.status,
|
|
1044
|
+
producedAt: value.producedAt,
|
|
1045
|
+
sourceManifestPath: value.sourceManifestPath,
|
|
1046
|
+
sourceManifestDigest: value.sourceManifestDigest,
|
|
1047
|
+
warnings: [...value.warnings],
|
|
1048
|
+
failures: [...value.failures]
|
|
1049
|
+
};
|
|
1050
|
+
}
|
|
1051
|
+
function cloneScannerExportManifestOrNull(value) {
|
|
1052
|
+
return value === null ? null : cloneScannerExportManifest(value);
|
|
1053
|
+
}
|
|
1054
|
+
function validateScannerExportManifest(value) {
|
|
1055
|
+
const errors = [];
|
|
1056
|
+
if (normalizeOptionalString(value.scannerId) === undefined) {
|
|
1057
|
+
errors.push("scannerId is required");
|
|
1058
|
+
}
|
|
1059
|
+
if (normalizeOptionalString(value.lane) === undefined) {
|
|
1060
|
+
errors.push("lane is required");
|
|
1061
|
+
}
|
|
1062
|
+
if (!["complete", "partial", "failed"].includes(value.status)) {
|
|
1063
|
+
errors.push("status must be complete, partial, or failed");
|
|
1064
|
+
}
|
|
1065
|
+
if (Number.isNaN(Date.parse(value.producedAt))) {
|
|
1066
|
+
errors.push("producedAt must be an ISO timestamp");
|
|
1067
|
+
}
|
|
1068
|
+
if (value.sourceManifestPath !== null && normalizeOptionalString(value.sourceManifestPath) === undefined) {
|
|
1069
|
+
errors.push("sourceManifestPath must be null or a non-empty string");
|
|
1070
|
+
}
|
|
1071
|
+
if (value.sourceManifestDigest !== null && normalizeOptionalString(value.sourceManifestDigest) === undefined) {
|
|
1072
|
+
errors.push("sourceManifestDigest must be null or a non-empty string");
|
|
1073
|
+
}
|
|
1074
|
+
if (!Array.isArray(value.warnings) || value.warnings.some((warning) => normalizeOptionalString(warning) === undefined)) {
|
|
1075
|
+
errors.push("warnings must contain only non-empty strings");
|
|
1076
|
+
}
|
|
1077
|
+
if (!Array.isArray(value.failures) || value.failures.some((failure) => normalizeOptionalString(failure) === undefined)) {
|
|
1078
|
+
errors.push("failures must contain only non-empty strings");
|
|
1079
|
+
}
|
|
1080
|
+
if (value.status === "failed" && value.failures.length === 0) {
|
|
1081
|
+
errors.push("failed scanner manifests require at least one failure");
|
|
1082
|
+
}
|
|
1083
|
+
return errors;
|
|
1084
|
+
}
|
|
1085
|
+
function normalizeScannerExportWarnings(value) {
|
|
1086
|
+
const warnings = new Set();
|
|
1087
|
+
for (const warning of value.warnings) {
|
|
1088
|
+
const normalized = normalizeOptionalString(warning);
|
|
1089
|
+
if (normalized !== undefined) {
|
|
1090
|
+
warnings.add(normalized);
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
1093
|
+
for (const failure of value.failures) {
|
|
1094
|
+
const normalized = normalizeOptionalString(failure);
|
|
1095
|
+
if (normalized !== undefined) {
|
|
1096
|
+
warnings.add(`scanner_failure:${normalized}`);
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
return [...warnings];
|
|
1100
|
+
}
|
|
1101
|
+
export function buildNormalizedEventExportFromScannedEvents(input) {
|
|
1102
|
+
const scanner = cloneScannerExportManifest(input.scanner);
|
|
1103
|
+
const scannerErrors = validateScannerExportManifest(scanner);
|
|
1104
|
+
if (scannerErrors.length > 0) {
|
|
1105
|
+
return {
|
|
1106
|
+
ok: false,
|
|
1107
|
+
normalizedEventExport: null,
|
|
1108
|
+
scanner,
|
|
1109
|
+
warnings: normalizeScannerExportWarnings(scanner),
|
|
1110
|
+
reason: "invalid_scanner_manifest",
|
|
1111
|
+
error: `scanner manifest is invalid: ${scannerErrors.join("; ")}`
|
|
1112
|
+
};
|
|
1113
|
+
}
|
|
1114
|
+
const eventCount = input.interactionEvents.length + input.feedbackEvents.length;
|
|
1115
|
+
if (eventCount === 0) {
|
|
1116
|
+
return {
|
|
1117
|
+
ok: false,
|
|
1118
|
+
normalizedEventExport: null,
|
|
1119
|
+
scanner,
|
|
1120
|
+
warnings: normalizeScannerExportWarnings(scanner),
|
|
1121
|
+
reason: scanner.status === "failed" ? "scan_failed" : "no_events",
|
|
1122
|
+
error: scanner.status === "failed"
|
|
1123
|
+
? "scanner failed before producing any interaction or feedback events"
|
|
1124
|
+
: "scanner output did not contain any interaction or feedback events"
|
|
1125
|
+
};
|
|
1126
|
+
}
|
|
1127
|
+
const normalizedEventExport = buildNormalizedEventExport({
|
|
1128
|
+
interactionEvents: [...input.interactionEvents],
|
|
1129
|
+
feedbackEvents: [...input.feedbackEvents]
|
|
1130
|
+
});
|
|
1131
|
+
const exportErrors = validateNormalizedEventExport(normalizedEventExport);
|
|
1132
|
+
if (exportErrors.length > 0) {
|
|
1133
|
+
return {
|
|
1134
|
+
ok: false,
|
|
1135
|
+
normalizedEventExport: null,
|
|
1136
|
+
scanner,
|
|
1137
|
+
warnings: normalizeScannerExportWarnings(scanner),
|
|
1138
|
+
reason: "invalid_scanner_manifest",
|
|
1139
|
+
error: `scanner output produced an invalid normalized event export: ${exportErrors.join("; ")}`
|
|
1140
|
+
};
|
|
1141
|
+
}
|
|
1142
|
+
return {
|
|
1143
|
+
ok: true,
|
|
1144
|
+
normalizedEventExport,
|
|
1145
|
+
scanner,
|
|
1146
|
+
warnings: normalizeScannerExportWarnings(scanner)
|
|
1147
|
+
};
|
|
1148
|
+
}
|
|
1149
|
+
function writeNormalizedEventExportBundleFiles(input) {
|
|
1150
|
+
const resolvedRoot = path.resolve(input.rootDir);
|
|
1151
|
+
const manifestPath = path.join(resolvedRoot, RUNTIME_EVENT_EXPORT_BUNDLE_LAYOUT.manifest);
|
|
1152
|
+
const payloadPath = path.join(resolvedRoot, input.manifest.payloadPath);
|
|
1153
|
+
mkdirSync(path.dirname(payloadPath), { recursive: true });
|
|
1154
|
+
writeFileSync(payloadPath, canonicalJson(input.normalizedEventExport), "utf8");
|
|
1155
|
+
writeFileSync(manifestPath, canonicalJson(input.manifest), "utf8");
|
|
1156
|
+
const descriptor = loadRuntimeEventExportBundle(resolvedRoot);
|
|
1157
|
+
return {
|
|
1158
|
+
ok: true,
|
|
1159
|
+
wroteBundle: true,
|
|
1160
|
+
normalizedEventExport: descriptor.normalizedEventExport,
|
|
1161
|
+
rootDir: descriptor.rootDir,
|
|
1162
|
+
manifestPath: descriptor.manifestPath,
|
|
1163
|
+
payloadPath: descriptor.payloadPath,
|
|
1164
|
+
manifest: descriptor.manifest
|
|
1165
|
+
};
|
|
1166
|
+
}
|
|
1167
|
+
const RUNTIME_EVENT_EXPORT_SCANNER_CHECKPOINT_CONTRACT = "runtime_event_export_scanner_checkpoint.v1";
|
|
1168
|
+
export const DEFAULT_RUNTIME_EVENT_EXPORT_SCANNER_LIVE_TAIL_BUNDLES = 2;
|
|
1169
|
+
export const DEFAULT_RUNTIME_EVENT_EXPORT_SCANNER_BACKFILL_BUNDLES_PER_PASS = 1;
|
|
1170
|
+
export const DEFAULT_RUNTIME_EVENT_EXPORT_SCANNER_STALE_HISTORY_MS = 1000 * 60 * 60 * 24 * 7;
|
|
1171
|
+
export const DEFAULT_RUNTIME_EVENT_EXPORT_SCANNER_CHECKPOINT_BASENAME = ".openclawbrain-scanner-checkpoint.json";
|
|
1172
|
+
function validateRuntimeEventExportScannerBundleCursor(value, fieldName) {
|
|
1173
|
+
const errors = [];
|
|
1174
|
+
if (value.exportDigest.length === 0) {
|
|
1175
|
+
errors.push(`${fieldName}.exportDigest is required`);
|
|
1176
|
+
}
|
|
1177
|
+
if (value.exportName.length === 0) {
|
|
1178
|
+
errors.push(`${fieldName}.exportName is required`);
|
|
1179
|
+
}
|
|
1180
|
+
if (Number.isNaN(Date.parse(value.exportedAt))) {
|
|
1181
|
+
errors.push(`${fieldName}.exportedAt must be an ISO timestamp`);
|
|
1182
|
+
}
|
|
1183
|
+
if (!Number.isInteger(value.eventRange.start) || value.eventRange.start < 0) {
|
|
1184
|
+
errors.push(`${fieldName}.eventRange.start must be a non-negative integer`);
|
|
1185
|
+
}
|
|
1186
|
+
if (!Number.isInteger(value.eventRange.end) || value.eventRange.end < 0) {
|
|
1187
|
+
errors.push(`${fieldName}.eventRange.end must be a non-negative integer`);
|
|
1188
|
+
}
|
|
1189
|
+
if (!Number.isInteger(value.eventRange.count) || value.eventRange.count < 0) {
|
|
1190
|
+
errors.push(`${fieldName}.eventRange.count must be a non-negative integer`);
|
|
1191
|
+
}
|
|
1192
|
+
return errors;
|
|
1193
|
+
}
|
|
1194
|
+
export function createRuntimeEventExportScannerCheckpoint(input) {
|
|
1195
|
+
return {
|
|
1196
|
+
contract: RUNTIME_EVENT_EXPORT_SCANNER_CHECKPOINT_CONTRACT,
|
|
1197
|
+
runtimeOwner: "openclaw",
|
|
1198
|
+
scanRoot: path.resolve(normalizeNonEmptyString(input.scanRoot, "scanRoot")),
|
|
1199
|
+
updatedAt: normalizeIsoTimestamp(input.updatedAt, "updatedAt", new Date().toISOString()),
|
|
1200
|
+
live: {
|
|
1201
|
+
after: null
|
|
1202
|
+
},
|
|
1203
|
+
backfill: {
|
|
1204
|
+
before: null,
|
|
1205
|
+
exhausted: false,
|
|
1206
|
+
staleBefore: null
|
|
1207
|
+
},
|
|
1208
|
+
processedExportDigests: [],
|
|
1209
|
+
stats: {
|
|
1210
|
+
scanPasses: 0,
|
|
1211
|
+
liveBundlesScanned: 0,
|
|
1212
|
+
backfillBundlesScanned: 0,
|
|
1213
|
+
duplicateBundlesSkipped: 0,
|
|
1214
|
+
staleBundlesSkipped: 0,
|
|
1215
|
+
invalidBundlesSkipped: 0
|
|
1216
|
+
}
|
|
1217
|
+
};
|
|
1218
|
+
}
|
|
1219
|
+
export function validateRuntimeEventExportScannerCheckpoint(value) {
|
|
1220
|
+
const errors = [];
|
|
1221
|
+
if (value.contract !== RUNTIME_EVENT_EXPORT_SCANNER_CHECKPOINT_CONTRACT) {
|
|
1222
|
+
errors.push("runtime_event_export_scanner_checkpoint.v1 contract is required");
|
|
1223
|
+
}
|
|
1224
|
+
if (value.runtimeOwner !== "openclaw") {
|
|
1225
|
+
errors.push("runtime event export scanner checkpoint runtimeOwner must be openclaw");
|
|
1226
|
+
}
|
|
1227
|
+
if (value.scanRoot.length === 0) {
|
|
1228
|
+
errors.push("runtime event export scanner checkpoint scanRoot is required");
|
|
1229
|
+
}
|
|
1230
|
+
if (Number.isNaN(Date.parse(value.updatedAt))) {
|
|
1231
|
+
errors.push("runtime event export scanner checkpoint updatedAt must be an ISO timestamp");
|
|
1232
|
+
}
|
|
1233
|
+
if (value.live.after !== null) {
|
|
1234
|
+
errors.push(...validateRuntimeEventExportScannerBundleCursor(value.live.after, "live.after"));
|
|
1235
|
+
}
|
|
1236
|
+
if (value.backfill.before !== null) {
|
|
1237
|
+
errors.push(...validateRuntimeEventExportScannerBundleCursor(value.backfill.before, "backfill.before"));
|
|
1238
|
+
}
|
|
1239
|
+
if (value.backfill.staleBefore !== null && Number.isNaN(Date.parse(value.backfill.staleBefore))) {
|
|
1240
|
+
errors.push("runtime event export scanner checkpoint backfill.staleBefore must be null or an ISO timestamp");
|
|
1241
|
+
}
|
|
1242
|
+
if (new Set(value.processedExportDigests).size !== value.processedExportDigests.length) {
|
|
1243
|
+
errors.push("runtime event export scanner checkpoint processedExportDigests must be unique");
|
|
1244
|
+
}
|
|
1245
|
+
for (const [label, count] of Object.entries(value.stats)) {
|
|
1246
|
+
if (!Number.isInteger(count) || count < 0) {
|
|
1247
|
+
errors.push(`runtime event export scanner checkpoint stats.${label} must be a non-negative integer`);
|
|
1248
|
+
}
|
|
1249
|
+
}
|
|
1250
|
+
return errors;
|
|
1251
|
+
}
|
|
1252
|
+
export function loadRuntimeEventExportScannerCheckpoint(checkpointPath) {
|
|
1253
|
+
const resolvedPath = path.resolve(normalizeNonEmptyString(checkpointPath, "checkpointPath"));
|
|
1254
|
+
const checkpoint = readJsonFile(resolvedPath);
|
|
1255
|
+
const errors = validateRuntimeEventExportScannerCheckpoint(checkpoint);
|
|
1256
|
+
if (errors.length > 0) {
|
|
1257
|
+
throw new Error(`runtime event export scanner checkpoint is invalid: ${errors.join("; ")}`);
|
|
1258
|
+
}
|
|
1259
|
+
return structuredClone(checkpoint);
|
|
1260
|
+
}
|
|
1261
|
+
function writeRuntimeEventExportScannerCheckpoint(checkpointPath, checkpoint) {
|
|
1262
|
+
const resolvedPath = path.resolve(checkpointPath);
|
|
1263
|
+
const errors = validateRuntimeEventExportScannerCheckpoint(checkpoint);
|
|
1264
|
+
if (errors.length > 0) {
|
|
1265
|
+
throw new Error(`runtime event export scanner checkpoint is invalid: ${errors.join("; ")}`);
|
|
1266
|
+
}
|
|
1267
|
+
mkdirSync(path.dirname(resolvedPath), { recursive: true });
|
|
1268
|
+
writeFileSync(resolvedPath, canonicalJson(checkpoint), "utf8");
|
|
1269
|
+
}
|
|
1270
|
+
function normalizePositiveInteger(value, fieldName, fallbackValue) {
|
|
1271
|
+
const resolved = value ?? fallbackValue;
|
|
1272
|
+
if (!Number.isInteger(resolved) || resolved <= 0) {
|
|
1273
|
+
throw new Error(`${fieldName} must be a positive integer`);
|
|
1274
|
+
}
|
|
1275
|
+
return resolved;
|
|
1276
|
+
}
|
|
1277
|
+
function normalizeNonNegativeDurationMs(value, fieldName, fallbackValue) {
|
|
1278
|
+
const resolved = value ?? fallbackValue;
|
|
1279
|
+
if (!Number.isInteger(resolved) || resolved < 0) {
|
|
1280
|
+
throw new Error(`${fieldName} must be a non-negative integer`);
|
|
1281
|
+
}
|
|
1282
|
+
return resolved;
|
|
1283
|
+
}
|
|
1284
|
+
function buildRuntimeEventExportScannerBundleCursor(descriptor) {
|
|
1285
|
+
return {
|
|
1286
|
+
exportDigest: descriptor.normalizedEventExport.provenance.exportDigest,
|
|
1287
|
+
exportName: descriptor.manifest.exportName,
|
|
1288
|
+
exportedAt: descriptor.manifest.exportedAt,
|
|
1289
|
+
eventRange: {
|
|
1290
|
+
start: descriptor.normalizedEventExport.range.start,
|
|
1291
|
+
end: descriptor.normalizedEventExport.range.end,
|
|
1292
|
+
count: descriptor.normalizedEventExport.range.count
|
|
1293
|
+
}
|
|
1294
|
+
};
|
|
1295
|
+
}
|
|
1296
|
+
function compareRuntimeEventExportScannerBundleCursor(left, right) {
|
|
1297
|
+
if (left.eventRange.end !== right.eventRange.end) {
|
|
1298
|
+
return left.eventRange.end - right.eventRange.end;
|
|
1299
|
+
}
|
|
1300
|
+
if (left.eventRange.start !== right.eventRange.start) {
|
|
1301
|
+
return left.eventRange.start - right.eventRange.start;
|
|
1302
|
+
}
|
|
1303
|
+
if (left.exportedAt !== right.exportedAt) {
|
|
1304
|
+
return left.exportedAt.localeCompare(right.exportedAt);
|
|
1305
|
+
}
|
|
1306
|
+
return left.exportDigest.localeCompare(right.exportDigest);
|
|
1307
|
+
}
|
|
1308
|
+
function buildRuntimeEventExportScannerHit(bundle, queueEntry) {
|
|
1309
|
+
return {
|
|
1310
|
+
lane: queueEntry.lane,
|
|
1311
|
+
rootDir: queueEntry.rootDir,
|
|
1312
|
+
exportDigest: queueEntry.exportDigest,
|
|
1313
|
+
exportName: queueEntry.exportName,
|
|
1314
|
+
exportedAt: queueEntry.exportedAt,
|
|
1315
|
+
eventRange: {
|
|
1316
|
+
start: queueEntry.eventRange.start,
|
|
1317
|
+
end: queueEntry.eventRange.end,
|
|
1318
|
+
count: queueEntry.eventRange.count
|
|
1319
|
+
},
|
|
1320
|
+
priorityBucket: queueEntry.priorityBucket,
|
|
1321
|
+
priorityScore: queueEntry.priorityScore,
|
|
1322
|
+
priorityReasons: [...queueEntry.priorityReasons],
|
|
1323
|
+
humanLabelCount: queueEntry.humanLabelCount,
|
|
1324
|
+
feedbackCount: queueEntry.feedbackCount,
|
|
1325
|
+
teacherRoles: [...queueEntry.teacherRoles],
|
|
1326
|
+
teacherAuthorities: [...queueEntry.teacherAuthorities],
|
|
1327
|
+
priorityClasses: [...queueEntry.priorityClasses],
|
|
1328
|
+
scopedPrincipalEventCount: queueEntry.scopedPrincipalEventCount,
|
|
1329
|
+
supersedingPrincipalEventCount: queueEntry.supersedingPrincipalEventCount,
|
|
1330
|
+
staleHistory: queueEntry.staleHistory,
|
|
1331
|
+
ageMsFromLatest: queueEntry.ageMsFromLatest,
|
|
1332
|
+
normalizedEventExport: structuredClone(bundle.descriptor.normalizedEventExport)
|
|
1333
|
+
};
|
|
1334
|
+
}
|
|
1335
|
+
function scannerTeacherRoleWeight(role) {
|
|
1336
|
+
switch (role) {
|
|
1337
|
+
case "principal":
|
|
1338
|
+
return 4;
|
|
1339
|
+
case "admin":
|
|
1340
|
+
return 3;
|
|
1341
|
+
case "operator":
|
|
1342
|
+
return 1.5;
|
|
1343
|
+
case "user":
|
|
1344
|
+
case "assistant":
|
|
1345
|
+
case "system":
|
|
1346
|
+
default:
|
|
1347
|
+
return 0;
|
|
1348
|
+
}
|
|
1349
|
+
}
|
|
1350
|
+
function scannerTeacherAuthorityWeight(authority) {
|
|
1351
|
+
switch (authority) {
|
|
1352
|
+
case "binding":
|
|
1353
|
+
return 4;
|
|
1354
|
+
case "primary_human":
|
|
1355
|
+
return 3;
|
|
1356
|
+
case "high":
|
|
1357
|
+
return 2;
|
|
1358
|
+
case "normal":
|
|
1359
|
+
return 0.5;
|
|
1360
|
+
case "background":
|
|
1361
|
+
default:
|
|
1362
|
+
return 0;
|
|
1363
|
+
}
|
|
1364
|
+
}
|
|
1365
|
+
function scannerPrincipalPriorityWeight(priorityClass) {
|
|
1366
|
+
switch (priorityClass) {
|
|
1367
|
+
case "critical":
|
|
1368
|
+
return 4;
|
|
1369
|
+
case "high":
|
|
1370
|
+
return 3;
|
|
1371
|
+
case "normal":
|
|
1372
|
+
return 1;
|
|
1373
|
+
case "low":
|
|
1374
|
+
default:
|
|
1375
|
+
return 0;
|
|
1376
|
+
}
|
|
1377
|
+
}
|
|
1378
|
+
function scannerFeedbackPriorityWeight(kind) {
|
|
1379
|
+
switch (kind) {
|
|
1380
|
+
case "correction":
|
|
1381
|
+
return 5;
|
|
1382
|
+
case "teaching":
|
|
1383
|
+
return 4;
|
|
1384
|
+
case "approval":
|
|
1385
|
+
return 2;
|
|
1386
|
+
case "suppression":
|
|
1387
|
+
default:
|
|
1388
|
+
return 1;
|
|
1389
|
+
}
|
|
1390
|
+
}
|
|
1391
|
+
function isPrincipalHeavyScannerBundle(learningSurface) {
|
|
1392
|
+
return (learningSurface.principalSummary.teacherRoles.some((role) => role === "principal" || role === "admin") ||
|
|
1393
|
+
learningSurface.principalSummary.teacherAuthorities.some((authority) => authority === "binding" || authority === "primary_human" || authority === "high") ||
|
|
1394
|
+
learningSurface.principalSummary.priorityClasses.some((priorityClass) => priorityClass === "critical" || priorityClass === "high") ||
|
|
1395
|
+
learningSurface.principalSummary.supersedingEventCount > 0);
|
|
1396
|
+
}
|
|
1397
|
+
function buildRuntimeEventExportScannerQueueEntry(input) {
|
|
1398
|
+
const learningSurface = input.bundle.descriptor.normalizedEventExport.provenance.learningSurface;
|
|
1399
|
+
const ageMsFromLatest = input.latestExportedAt === null ? null : Math.max(0, Date.parse(input.latestExportedAt) - Date.parse(input.bundle.cursor.exportedAt));
|
|
1400
|
+
const recencyScore = ageMsFromLatest === null ? 0 : roundMetric(clamp(3 - ageMsFromLatest / 86_400_000, 0, 3));
|
|
1401
|
+
const feedbackScore = input.bundle.descriptor.normalizedEventExport.feedbackEvents.reduce((sum, event) => sum + scannerFeedbackPriorityWeight(event.kind), 0);
|
|
1402
|
+
const roleScore = learningSurface.principalSummary.teacherRoles.reduce((sum, role) => sum + scannerTeacherRoleWeight(role), 0);
|
|
1403
|
+
const authorityScore = learningSurface.principalSummary.teacherAuthorities.reduce((sum, authority) => sum + scannerTeacherAuthorityWeight(authority), 0);
|
|
1404
|
+
const priorityClassScore = learningSurface.principalSummary.priorityClasses.reduce((sum, priorityClass) => sum + scannerPrincipalPriorityWeight(priorityClass), 0);
|
|
1405
|
+
const principalHeavy = isPrincipalHeavyScannerBundle(learningSurface);
|
|
1406
|
+
const staleHistory = input.staleBefore !== null && input.bundle.cursor.exportedAt < input.staleBefore;
|
|
1407
|
+
const priorityBucket = staleHistory
|
|
1408
|
+
? "stale_history"
|
|
1409
|
+
: input.lane === "live"
|
|
1410
|
+
? "live"
|
|
1411
|
+
: principalHeavy
|
|
1412
|
+
? "principal_backfill"
|
|
1413
|
+
: "backfill";
|
|
1414
|
+
const priorityScore = roundMetric(feedbackScore +
|
|
1415
|
+
roleScore +
|
|
1416
|
+
authorityScore +
|
|
1417
|
+
priorityClassScore +
|
|
1418
|
+
learningSurface.principalSummary.scopedEventCount +
|
|
1419
|
+
learningSurface.principalSummary.supersedingEventCount * 3 +
|
|
1420
|
+
learningSurface.labelHarvest.humanLabels +
|
|
1421
|
+
recencyScore +
|
|
1422
|
+
(principalHeavy ? 6 : 0));
|
|
1423
|
+
const priorityReasons = [];
|
|
1424
|
+
if (input.lane === "live") {
|
|
1425
|
+
priorityReasons.push("live_tail");
|
|
1426
|
+
}
|
|
1427
|
+
if (principalHeavy) {
|
|
1428
|
+
priorityReasons.push("principal_heavy");
|
|
1429
|
+
}
|
|
1430
|
+
for (const role of learningSurface.principalSummary.teacherRoles) {
|
|
1431
|
+
if (role === "principal" || role === "admin") {
|
|
1432
|
+
priorityReasons.push(`teacher_role=${role}`);
|
|
1433
|
+
}
|
|
1434
|
+
}
|
|
1435
|
+
for (const authority of learningSurface.principalSummary.teacherAuthorities) {
|
|
1436
|
+
if (authority === "binding" || authority === "primary_human" || authority === "high") {
|
|
1437
|
+
priorityReasons.push(`teacher_authority=${authority}`);
|
|
1438
|
+
}
|
|
1439
|
+
}
|
|
1440
|
+
for (const priorityClass of learningSurface.principalSummary.priorityClasses) {
|
|
1441
|
+
if (priorityClass === "critical" || priorityClass === "high") {
|
|
1442
|
+
priorityReasons.push(`priority_class=${priorityClass}`);
|
|
1443
|
+
}
|
|
1444
|
+
}
|
|
1445
|
+
if (learningSurface.principalSummary.supersedingEventCount > 0) {
|
|
1446
|
+
priorityReasons.push(`superseding_events=${learningSurface.principalSummary.supersedingEventCount}`);
|
|
1447
|
+
}
|
|
1448
|
+
if (learningSurface.labelHarvest.humanLabels > 0) {
|
|
1449
|
+
priorityReasons.push(`human_labels=${learningSurface.labelHarvest.humanLabels}`);
|
|
1450
|
+
}
|
|
1451
|
+
if (staleHistory) {
|
|
1452
|
+
priorityReasons.push("stale_history_floor");
|
|
1453
|
+
}
|
|
1454
|
+
return {
|
|
1455
|
+
lane: input.lane,
|
|
1456
|
+
rootDir: input.bundle.descriptor.rootDir,
|
|
1457
|
+
exportDigest: input.bundle.cursor.exportDigest,
|
|
1458
|
+
exportName: input.bundle.cursor.exportName,
|
|
1459
|
+
exportedAt: input.bundle.cursor.exportedAt,
|
|
1460
|
+
eventRange: {
|
|
1461
|
+
start: input.bundle.cursor.eventRange.start,
|
|
1462
|
+
end: input.bundle.cursor.eventRange.end,
|
|
1463
|
+
count: input.bundle.cursor.eventRange.count
|
|
1464
|
+
},
|
|
1465
|
+
priorityBucket,
|
|
1466
|
+
priorityScore,
|
|
1467
|
+
priorityReasons,
|
|
1468
|
+
humanLabelCount: learningSurface.labelHarvest.humanLabels,
|
|
1469
|
+
feedbackCount: input.bundle.descriptor.normalizedEventExport.feedbackEvents.length,
|
|
1470
|
+
teacherRoles: [...learningSurface.principalSummary.teacherRoles],
|
|
1471
|
+
teacherAuthorities: [...learningSurface.principalSummary.teacherAuthorities],
|
|
1472
|
+
priorityClasses: [...learningSurface.principalSummary.priorityClasses],
|
|
1473
|
+
scopedPrincipalEventCount: learningSurface.principalSummary.scopedEventCount,
|
|
1474
|
+
supersedingPrincipalEventCount: learningSurface.principalSummary.supersedingEventCount,
|
|
1475
|
+
staleHistory,
|
|
1476
|
+
ageMsFromLatest
|
|
1477
|
+
};
|
|
1478
|
+
}
|
|
1479
|
+
function compareRuntimeEventExportScannerQueueEntry(left, right) {
|
|
1480
|
+
const bucketOrder = {
|
|
1481
|
+
live: 0,
|
|
1482
|
+
principal_backfill: 1,
|
|
1483
|
+
backfill: 2,
|
|
1484
|
+
stale_history: 3
|
|
1485
|
+
};
|
|
1486
|
+
if (bucketOrder[left.priorityBucket] !== bucketOrder[right.priorityBucket]) {
|
|
1487
|
+
return bucketOrder[left.priorityBucket] - bucketOrder[right.priorityBucket];
|
|
1488
|
+
}
|
|
1489
|
+
if (right.priorityScore !== left.priorityScore) {
|
|
1490
|
+
return right.priorityScore - left.priorityScore;
|
|
1491
|
+
}
|
|
1492
|
+
if (right.eventRange.end !== left.eventRange.end) {
|
|
1493
|
+
return right.eventRange.end - left.eventRange.end;
|
|
1494
|
+
}
|
|
1495
|
+
if (right.eventRange.start !== left.eventRange.start) {
|
|
1496
|
+
return right.eventRange.start - left.eventRange.start;
|
|
1497
|
+
}
|
|
1498
|
+
if (right.exportedAt !== left.exportedAt) {
|
|
1499
|
+
return right.exportedAt.localeCompare(left.exportedAt);
|
|
1500
|
+
}
|
|
1501
|
+
return right.exportDigest.localeCompare(left.exportDigest);
|
|
1502
|
+
}
|
|
1503
|
+
function listRuntimeEventExportBundleRoots(scanRoot) {
|
|
1504
|
+
if (!existsSync(scanRoot)) {
|
|
1505
|
+
return [];
|
|
1506
|
+
}
|
|
1507
|
+
return readdirSync(scanRoot, { withFileTypes: true })
|
|
1508
|
+
.filter((entry) => entry.isDirectory())
|
|
1509
|
+
.map((entry) => path.join(scanRoot, entry.name))
|
|
1510
|
+
.sort((left, right) => left.localeCompare(right));
|
|
1511
|
+
}
|
|
1512
|
+
function discoverRuntimeEventExportBundles(scanRoot) {
|
|
1513
|
+
const loaded = [];
|
|
1514
|
+
const invalidBundles = [];
|
|
1515
|
+
for (const rootDir of listRuntimeEventExportBundleRoots(scanRoot)) {
|
|
1516
|
+
try {
|
|
1517
|
+
const descriptor = loadRuntimeEventExportBundle(rootDir);
|
|
1518
|
+
loaded.push({
|
|
1519
|
+
descriptor,
|
|
1520
|
+
cursor: buildRuntimeEventExportScannerBundleCursor(descriptor)
|
|
1521
|
+
});
|
|
1522
|
+
}
|
|
1523
|
+
catch (error) {
|
|
1524
|
+
invalidBundles.push({
|
|
1525
|
+
rootDir,
|
|
1526
|
+
error: toErrorMessage(error)
|
|
1527
|
+
});
|
|
1528
|
+
}
|
|
1529
|
+
}
|
|
1530
|
+
loaded.sort((left, right) => compareRuntimeEventExportScannerBundleCursor(left.cursor, right.cursor));
|
|
1531
|
+
const deduped = [];
|
|
1532
|
+
const seenDigests = new Set();
|
|
1533
|
+
const duplicateExportDigests = [];
|
|
1534
|
+
for (const bundle of loaded) {
|
|
1535
|
+
if (seenDigests.has(bundle.cursor.exportDigest)) {
|
|
1536
|
+
duplicateExportDigests.push(bundle.cursor.exportDigest);
|
|
1537
|
+
continue;
|
|
1538
|
+
}
|
|
1539
|
+
seenDigests.add(bundle.cursor.exportDigest);
|
|
1540
|
+
deduped.push(bundle);
|
|
1541
|
+
}
|
|
1542
|
+
return {
|
|
1543
|
+
bundles: deduped,
|
|
1544
|
+
invalidBundles,
|
|
1545
|
+
duplicateExportDigests: [...new Set(duplicateExportDigests)]
|
|
1546
|
+
};
|
|
1547
|
+
}
|
|
1548
|
+
function delayRuntimeEventExportScannerPoll(ms) {
|
|
1549
|
+
return new Promise((resolve) => {
|
|
1550
|
+
setTimeout(resolve, ms);
|
|
1551
|
+
});
|
|
1552
|
+
}
|
|
1553
|
+
export class RuntimeEventExportScanner {
|
|
1554
|
+
scanRoot;
|
|
1555
|
+
checkpointPath;
|
|
1556
|
+
liveTailBundles;
|
|
1557
|
+
backfillBundlesPerPass;
|
|
1558
|
+
staleHistoryMs;
|
|
1559
|
+
checkpoint;
|
|
1560
|
+
constructor(input) {
|
|
1561
|
+
this.scanRoot = path.resolve(normalizeNonEmptyString(input.scanRoot, "scanRoot"));
|
|
1562
|
+
this.checkpointPath = path.resolve(input.checkpointPath ?? path.join(this.scanRoot, DEFAULT_RUNTIME_EVENT_EXPORT_SCANNER_CHECKPOINT_BASENAME));
|
|
1563
|
+
this.liveTailBundles = normalizePositiveInteger(input.liveTailBundles, "liveTailBundles", DEFAULT_RUNTIME_EVENT_EXPORT_SCANNER_LIVE_TAIL_BUNDLES);
|
|
1564
|
+
this.backfillBundlesPerPass = normalizePositiveInteger(input.backfillBundlesPerPass, "backfillBundlesPerPass", DEFAULT_RUNTIME_EVENT_EXPORT_SCANNER_BACKFILL_BUNDLES_PER_PASS);
|
|
1565
|
+
this.staleHistoryMs = normalizeNonNegativeDurationMs(input.staleHistoryMs, "staleHistoryMs", DEFAULT_RUNTIME_EVENT_EXPORT_SCANNER_STALE_HISTORY_MS);
|
|
1566
|
+
this.checkpoint = existsSync(this.checkpointPath)
|
|
1567
|
+
? loadRuntimeEventExportScannerCheckpoint(this.checkpointPath)
|
|
1568
|
+
: createRuntimeEventExportScannerCheckpoint({ scanRoot: this.scanRoot });
|
|
1569
|
+
if (this.checkpoint.scanRoot !== this.scanRoot) {
|
|
1570
|
+
throw new Error(`runtime event export scanner checkpoint scanRoot mismatch: checkpoint=${this.checkpoint.scanRoot} scanner=${this.scanRoot}`);
|
|
1571
|
+
}
|
|
1572
|
+
}
|
|
1573
|
+
snapshot() {
|
|
1574
|
+
return structuredClone(this.checkpoint);
|
|
1575
|
+
}
|
|
1576
|
+
scanOnce(options = {}) {
|
|
1577
|
+
const scannedAt = normalizeIsoTimestamp(options.scannedAt, "scannedAt", new Date().toISOString());
|
|
1578
|
+
const discovered = discoverRuntimeEventExportBundles(this.scanRoot);
|
|
1579
|
+
const processedDigests = new Set(this.checkpoint.processedExportDigests);
|
|
1580
|
+
const latestExportedAt = discovered.bundles[discovered.bundles.length - 1]?.cursor.exportedAt ?? null;
|
|
1581
|
+
const staleBefore = latestExportedAt === null ? null : new Date(Date.parse(latestExportedAt) - this.staleHistoryMs).toISOString();
|
|
1582
|
+
const staleSkipped = new Set();
|
|
1583
|
+
const isFreshEnough = (bundle) => {
|
|
1584
|
+
if (staleBefore === null) {
|
|
1585
|
+
return true;
|
|
1586
|
+
}
|
|
1587
|
+
return bundle.cursor.exportedAt >= staleBefore;
|
|
1588
|
+
};
|
|
1589
|
+
const live = this.checkpoint.live.after === null
|
|
1590
|
+
? discovered.bundles.filter((bundle) => !processedDigests.has(bundle.cursor.exportDigest) && isFreshEnough(bundle)).slice(-this.liveTailBundles)
|
|
1591
|
+
: discovered.bundles
|
|
1592
|
+
.filter((bundle) => !processedDigests.has(bundle.cursor.exportDigest) &&
|
|
1593
|
+
compareRuntimeEventExportScannerBundleCursor(bundle.cursor, this.checkpoint.live.after) > 0 &&
|
|
1594
|
+
isFreshEnough(bundle))
|
|
1595
|
+
.slice(0, this.liveTailBundles);
|
|
1596
|
+
const liveQueue = live.map((bundle) => buildRuntimeEventExportScannerQueueEntry({
|
|
1597
|
+
lane: "live",
|
|
1598
|
+
bundle,
|
|
1599
|
+
latestExportedAt,
|
|
1600
|
+
staleBefore
|
|
1601
|
+
}));
|
|
1602
|
+
const nextLiveAfter = live.length > 0 ? live[live.length - 1]?.cursor ?? null : this.checkpoint.live.after;
|
|
1603
|
+
const backfillFrontier = nextLiveAfter;
|
|
1604
|
+
const liveDigests = new Set(live.map((bundle) => bundle.cursor.exportDigest));
|
|
1605
|
+
const prioritizedBackfillCandidates = [];
|
|
1606
|
+
const staleHistoryQueue = [];
|
|
1607
|
+
for (const bundle of discovered.bundles) {
|
|
1608
|
+
if (processedDigests.has(bundle.cursor.exportDigest) || liveDigests.has(bundle.cursor.exportDigest)) {
|
|
1609
|
+
continue;
|
|
1610
|
+
}
|
|
1611
|
+
const queueEntry = buildRuntimeEventExportScannerQueueEntry({
|
|
1612
|
+
lane: "backfill",
|
|
1613
|
+
bundle,
|
|
1614
|
+
latestExportedAt,
|
|
1615
|
+
staleBefore
|
|
1616
|
+
});
|
|
1617
|
+
if (queueEntry.staleHistory) {
|
|
1618
|
+
staleHistoryQueue.push(queueEntry);
|
|
1619
|
+
staleSkipped.add(bundle.cursor.exportDigest);
|
|
1620
|
+
processedDigests.add(bundle.cursor.exportDigest);
|
|
1621
|
+
continue;
|
|
1622
|
+
}
|
|
1623
|
+
if (backfillFrontier !== null &&
|
|
1624
|
+
compareRuntimeEventExportScannerBundleCursor(bundle.cursor, backfillFrontier) < 0) {
|
|
1625
|
+
prioritizedBackfillCandidates.push({
|
|
1626
|
+
bundle,
|
|
1627
|
+
queueEntry
|
|
1628
|
+
});
|
|
1629
|
+
}
|
|
1630
|
+
}
|
|
1631
|
+
prioritizedBackfillCandidates.sort((left, right) => compareRuntimeEventExportScannerQueueEntry(left.queueEntry, right.queueEntry));
|
|
1632
|
+
const selectedBackfillCandidates = prioritizedBackfillCandidates.slice(0, this.backfillBundlesPerPass);
|
|
1633
|
+
const backfill = selectedBackfillCandidates.map((candidate) => candidate.bundle);
|
|
1634
|
+
for (const bundle of [...live, ...backfill]) {
|
|
1635
|
+
processedDigests.add(bundle.cursor.exportDigest);
|
|
1636
|
+
}
|
|
1637
|
+
const backfillQueue = prioritizedBackfillCandidates.map((candidate) => candidate.queueEntry);
|
|
1638
|
+
const selectedLive = liveQueue.map((queueEntry, index) => buildRuntimeEventExportScannerHit(live[index], queueEntry));
|
|
1639
|
+
const selectedBackfill = selectedBackfillCandidates.map((candidate) => buildRuntimeEventExportScannerHit(candidate.bundle, candidate.queueEntry));
|
|
1640
|
+
const nextBackfillBefore = backfill.length > 0
|
|
1641
|
+
? [...backfill].sort((left, right) => compareRuntimeEventExportScannerBundleCursor(left.cursor, right.cursor))[0]?.cursor ?? null
|
|
1642
|
+
: this.checkpoint.backfill.before;
|
|
1643
|
+
const exhausted = backfillFrontier === null
|
|
1644
|
+
? true
|
|
1645
|
+
: !prioritizedBackfillCandidates.some((candidate) => !processedDigests.has(candidate.bundle.cursor.exportDigest));
|
|
1646
|
+
this.checkpoint = {
|
|
1647
|
+
contract: RUNTIME_EVENT_EXPORT_SCANNER_CHECKPOINT_CONTRACT,
|
|
1648
|
+
runtimeOwner: "openclaw",
|
|
1649
|
+
scanRoot: this.scanRoot,
|
|
1650
|
+
updatedAt: scannedAt,
|
|
1651
|
+
live: {
|
|
1652
|
+
after: nextLiveAfter === null ? null : { ...nextLiveAfter, eventRange: { ...nextLiveAfter.eventRange } }
|
|
1653
|
+
},
|
|
1654
|
+
backfill: {
|
|
1655
|
+
before: nextBackfillBefore === null ? null : { ...nextBackfillBefore, eventRange: { ...nextBackfillBefore.eventRange } },
|
|
1656
|
+
exhausted,
|
|
1657
|
+
staleBefore
|
|
1658
|
+
},
|
|
1659
|
+
processedExportDigests: [...processedDigests],
|
|
1660
|
+
stats: {
|
|
1661
|
+
scanPasses: this.checkpoint.stats.scanPasses + 1,
|
|
1662
|
+
liveBundlesScanned: this.checkpoint.stats.liveBundlesScanned + selectedLive.length,
|
|
1663
|
+
backfillBundlesScanned: this.checkpoint.stats.backfillBundlesScanned + selectedBackfill.length,
|
|
1664
|
+
duplicateBundlesSkipped: this.checkpoint.stats.duplicateBundlesSkipped + discovered.duplicateExportDigests.length,
|
|
1665
|
+
staleBundlesSkipped: this.checkpoint.stats.staleBundlesSkipped + staleSkipped.size,
|
|
1666
|
+
invalidBundlesSkipped: this.checkpoint.stats.invalidBundlesSkipped + discovered.invalidBundles.length
|
|
1667
|
+
}
|
|
1668
|
+
};
|
|
1669
|
+
writeRuntimeEventExportScannerCheckpoint(this.checkpointPath, this.checkpoint);
|
|
1670
|
+
return {
|
|
1671
|
+
runtimeOwner: "openclaw",
|
|
1672
|
+
scanRoot: this.scanRoot,
|
|
1673
|
+
checkpointPath: this.checkpointPath,
|
|
1674
|
+
scannedAt,
|
|
1675
|
+
live: selectedLive,
|
|
1676
|
+
backfill: selectedBackfill,
|
|
1677
|
+
selected: [...selectedLive, ...selectedBackfill],
|
|
1678
|
+
queue: {
|
|
1679
|
+
ageFloor: {
|
|
1680
|
+
newestExportedAt: latestExportedAt,
|
|
1681
|
+
staleBefore,
|
|
1682
|
+
staleHistoryMs: this.staleHistoryMs
|
|
1683
|
+
},
|
|
1684
|
+
live: liveQueue,
|
|
1685
|
+
backfill: backfillQueue,
|
|
1686
|
+
staleHistory: staleHistoryQueue.sort(compareRuntimeEventExportScannerQueueEntry)
|
|
1687
|
+
},
|
|
1688
|
+
duplicateExportDigests: discovered.duplicateExportDigests,
|
|
1689
|
+
staleSkippedExportDigests: [...staleSkipped],
|
|
1690
|
+
invalidBundles: discovered.invalidBundles,
|
|
1691
|
+
idle: selectedLive.length === 0 && selectedBackfill.length === 0,
|
|
1692
|
+
checkpoint: this.snapshot()
|
|
1693
|
+
};
|
|
1694
|
+
}
|
|
1695
|
+
async runLoop(options = {}) {
|
|
1696
|
+
const pollIntervalMs = normalizeNonNegativeDurationMs(options.pollIntervalMs, "pollIntervalMs", 0);
|
|
1697
|
+
const maxPasses = normalizePositiveInteger(options.maxPasses, "maxPasses", 1);
|
|
1698
|
+
const stopWhenIdle = options.stopWhenIdle !== false;
|
|
1699
|
+
let passCount = 0;
|
|
1700
|
+
let liveBundlesScanned = 0;
|
|
1701
|
+
let backfillBundlesScanned = 0;
|
|
1702
|
+
let lastScan = null;
|
|
1703
|
+
let stoppedReason = "max_passes";
|
|
1704
|
+
while (passCount < maxPasses) {
|
|
1705
|
+
if (options.signal?.aborted) {
|
|
1706
|
+
stoppedReason = "aborted";
|
|
1707
|
+
break;
|
|
1708
|
+
}
|
|
1709
|
+
lastScan = this.scanOnce();
|
|
1710
|
+
passCount += 1;
|
|
1711
|
+
liveBundlesScanned += lastScan.live.length;
|
|
1712
|
+
backfillBundlesScanned += lastScan.backfill.length;
|
|
1713
|
+
if (options.onPass !== undefined) {
|
|
1714
|
+
await options.onPass(lastScan);
|
|
1715
|
+
}
|
|
1716
|
+
if (stopWhenIdle && lastScan.idle) {
|
|
1717
|
+
stoppedReason = "idle";
|
|
1718
|
+
break;
|
|
1719
|
+
}
|
|
1720
|
+
if (passCount >= maxPasses) {
|
|
1721
|
+
stoppedReason = "max_passes";
|
|
1722
|
+
break;
|
|
1723
|
+
}
|
|
1724
|
+
if (pollIntervalMs > 0) {
|
|
1725
|
+
await delayRuntimeEventExportScannerPoll(pollIntervalMs);
|
|
1726
|
+
}
|
|
1727
|
+
}
|
|
1728
|
+
return {
|
|
1729
|
+
runtimeOwner: "openclaw",
|
|
1730
|
+
passCount,
|
|
1731
|
+
liveBundlesScanned,
|
|
1732
|
+
backfillBundlesScanned,
|
|
1733
|
+
stoppedReason,
|
|
1734
|
+
lastScan,
|
|
1735
|
+
checkpoint: this.snapshot()
|
|
1736
|
+
};
|
|
1737
|
+
}
|
|
1738
|
+
}
|
|
1739
|
+
export function createRuntimeEventExportScanner(input) {
|
|
1740
|
+
return new RuntimeEventExportScanner(input);
|
|
1741
|
+
}
|
|
776
1742
|
function normalizeNonEmptyString(value, fieldName) {
|
|
777
1743
|
if (typeof value !== "string" || value.trim().length === 0) {
|
|
778
1744
|
throw new Error(`${fieldName} is required`);
|
|
@@ -836,23 +1802,134 @@ function isFeedbackKind(value) {
|
|
|
836
1802
|
function isPresent(value) {
|
|
837
1803
|
return value !== null;
|
|
838
1804
|
}
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
1805
|
+
const KNOWN_SCANNER_REALITY_PRINCIPALS = {
|
|
1806
|
+
bihua: {
|
|
1807
|
+
teacherIdentity: "bihua",
|
|
1808
|
+
teacherRole: "principal",
|
|
1809
|
+
teacherAuthority: "binding",
|
|
1810
|
+
priorityClass: "critical"
|
|
1811
|
+
},
|
|
1812
|
+
jonathan: {
|
|
1813
|
+
teacherIdentity: "jonathan",
|
|
1814
|
+
teacherRole: "admin",
|
|
1815
|
+
teacherAuthority: "high",
|
|
1816
|
+
priorityClass: "high"
|
|
1817
|
+
},
|
|
1818
|
+
"jonathan gu": {
|
|
1819
|
+
teacherIdentity: "jonathan",
|
|
1820
|
+
teacherRole: "admin",
|
|
1821
|
+
teacherAuthority: "high",
|
|
1822
|
+
priorityClass: "high"
|
|
843
1823
|
}
|
|
844
|
-
|
|
845
|
-
|
|
1824
|
+
};
|
|
1825
|
+
function normalizePrincipalPriorityHint(value, fieldName) {
|
|
1826
|
+
if (value === undefined || value === null) {
|
|
1827
|
+
return undefined;
|
|
846
1828
|
}
|
|
847
|
-
if (
|
|
848
|
-
return
|
|
1829
|
+
if (value === "critical" || value === "high" || value === "normal" || value === "low") {
|
|
1830
|
+
return value;
|
|
849
1831
|
}
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
1832
|
+
throw new Error(`${fieldName} must be critical, high, normal, or low`);
|
|
1833
|
+
}
|
|
1834
|
+
function normalizePrincipalActorName(value) {
|
|
1835
|
+
const normalized = normalizeOptionalString(value);
|
|
1836
|
+
return normalized === undefined ? undefined : normalized.toLowerCase().replace(/\s+/gu, " ");
|
|
1837
|
+
}
|
|
1838
|
+
function slugifyPrincipalIdentityFragment(value) {
|
|
1839
|
+
return value
|
|
1840
|
+
.toLowerCase()
|
|
1841
|
+
.replace(/[^a-z0-9]+/gu, "-")
|
|
1842
|
+
.replace(/^-+|-+$/gu, "");
|
|
1843
|
+
}
|
|
1844
|
+
function buildRuntimePrincipalScope(input) {
|
|
1845
|
+
const scopeKey = [
|
|
1846
|
+
`profile:${input.profileSelector}`,
|
|
1847
|
+
input.profileId === undefined ? null : `profile_id:${input.profileId}`,
|
|
1848
|
+
`session:${input.sessionId}`,
|
|
1849
|
+
input.userId === undefined ? null : `user:${input.userId}`,
|
|
1850
|
+
input.interactionId === undefined ? null : `interaction:${input.interactionId}`,
|
|
1851
|
+
input.messageId === undefined ? null : `message:${input.messageId}`
|
|
1852
|
+
]
|
|
1853
|
+
.filter(isPresent)
|
|
1854
|
+
.join("|");
|
|
1855
|
+
if (input.messageId !== undefined) {
|
|
1856
|
+
return {
|
|
1857
|
+
kind: "message",
|
|
1858
|
+
profileSelector: input.profileSelector,
|
|
1859
|
+
sessionId: input.sessionId,
|
|
1860
|
+
...(input.interactionId === undefined ? {} : { interactionId: input.interactionId }),
|
|
1861
|
+
messageId: input.messageId,
|
|
1862
|
+
scopeKey
|
|
1863
|
+
};
|
|
1864
|
+
}
|
|
1865
|
+
if (input.interactionId !== undefined) {
|
|
1866
|
+
return {
|
|
1867
|
+
kind: "interaction",
|
|
1868
|
+
profileSelector: input.profileSelector,
|
|
1869
|
+
sessionId: input.sessionId,
|
|
1870
|
+
interactionId: input.interactionId,
|
|
1871
|
+
scopeKey
|
|
1872
|
+
};
|
|
1873
|
+
}
|
|
1874
|
+
return {
|
|
1875
|
+
kind: "session",
|
|
1876
|
+
profileSelector: input.profileSelector,
|
|
1877
|
+
sessionId: input.sessionId,
|
|
1878
|
+
scopeKey
|
|
1879
|
+
};
|
|
1880
|
+
}
|
|
1881
|
+
function buildRuntimeSelfPrincipal(turn) {
|
|
1882
|
+
const profileSelector = normalizeRuntimeProfileSelector(turn.profileSelector, "profileSelector");
|
|
1883
|
+
const profileId = normalizeOptionalString(turn.profileId);
|
|
1884
|
+
const userId = normalizeOptionalString(turn.userId);
|
|
1885
|
+
return {
|
|
1886
|
+
teacherIdentity: "openclaw/self",
|
|
1887
|
+
teacherRole: "assistant",
|
|
1888
|
+
teacherAuthority: "background",
|
|
1889
|
+
priorityClass: "low",
|
|
1890
|
+
principalScope: buildRuntimePrincipalScope({
|
|
1891
|
+
profileSelector,
|
|
1892
|
+
...(profileId === undefined ? {} : { profileId }),
|
|
1893
|
+
sessionId: turn.sessionId,
|
|
1894
|
+
...(userId === undefined ? {} : { userId })
|
|
1895
|
+
})
|
|
1896
|
+
};
|
|
1897
|
+
}
|
|
1898
|
+
function resolveRuntimeFeedbackPrincipal(input) {
|
|
1899
|
+
const actorName = normalizePrincipalActorName(input.feedback.actorName);
|
|
1900
|
+
const knownPrincipal = actorName === undefined ? undefined : KNOWN_SCANNER_REALITY_PRINCIPALS[actorName];
|
|
1901
|
+
const profileSelector = normalizeRuntimeProfileSelector(input.turn.profileSelector, "profileSelector");
|
|
1902
|
+
const profileId = normalizeOptionalString(input.turn.profileId);
|
|
1903
|
+
const userId = normalizeOptionalString(input.turn.userId);
|
|
1904
|
+
const priorityHint = normalizePrincipalPriorityHint(input.feedback.priorityHint, "feedback.priorityHint");
|
|
1905
|
+
const userIdentityFragment = userId === undefined ? undefined : slugifyPrincipalIdentityFragment(userId);
|
|
1906
|
+
const actorIdentityFragment = actorName === undefined ? undefined : slugifyPrincipalIdentityFragment(actorName);
|
|
1907
|
+
const teacherIdentity = knownPrincipal?.teacherIdentity ??
|
|
1908
|
+
(userIdentityFragment === undefined || userIdentityFragment.length === 0
|
|
1909
|
+
? actorIdentityFragment === undefined || actorIdentityFragment.length === 0
|
|
1910
|
+
? undefined
|
|
1911
|
+
: `scanner/actor/${actorIdentityFragment}`
|
|
1912
|
+
: `scanner/user/${userIdentityFragment}`);
|
|
1913
|
+
if (teacherIdentity === undefined) {
|
|
1914
|
+
return undefined;
|
|
854
1915
|
}
|
|
855
|
-
return
|
|
1916
|
+
return {
|
|
1917
|
+
teacherIdentity,
|
|
1918
|
+
teacherRole: knownPrincipal?.teacherRole ?? "user",
|
|
1919
|
+
teacherAuthority: knownPrincipal?.teacherAuthority ?? "normal",
|
|
1920
|
+
priorityClass: priorityHint ?? knownPrincipal?.priorityClass ?? "normal",
|
|
1921
|
+
principalScope: buildRuntimePrincipalScope({
|
|
1922
|
+
profileSelector,
|
|
1923
|
+
...(profileId === undefined ? {} : { profileId }),
|
|
1924
|
+
sessionId: input.turn.sessionId,
|
|
1925
|
+
...(userId === undefined ? {} : { userId }),
|
|
1926
|
+
...(input.relatedInteractionId === undefined ? {} : { interactionId: input.relatedInteractionId }),
|
|
1927
|
+
...(input.messageId === undefined ? {} : { messageId: input.messageId })
|
|
1928
|
+
})
|
|
1929
|
+
};
|
|
1930
|
+
}
|
|
1931
|
+
export function classifyFeedbackKind(content) {
|
|
1932
|
+
return classifyFeedbackSignalContent(content)?.kind ?? "teaching";
|
|
856
1933
|
}
|
|
857
1934
|
export function formatPromptContext(compileResponse) {
|
|
858
1935
|
const lines = [
|
|
@@ -913,6 +1990,12 @@ function classifyCompileFailure(error, activationRoot) {
|
|
|
913
1990
|
function uniqueNotes(notes) {
|
|
914
1991
|
return [...new Set(notes.filter((note) => note.length > 0))];
|
|
915
1992
|
}
|
|
1993
|
+
function roundMetric(value) {
|
|
1994
|
+
return Math.round(value * 100) / 100;
|
|
1995
|
+
}
|
|
1996
|
+
function clamp(value, min, max) {
|
|
1997
|
+
return Math.min(max, Math.max(min, value));
|
|
1998
|
+
}
|
|
916
1999
|
function clampInteger(value, minimum, maximum) {
|
|
917
2000
|
return Math.min(maximum, Math.max(minimum, Math.round(value)));
|
|
918
2001
|
}
|
|
@@ -1192,6 +2275,97 @@ function readDiagnosticNoteList(notes, prefix) {
|
|
|
1192
2275
|
.map((entry) => entry.trim())
|
|
1193
2276
|
.filter((entry) => entry.length > 0);
|
|
1194
2277
|
}
|
|
2278
|
+
function parseDiagnosticInteger(value) {
|
|
2279
|
+
if (value === null) {
|
|
2280
|
+
return null;
|
|
2281
|
+
}
|
|
2282
|
+
const parsed = Number.parseInt(value, 10);
|
|
2283
|
+
return Number.isInteger(parsed) ? parsed : null;
|
|
2284
|
+
}
|
|
2285
|
+
function parseStructuralBudgetStrategy(value) {
|
|
2286
|
+
return value === "fixed_v1" || value === "empirical_v1" ? value : null;
|
|
2287
|
+
}
|
|
2288
|
+
function summarizeStructuralDecisionFromNotes(notes) {
|
|
2289
|
+
const requestedBudgetStrategy = parseStructuralBudgetStrategy(readDiagnosticNoteValue(notes, "requested_budget_strategy="));
|
|
2290
|
+
const resolvedBudgetStrategy = parseStructuralBudgetStrategy(readDiagnosticNoteValue(notes, "resolved_budget_strategy="));
|
|
2291
|
+
const resolvedMaxContextBlocks = parseDiagnosticInteger(readDiagnosticNoteValue(notes, "resolved_max_context_blocks="));
|
|
2292
|
+
const requestedMaxContextBlocks = parseDiagnosticInteger(readDiagnosticNoteValue(notes, "requested_max_context_blocks="));
|
|
2293
|
+
const structuralBudgetSource = readDiagnosticNoteValue(notes, "structural_budget_source=");
|
|
2294
|
+
switch (structuralBudgetSource) {
|
|
2295
|
+
case "caller_override":
|
|
2296
|
+
return {
|
|
2297
|
+
origin: "manual_caller_shape",
|
|
2298
|
+
basis: "caller_override",
|
|
2299
|
+
requestedBudgetStrategy,
|
|
2300
|
+
resolvedBudgetStrategy,
|
|
2301
|
+
resolvedMaxContextBlocks,
|
|
2302
|
+
detail: `manual caller shaping fixed the serve-path structural budget at ${requestedMaxContextBlocks ?? resolvedMaxContextBlocks ?? "unknown"} blocks; empirical and default-path control were bypassed`
|
|
2303
|
+
};
|
|
2304
|
+
case "compile_structural_signals_empirical_v1":
|
|
2305
|
+
return {
|
|
2306
|
+
origin: "empirical_control",
|
|
2307
|
+
basis: "compile_structural_signals",
|
|
2308
|
+
requestedBudgetStrategy,
|
|
2309
|
+
resolvedBudgetStrategy,
|
|
2310
|
+
resolvedMaxContextBlocks,
|
|
2311
|
+
detail: `empirical control resolved the serve-path structural budget to ${resolvedMaxContextBlocks ?? "unknown"} blocks from prior compile structural signals; no manual caller shaping was applied`
|
|
2312
|
+
};
|
|
2313
|
+
case "graph_evolution_empirical_v1":
|
|
2314
|
+
return {
|
|
2315
|
+
origin: "empirical_control",
|
|
2316
|
+
basis: "graph_evolution",
|
|
2317
|
+
requestedBudgetStrategy,
|
|
2318
|
+
resolvedBudgetStrategy,
|
|
2319
|
+
resolvedMaxContextBlocks,
|
|
2320
|
+
detail: `empirical control resolved the serve-path structural budget to ${resolvedMaxContextBlocks ?? "unknown"} blocks from active-pack graph evolution evidence; no manual caller shaping was applied`
|
|
2321
|
+
};
|
|
2322
|
+
case "fixed_default":
|
|
2323
|
+
return {
|
|
2324
|
+
origin: "default_path_control",
|
|
2325
|
+
basis: "fixed_default",
|
|
2326
|
+
requestedBudgetStrategy,
|
|
2327
|
+
resolvedBudgetStrategy,
|
|
2328
|
+
resolvedMaxContextBlocks,
|
|
2329
|
+
detail: `default-path fixed-budget control set the serve-path structural budget to ${resolvedMaxContextBlocks ?? "unknown"} blocks because empirical control was not requested`
|
|
2330
|
+
};
|
|
2331
|
+
case "fixed_fallback":
|
|
2332
|
+
return {
|
|
2333
|
+
origin: "default_path_control",
|
|
2334
|
+
basis: "fixed_fallback",
|
|
2335
|
+
requestedBudgetStrategy,
|
|
2336
|
+
resolvedBudgetStrategy,
|
|
2337
|
+
resolvedMaxContextBlocks,
|
|
2338
|
+
detail: `default-path fixed-budget fallback set the serve-path structural budget to ${resolvedMaxContextBlocks ?? "unknown"} blocks; no manual caller shaping was applied`
|
|
2339
|
+
};
|
|
2340
|
+
case "no_evidence_fallback":
|
|
2341
|
+
return {
|
|
2342
|
+
origin: "default_path_control",
|
|
2343
|
+
basis: "no_evidence_fallback",
|
|
2344
|
+
requestedBudgetStrategy,
|
|
2345
|
+
resolvedBudgetStrategy,
|
|
2346
|
+
resolvedMaxContextBlocks,
|
|
2347
|
+
detail: `default-path fixed-budget fallback kept the serve-path structural budget at ${resolvedMaxContextBlocks ?? "unknown"} blocks because graph-evolution evidence was absent`
|
|
2348
|
+
};
|
|
2349
|
+
case "no_compile_signal_evidence_fallback":
|
|
2350
|
+
return {
|
|
2351
|
+
origin: "default_path_control",
|
|
2352
|
+
basis: "no_compile_signal_evidence_fallback",
|
|
2353
|
+
requestedBudgetStrategy,
|
|
2354
|
+
resolvedBudgetStrategy,
|
|
2355
|
+
resolvedMaxContextBlocks,
|
|
2356
|
+
detail: `default-path fixed-budget fallback kept the serve-path structural budget at ${resolvedMaxContextBlocks ?? "unknown"} blocks because compile-signal evidence was absent`
|
|
2357
|
+
};
|
|
2358
|
+
default:
|
|
2359
|
+
return {
|
|
2360
|
+
origin: "unknown",
|
|
2361
|
+
basis: "unknown",
|
|
2362
|
+
requestedBudgetStrategy,
|
|
2363
|
+
resolvedBudgetStrategy,
|
|
2364
|
+
resolvedMaxContextBlocks,
|
|
2365
|
+
detail: "structural decision attribution is unavailable from the compile notes"
|
|
2366
|
+
};
|
|
2367
|
+
}
|
|
2368
|
+
}
|
|
1195
2369
|
function sortedUniqueStrings(values) {
|
|
1196
2370
|
return [...new Set(values.filter((value) => value.length > 0))].sort((left, right) => left.localeCompare(right));
|
|
1197
2371
|
}
|
|
@@ -1527,11 +2701,14 @@ export function bootstrapRuntimeAttach(input) {
|
|
|
1527
2701
|
});
|
|
1528
2702
|
const currentProfile = describeCurrentProfileBrainStatus({
|
|
1529
2703
|
activationRoot,
|
|
1530
|
-
updatedAt: activatedAt
|
|
2704
|
+
updatedAt: activatedAt,
|
|
2705
|
+
...(input.brainAttachmentPolicy !== undefined ? { brainAttachmentPolicy: input.brainAttachmentPolicy } : {}),
|
|
2706
|
+
...(input.profileId !== undefined ? { profileId: input.profileId } : {})
|
|
1531
2707
|
});
|
|
1532
2708
|
return {
|
|
1533
2709
|
runtimeOwner: "openclaw",
|
|
1534
2710
|
profileSelector,
|
|
2711
|
+
operatorReadScope: "current_profile_only",
|
|
1535
2712
|
activationRoot,
|
|
1536
2713
|
packRoot,
|
|
1537
2714
|
packId: descriptor.manifest.packId,
|
|
@@ -1540,12 +2717,80 @@ export function bootstrapRuntimeAttach(input) {
|
|
|
1540
2717
|
currentProfile,
|
|
1541
2718
|
nextSteps: buildBootstrapRuntimeAttachNextSteps({
|
|
1542
2719
|
activationRoot,
|
|
2720
|
+
profileSelector,
|
|
1543
2721
|
currentProfile
|
|
1544
2722
|
})
|
|
1545
2723
|
};
|
|
1546
2724
|
}
|
|
2725
|
+
function normalizeFingerprintEntries(values) {
|
|
2726
|
+
return uniqueStringsInOrder((values ?? [])
|
|
2727
|
+
.map((value) => normalizeOptionalString(value))
|
|
2728
|
+
.filter((value) => value !== undefined));
|
|
2729
|
+
}
|
|
2730
|
+
function buildRuntimeContextFingerprint(input) {
|
|
2731
|
+
const promptContextFingerprints = normalizeFingerprintEntries(input.turn.contextFingerprint?.promptContextFingerprints);
|
|
2732
|
+
const workspaceInjectionSurfaceDigest = input.turn.contextFingerprint?.workspaceInjectionSurface === undefined ||
|
|
2733
|
+
input.turn.contextFingerprint?.workspaceInjectionSurface === null
|
|
2734
|
+
? null
|
|
2735
|
+
: checksumJsonPayload(input.turn.contextFingerprint.workspaceInjectionSurface);
|
|
2736
|
+
const promptContextDigest = promptContextFingerprints.length === 0 && workspaceInjectionSurfaceDigest === null
|
|
2737
|
+
? null
|
|
2738
|
+
: checksumJsonPayload({
|
|
2739
|
+
promptContextFingerprints,
|
|
2740
|
+
workspaceInjectionSurfaceDigest
|
|
2741
|
+
});
|
|
2742
|
+
const runtimeHints = normalizeFingerprintEntries(input.turn.runtimeHints);
|
|
2743
|
+
const runtimeHintsDigest = runtimeHints.length === 0 ? null : checksumJsonPayload(runtimeHints);
|
|
2744
|
+
const profileId = normalizeOptionalString(input.turn.profileId);
|
|
2745
|
+
const profileLineage = uniqueStringsInOrder([
|
|
2746
|
+
"host:openclaw",
|
|
2747
|
+
`profile:${input.profileSelector}`,
|
|
2748
|
+
profileId === undefined ? undefined : `profile_id:${profileId}`,
|
|
2749
|
+
`attachment_policy:${input.brainAttachmentPolicy}`,
|
|
2750
|
+
...normalizeFingerprintEntries(input.turn.contextFingerprint?.profileLineage)
|
|
2751
|
+
].filter((value) => value !== undefined));
|
|
2752
|
+
const sessionLineage = uniqueStringsInOrder([
|
|
2753
|
+
`session:${input.turn.sessionId}`,
|
|
2754
|
+
`channel:${input.turn.channel}`,
|
|
2755
|
+
`source_stream:${input.sourceStream}`,
|
|
2756
|
+
...normalizeFingerprintEntries(input.turn.contextFingerprint?.sessionLineage)
|
|
2757
|
+
]);
|
|
2758
|
+
const brainLineage = uniqueStringsInOrder([
|
|
2759
|
+
`brain_status:${input.brainStatus}`,
|
|
2760
|
+
`active_pack:${input.activePackId ?? "none"}`,
|
|
2761
|
+
`router:${input.routerIdentity ?? "none"}`,
|
|
2762
|
+
`used_learned_route_fn:${input.usedLearnedRouteFn === null ? "unknown" : String(input.usedLearnedRouteFn)}`
|
|
2763
|
+
]);
|
|
2764
|
+
const profileLineageDigest = checksumJsonPayload(profileLineage);
|
|
2765
|
+
const sessionLineageDigest = checksumJsonPayload(sessionLineage);
|
|
2766
|
+
const brainLineageDigest = checksumJsonPayload(brainLineage);
|
|
2767
|
+
return {
|
|
2768
|
+
selectionDigest: input.selectionDigest,
|
|
2769
|
+
promptContextDigest,
|
|
2770
|
+
promptContextFingerprints,
|
|
2771
|
+
workspaceInjectionSurfaceDigest,
|
|
2772
|
+
runtimeHintsDigest,
|
|
2773
|
+
runtimeHints,
|
|
2774
|
+
profileLineageDigest,
|
|
2775
|
+
profileLineage,
|
|
2776
|
+
sessionLineageDigest,
|
|
2777
|
+
sessionLineage,
|
|
2778
|
+
brainLineageDigest,
|
|
2779
|
+
brainLineage,
|
|
2780
|
+
digest: checksumJsonPayload({
|
|
2781
|
+
selectionDigest: input.selectionDigest,
|
|
2782
|
+
promptContextDigest,
|
|
2783
|
+
runtimeHintsDigest,
|
|
2784
|
+
profileLineageDigest,
|
|
2785
|
+
sessionLineageDigest,
|
|
2786
|
+
brainLineageDigest
|
|
2787
|
+
})
|
|
2788
|
+
};
|
|
2789
|
+
}
|
|
1547
2790
|
function buildRuntimeTurnAttribution(input) {
|
|
2791
|
+
const profileSelector = normalizeRuntimeProfileSelector(input.turn.profileSelector, "profileSelector");
|
|
1548
2792
|
const brainAttachmentPolicy = normalizeBrainAttachmentPolicy(input.turn.brainAttachmentPolicy);
|
|
2793
|
+
const profileId = normalizeOptionalString(input.turn.profileId) ?? null;
|
|
1549
2794
|
if (input.compileResult.ok) {
|
|
1550
2795
|
const notes = input.compileResult.compileResponse.diagnostics.notes;
|
|
1551
2796
|
const contextAttribution = buildContextAttributionSummary({
|
|
@@ -1557,7 +2802,8 @@ function buildRuntimeTurnAttribution(input) {
|
|
|
1557
2802
|
});
|
|
1558
2803
|
return {
|
|
1559
2804
|
hostRuntimeOwner: "openclaw",
|
|
1560
|
-
profileSelector
|
|
2805
|
+
profileSelector,
|
|
2806
|
+
profileId,
|
|
1561
2807
|
brainAttachmentPolicy,
|
|
1562
2808
|
brainStatus: "serving_active_pack",
|
|
1563
2809
|
activePackId: input.compileResult.activePackId,
|
|
@@ -1565,6 +2811,17 @@ function buildRuntimeTurnAttribution(input) {
|
|
|
1565
2811
|
routerIdentity: input.compileResult.compileResponse.diagnostics.routerIdentity,
|
|
1566
2812
|
selectionDigest: input.compileResult.compileResponse.diagnostics.selectionDigest,
|
|
1567
2813
|
selectionTiers: readDiagnosticNoteValue(notes, "selection_tiers="),
|
|
2814
|
+
contextFingerprint: buildRuntimeContextFingerprint({
|
|
2815
|
+
turn: input.turn,
|
|
2816
|
+
sourceStream: input.sourceStream,
|
|
2817
|
+
profileSelector,
|
|
2818
|
+
brainAttachmentPolicy,
|
|
2819
|
+
brainStatus: "serving_active_pack",
|
|
2820
|
+
activePackId: input.compileResult.activePackId,
|
|
2821
|
+
usedLearnedRouteFn: input.compileResult.compileResponse.diagnostics.usedLearnedRouteFn,
|
|
2822
|
+
routerIdentity: input.compileResult.compileResponse.diagnostics.routerIdentity,
|
|
2823
|
+
selectionDigest: input.compileResult.compileResponse.diagnostics.selectionDigest
|
|
2824
|
+
}),
|
|
1568
2825
|
contextEvidence: contextAttribution.evidence === "unprobed" ? null : contextAttribution.evidence
|
|
1569
2826
|
};
|
|
1570
2827
|
}
|
|
@@ -1575,7 +2832,8 @@ function buildRuntimeTurnAttribution(input) {
|
|
|
1575
2832
|
});
|
|
1576
2833
|
return {
|
|
1577
2834
|
hostRuntimeOwner: "openclaw",
|
|
1578
|
-
profileSelector
|
|
2835
|
+
profileSelector,
|
|
2836
|
+
profileId,
|
|
1579
2837
|
brainAttachmentPolicy,
|
|
1580
2838
|
brainStatus: input.compileResult.hardRequirementViolated ? "hard_fail" : "fail_open_static_context",
|
|
1581
2839
|
activePackId: null,
|
|
@@ -1583,6 +2841,17 @@ function buildRuntimeTurnAttribution(input) {
|
|
|
1583
2841
|
routerIdentity: null,
|
|
1584
2842
|
selectionDigest: null,
|
|
1585
2843
|
selectionTiers: null,
|
|
2844
|
+
contextFingerprint: buildRuntimeContextFingerprint({
|
|
2845
|
+
turn: input.turn,
|
|
2846
|
+
sourceStream: input.sourceStream,
|
|
2847
|
+
profileSelector,
|
|
2848
|
+
brainAttachmentPolicy,
|
|
2849
|
+
brainStatus: input.compileResult.hardRequirementViolated ? "hard_fail" : "fail_open_static_context",
|
|
2850
|
+
activePackId: null,
|
|
2851
|
+
usedLearnedRouteFn: null,
|
|
2852
|
+
routerIdentity: null,
|
|
2853
|
+
selectionDigest: null
|
|
2854
|
+
}),
|
|
1586
2855
|
contextEvidence: contextAttribution.evidence === "unprobed" ? null : contextAttribution.evidence
|
|
1587
2856
|
};
|
|
1588
2857
|
}
|
|
@@ -1600,10 +2869,6 @@ function buildCompileInteractionEvent(input) {
|
|
|
1600
2869
|
sessionId: input.turn.sessionId,
|
|
1601
2870
|
source: input.sourceStream
|
|
1602
2871
|
});
|
|
1603
|
-
const attribution = buildRuntimeTurnAttribution({
|
|
1604
|
-
turn: input.turn,
|
|
1605
|
-
compileResult: input.compileResult
|
|
1606
|
-
});
|
|
1607
2872
|
return createInteractionEvent({
|
|
1608
2873
|
eventId,
|
|
1609
2874
|
agentId: input.agentId,
|
|
@@ -1617,7 +2882,8 @@ function buildCompileInteractionEvent(input) {
|
|
|
1617
2882
|
stream: input.sourceStream
|
|
1618
2883
|
},
|
|
1619
2884
|
packId: input.compileResult.compileResponse.packId,
|
|
1620
|
-
|
|
2885
|
+
principal: buildRuntimeSelfPrincipal(input.turn),
|
|
2886
|
+
attribution: input.attribution
|
|
1621
2887
|
});
|
|
1622
2888
|
}
|
|
1623
2889
|
function buildDeliveryInteractionEvent(input) {
|
|
@@ -1638,10 +2904,6 @@ function buildDeliveryInteractionEvent(input) {
|
|
|
1638
2904
|
sessionId: input.turn.sessionId,
|
|
1639
2905
|
source: input.sourceStream
|
|
1640
2906
|
});
|
|
1641
|
-
const attribution = buildRuntimeTurnAttribution({
|
|
1642
|
-
turn: input.turn,
|
|
1643
|
-
compileResult: input.compileResult
|
|
1644
|
-
});
|
|
1645
2907
|
return createInteractionEvent({
|
|
1646
2908
|
eventId,
|
|
1647
2909
|
agentId: input.agentId,
|
|
@@ -1654,17 +2916,13 @@ function buildDeliveryInteractionEvent(input) {
|
|
|
1654
2916
|
runtimeOwner: "openclaw",
|
|
1655
2917
|
stream: input.sourceStream
|
|
1656
2918
|
},
|
|
1657
|
-
attribution,
|
|
2919
|
+
attribution: input.attribution,
|
|
1658
2920
|
...(input.compileResult.ok ? { packId: input.compileResult.compileResponse.packId } : {}),
|
|
1659
2921
|
...(messageId !== undefined ? { messageId } : {})
|
|
1660
2922
|
});
|
|
1661
2923
|
}
|
|
1662
2924
|
function buildFeedbackEvents(input) {
|
|
1663
2925
|
const feedbackItems = input.turn.feedback ?? [];
|
|
1664
|
-
const attribution = buildRuntimeTurnAttribution({
|
|
1665
|
-
turn: input.turn,
|
|
1666
|
-
compileResult: input.compileResult
|
|
1667
|
-
});
|
|
1668
2926
|
return feedbackItems.map((item, index) => {
|
|
1669
2927
|
if (item === null) {
|
|
1670
2928
|
throw new Error(`feedback[${index}] must be an object`);
|
|
@@ -1688,6 +2946,12 @@ function buildFeedbackEvents(input) {
|
|
|
1688
2946
|
source: input.sourceStream
|
|
1689
2947
|
});
|
|
1690
2948
|
const relatedInteractionId = normalizeOptionalString(item.relatedInteractionId) ?? input.compileInteraction?.eventId;
|
|
2949
|
+
const principal = resolveRuntimeFeedbackPrincipal({
|
|
2950
|
+
turn: input.turn,
|
|
2951
|
+
feedback: item,
|
|
2952
|
+
...(relatedInteractionId === undefined ? {} : { relatedInteractionId }),
|
|
2953
|
+
...(messageId === undefined ? {} : { messageId })
|
|
2954
|
+
});
|
|
1691
2955
|
return createFeedbackEvent({
|
|
1692
2956
|
eventId,
|
|
1693
2957
|
agentId: input.agentId,
|
|
@@ -1701,14 +2965,16 @@ function buildFeedbackEvents(input) {
|
|
|
1701
2965
|
stream: input.sourceStream
|
|
1702
2966
|
},
|
|
1703
2967
|
content,
|
|
1704
|
-
attribution,
|
|
2968
|
+
attribution: input.attribution,
|
|
1705
2969
|
...(messageId !== undefined ? { messageId } : {}),
|
|
2970
|
+
...(principal === undefined ? {} : { principal }),
|
|
1706
2971
|
...(relatedInteractionId !== undefined ? { relatedInteractionId } : {})
|
|
1707
2972
|
});
|
|
1708
2973
|
});
|
|
1709
2974
|
}
|
|
1710
2975
|
export function buildNormalizedRuntimeEventExport(turn, compileResult) {
|
|
1711
2976
|
const agentId = normalizeOptionalString(turn.agentId) ?? process.env.OPENCLAWBRAIN_AGENT_ID ?? DEFAULT_AGENT_ID;
|
|
2977
|
+
const profileSelector = normalizeRuntimeProfileSelector(turn.profileSelector, "profileSelector");
|
|
1712
2978
|
const sessionId = normalizeNonEmptyString(turn.sessionId, "sessionId");
|
|
1713
2979
|
const channel = normalizeNonEmptyString(turn.channel, "channel");
|
|
1714
2980
|
const sourceStream = normalizeOptionalString(turn.sourceStream) ?? `openclaw/runtime/${channel}`;
|
|
@@ -1717,12 +2983,19 @@ export function buildNormalizedRuntimeEventExport(turn, compileResult) {
|
|
|
1717
2983
|
const normalizedTurn = {
|
|
1718
2984
|
...turn,
|
|
1719
2985
|
agentId,
|
|
2986
|
+
profileSelector,
|
|
1720
2987
|
channel,
|
|
1721
2988
|
sessionId
|
|
1722
2989
|
};
|
|
2990
|
+
const attribution = buildRuntimeTurnAttribution({
|
|
2991
|
+
turn: normalizedTurn,
|
|
2992
|
+
compileResult,
|
|
2993
|
+
sourceStream
|
|
2994
|
+
});
|
|
1723
2995
|
const compileInteraction = buildCompileInteractionEvent({
|
|
1724
2996
|
turn: normalizedTurn,
|
|
1725
2997
|
compileResult,
|
|
2998
|
+
attribution,
|
|
1726
2999
|
sourceStream,
|
|
1727
3000
|
nextSequence,
|
|
1728
3001
|
createdAt: compileCreatedAt,
|
|
@@ -1730,6 +3003,7 @@ export function buildNormalizedRuntimeEventExport(turn, compileResult) {
|
|
|
1730
3003
|
});
|
|
1731
3004
|
const feedbackEvents = buildFeedbackEvents({
|
|
1732
3005
|
turn: normalizedTurn,
|
|
3006
|
+
attribution,
|
|
1733
3007
|
sourceStream,
|
|
1734
3008
|
nextSequence,
|
|
1735
3009
|
defaultCreatedAt: compileCreatedAt,
|
|
@@ -1740,6 +3014,7 @@ export function buildNormalizedRuntimeEventExport(turn, compileResult) {
|
|
|
1740
3014
|
const deliveryInteraction = buildDeliveryInteractionEvent({
|
|
1741
3015
|
turn: normalizedTurn,
|
|
1742
3016
|
compileResult,
|
|
3017
|
+
attribution,
|
|
1743
3018
|
sourceStream,
|
|
1744
3019
|
nextSequence,
|
|
1745
3020
|
defaultCreatedAt: compileCreatedAt,
|
|
@@ -1778,21 +3053,33 @@ export function writeRuntimeEventExportBundle(turn, normalizedEventExport) {
|
|
|
1778
3053
|
payloadPath: RUNTIME_EVENT_EXPORT_BUNDLE_LAYOUT.payload,
|
|
1779
3054
|
normalizedEventExport
|
|
1780
3055
|
});
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
3056
|
+
return writeNormalizedEventExportBundleFiles({
|
|
3057
|
+
rootDir: resolvedRoot,
|
|
3058
|
+
manifest,
|
|
3059
|
+
normalizedEventExport
|
|
3060
|
+
});
|
|
3061
|
+
}
|
|
3062
|
+
export function writeScannedEventExportBundle(input) {
|
|
3063
|
+
const built = buildNormalizedEventExportFromScannedEvents(input.scannedEventExport);
|
|
3064
|
+
if (!built.ok) {
|
|
3065
|
+
return built;
|
|
3066
|
+
}
|
|
3067
|
+
const rootDir = normalizeNonEmptyString(input.rootDir, "rootDir");
|
|
3068
|
+
const exportName = normalizeOptionalString(input.exportName) ??
|
|
3069
|
+
`${built.scanner.scannerId}-${built.scanner.lane}-${built.normalizedEventExport.range.start}-${built.normalizedEventExport.range.end}`;
|
|
3070
|
+
const exportedAt = normalizeIsoTimestamp(input.exportedAt, "exportedAt", built.scanner.producedAt);
|
|
3071
|
+
const manifest = buildRuntimeEventExportBundleManifest({
|
|
3072
|
+
exportName,
|
|
3073
|
+
exportedAt,
|
|
3074
|
+
payloadPath: RUNTIME_EVENT_EXPORT_BUNDLE_LAYOUT.payload,
|
|
3075
|
+
normalizedEventExport: built.normalizedEventExport,
|
|
3076
|
+
scanner: built.scanner
|
|
3077
|
+
});
|
|
3078
|
+
return writeNormalizedEventExportBundleFiles({
|
|
3079
|
+
rootDir,
|
|
3080
|
+
manifest,
|
|
3081
|
+
normalizedEventExport: built.normalizedEventExport
|
|
3082
|
+
});
|
|
1796
3083
|
}
|
|
1797
3084
|
export function runRuntimeTurn(turn, options = {}) {
|
|
1798
3085
|
const agentId = normalizeOptionalString(turn.agentId);
|
|
@@ -1914,6 +3201,7 @@ export function runContinuousProductLoopTurn(input) {
|
|
|
1914
3201
|
const mergedHistory = mergeRuntimeEventHistory(currentState, normalizedEventExport);
|
|
1915
3202
|
currentState.interactionEvents = mergedHistory.interactionEvents;
|
|
1916
3203
|
currentState.feedbackEvents = mergedHistory.feedbackEvents;
|
|
3204
|
+
const compileStructuralSignals = turnResult.ok ? turnResult.compileResponse.diagnostics.structuralSignals : undefined;
|
|
1917
3205
|
try {
|
|
1918
3206
|
let activeBeforePack = null;
|
|
1919
3207
|
try {
|
|
@@ -1932,6 +3220,7 @@ export function runContinuousProductLoopTurn(input) {
|
|
|
1932
3220
|
builtAt: normalizeIsoTimestamp(input.candidateBuiltAt, "candidateBuiltAt", normalizedEventExport.range.lastCreatedAt ?? normalizedEventExport.range.firstCreatedAt),
|
|
1933
3221
|
...(input.offlineArtifacts !== undefined ? { offlineArtifacts: input.offlineArtifacts } : {}),
|
|
1934
3222
|
...(input.structuralOps !== undefined ? { structuralOps: input.structuralOps } : {}),
|
|
3223
|
+
...(compileStructuralSignals !== undefined ? { compileStructuralSignals } : {}),
|
|
1935
3224
|
...(input.sparseFeedback !== undefined ? { sparseFeedback: input.sparseFeedback } : {}),
|
|
1936
3225
|
...(input.liveSliceSize !== undefined ? { liveSliceSize: input.liveSliceSize } : {}),
|
|
1937
3226
|
...(input.backfillSliceSize !== undefined ? { backfillSliceSize: input.backfillSliceSize } : {}),
|
|
@@ -2242,6 +3531,34 @@ function buildReplayTurnScore(input) {
|
|
|
2242
3531
|
qualityScore: Math.min(100, compileScore + phraseScore)
|
|
2243
3532
|
};
|
|
2244
3533
|
}
|
|
3534
|
+
function buildRecordedSessionTurnObservability(result) {
|
|
3535
|
+
if (!result.eventExport.ok) {
|
|
3536
|
+
return {
|
|
3537
|
+
scanPolicy: null,
|
|
3538
|
+
scanSurfaces: [],
|
|
3539
|
+
humanLabelCount: 0,
|
|
3540
|
+
selfLabelCount: 0,
|
|
3541
|
+
totalEventCount: 0,
|
|
3542
|
+
attributedEventCount: 0,
|
|
3543
|
+
selectionDigestCount: 0,
|
|
3544
|
+
freshestSourceStream: null,
|
|
3545
|
+
freshestCreatedAt: null
|
|
3546
|
+
};
|
|
3547
|
+
}
|
|
3548
|
+
const observability = describeNormalizedEventExportObservability(result.eventExport.normalizedEventExport);
|
|
3549
|
+
const freshestSource = observability.supervisionFreshnessBySource[0] ?? null;
|
|
3550
|
+
return {
|
|
3551
|
+
scanPolicy: observability.learningSurface.scanPolicy,
|
|
3552
|
+
scanSurfaces: [...observability.learningSurface.scanSurfaces],
|
|
3553
|
+
humanLabelCount: observability.learningSurface.humanLabelCount,
|
|
3554
|
+
selfLabelCount: observability.learningSurface.selfLabelCount,
|
|
3555
|
+
totalEventCount: observability.attributionCoverage.totalEventCount,
|
|
3556
|
+
attributedEventCount: observability.attributionCoverage.attributedEventCount,
|
|
3557
|
+
selectionDigestCount: observability.attributionCoverage.selectionDigestCount,
|
|
3558
|
+
freshestSourceStream: observability.teacherFreshness.sourceStream ?? freshestSource?.sourceStream ?? null,
|
|
3559
|
+
freshestCreatedAt: observability.teacherFreshness.freshestCreatedAt ?? freshestSource?.freshestCreatedAt ?? null
|
|
3560
|
+
};
|
|
3561
|
+
}
|
|
2245
3562
|
function buildRecordedSessionTurnReport(turnFixture, result, options) {
|
|
2246
3563
|
const compileOk = result.ok;
|
|
2247
3564
|
const selectedContextTexts = compileOk ? result.compileResponse.selectedContext.map((block) => block.text) : [];
|
|
@@ -2251,6 +3568,7 @@ function buildRecordedSessionTurnReport(turnFixture, result, options) {
|
|
|
2251
3568
|
texts: selectedContextTexts,
|
|
2252
3569
|
expectedContextPhrases: turnFixture.expectedContextPhrases
|
|
2253
3570
|
});
|
|
3571
|
+
const observability = buildRecordedSessionTurnObservability(result);
|
|
2254
3572
|
const eventExportDigest = result.eventExport.ok === true ? result.eventExport.normalizedEventExport.provenance.exportDigest : null;
|
|
2255
3573
|
return {
|
|
2256
3574
|
turnId: turnFixture.turnId,
|
|
@@ -2271,9 +3589,78 @@ function buildRecordedSessionTurnReport(turnFixture, result, options) {
|
|
|
2271
3589
|
qualityScore: scoring.qualityScore,
|
|
2272
3590
|
compileActiveVersion: options.compileActiveVersion,
|
|
2273
3591
|
promoted: options.promoted,
|
|
3592
|
+
observability,
|
|
2274
3593
|
warnings: [...result.warnings]
|
|
2275
3594
|
};
|
|
2276
3595
|
}
|
|
3596
|
+
function countRecordedSessionActivePackChanges(turns) {
|
|
3597
|
+
let changes = 0;
|
|
3598
|
+
let previousPackId = null;
|
|
3599
|
+
for (const turn of turns) {
|
|
3600
|
+
if (turn.activePackId === null) {
|
|
3601
|
+
continue;
|
|
3602
|
+
}
|
|
3603
|
+
if (previousPackId !== null && previousPackId !== turn.activePackId) {
|
|
3604
|
+
changes += 1;
|
|
3605
|
+
}
|
|
3606
|
+
previousPackId = turn.activePackId;
|
|
3607
|
+
}
|
|
3608
|
+
return changes;
|
|
3609
|
+
}
|
|
3610
|
+
function buildRecordedSessionReplayScannerWarnings(mode, turns, activePackChangeCount) {
|
|
3611
|
+
const warnings = [];
|
|
3612
|
+
const exportTurnCount = turns.filter((turn) => turn.observability.totalEventCount > 0).length;
|
|
3613
|
+
const attributedTurnCount = turns.filter((turn) => turn.observability.attributedEventCount > 0).length;
|
|
3614
|
+
const selectionDigestTurnCount = turns.filter((turn) => turn.observability.selectionDigestCount > 0).length;
|
|
3615
|
+
const humanLabelCount = turns.reduce((sum, turn) => sum + turn.observability.humanLabelCount, 0);
|
|
3616
|
+
const scanSurfaceCount = new Set(turns.flatMap((turn) => turn.observability.scanSurfaces)).size;
|
|
3617
|
+
if (exportTurnCount === 0) {
|
|
3618
|
+
warnings.push("no_export_observability");
|
|
3619
|
+
}
|
|
3620
|
+
if (scanSurfaceCount === 0) {
|
|
3621
|
+
warnings.push("scan_surfaces_missing");
|
|
3622
|
+
}
|
|
3623
|
+
if (humanLabelCount === 0) {
|
|
3624
|
+
warnings.push("human_labels_missing");
|
|
3625
|
+
}
|
|
3626
|
+
if (attributedTurnCount === 0) {
|
|
3627
|
+
warnings.push("turn_attribution_missing");
|
|
3628
|
+
}
|
|
3629
|
+
if (turns.some((turn) => turn.compileOk) && selectionDigestTurnCount === 0) {
|
|
3630
|
+
warnings.push("selection_digest_missing");
|
|
3631
|
+
}
|
|
3632
|
+
if (mode === "learned_replay" && activePackChangeCount === 0) {
|
|
3633
|
+
warnings.push("active_pack_never_moved");
|
|
3634
|
+
}
|
|
3635
|
+
return warnings;
|
|
3636
|
+
}
|
|
3637
|
+
function buildRecordedSessionReplayScannerEvidence(mode, turns) {
|
|
3638
|
+
const exportTurnCount = turns.filter((turn) => turn.observability.totalEventCount > 0).length;
|
|
3639
|
+
const scanSurfaces = uniqueStringsInOrder(turns.flatMap((turn) => turn.observability.scanSurfaces));
|
|
3640
|
+
const attributedTurnCount = turns.filter((turn) => turn.observability.attributedEventCount > 0).length;
|
|
3641
|
+
const selectionDigestTurnCount = turns.filter((turn) => turn.observability.selectionDigestCount > 0).length;
|
|
3642
|
+
const activePackChangeCount = countRecordedSessionActivePackChanges(turns);
|
|
3643
|
+
const latestObservedTurn = [...turns]
|
|
3644
|
+
.filter((turn) => turn.observability.freshestCreatedAt !== null)
|
|
3645
|
+
.sort((left, right) => (right.observability.freshestCreatedAt ?? "").localeCompare(left.observability.freshestCreatedAt ?? ""))[0];
|
|
3646
|
+
return {
|
|
3647
|
+
exportTurnCount,
|
|
3648
|
+
scanPolicy: turns.find((turn) => turn.observability.scanPolicy !== null)?.observability.scanPolicy ?? null,
|
|
3649
|
+
scanSurfaceCount: scanSurfaces.length,
|
|
3650
|
+
scanSurfaces,
|
|
3651
|
+
humanLabelCount: turns.reduce((sum, turn) => sum + turn.observability.humanLabelCount, 0),
|
|
3652
|
+
selfLabelCount: turns.reduce((sum, turn) => sum + turn.observability.selfLabelCount, 0),
|
|
3653
|
+
totalEventCount: turns.reduce((sum, turn) => sum + turn.observability.totalEventCount, 0),
|
|
3654
|
+
attributedEventCount: turns.reduce((sum, turn) => sum + turn.observability.attributedEventCount, 0),
|
|
3655
|
+
attributedTurnCount,
|
|
3656
|
+
selectionDigestCount: turns.reduce((sum, turn) => sum + turn.observability.selectionDigestCount, 0),
|
|
3657
|
+
selectionDigestTurnCount,
|
|
3658
|
+
activePackChangeCount,
|
|
3659
|
+
freshestSourceStream: latestObservedTurn?.observability.freshestSourceStream ?? null,
|
|
3660
|
+
freshestCreatedAt: latestObservedTurn?.observability.freshestCreatedAt ?? null,
|
|
3661
|
+
warnings: buildRecordedSessionReplayScannerWarnings(mode, turns, activePackChangeCount)
|
|
3662
|
+
};
|
|
3663
|
+
}
|
|
2277
3664
|
function buildRecordedSessionReplayModeSummary(mode, turns) {
|
|
2278
3665
|
const compileOkCount = turns.filter((turn) => turn.compileOk).length;
|
|
2279
3666
|
const phraseHitCount = turns.reduce((sum, turn) => sum + turn.phraseHits.length, 0);
|
|
@@ -2282,6 +3669,7 @@ function buildRecordedSessionReplayModeSummary(mode, turns) {
|
|
|
2282
3669
|
const promotionCount = turns.filter((turn) => turn.promoted).length;
|
|
2283
3670
|
const qualityScore = turns.length === 0 ? 0 : Math.round(turns.reduce((sum, turn) => sum + turn.qualityScore, 0) / turns.length);
|
|
2284
3671
|
const packIds = uniqueStringsInOrder(turns.map((turn) => turn.activePackId).filter(isPresent));
|
|
3672
|
+
const scannerEvidence = buildRecordedSessionReplayScannerEvidence(mode, turns);
|
|
2285
3673
|
const base = {
|
|
2286
3674
|
mode,
|
|
2287
3675
|
qualityScore,
|
|
@@ -2290,7 +3678,8 @@ function buildRecordedSessionReplayModeSummary(mode, turns) {
|
|
|
2290
3678
|
phraseCount,
|
|
2291
3679
|
usedLearnedRouteTurnCount,
|
|
2292
3680
|
promotionCount,
|
|
2293
|
-
packIds
|
|
3681
|
+
packIds,
|
|
3682
|
+
scannerEvidence
|
|
2294
3683
|
};
|
|
2295
3684
|
return {
|
|
2296
3685
|
...base,
|
|
@@ -2328,6 +3717,7 @@ function buildRecordedSessionReplayScoreHash(modes) {
|
|
|
2328
3717
|
usedLearnedRouteTurnCount: mode.summary.usedLearnedRouteTurnCount,
|
|
2329
3718
|
promotionCount: mode.summary.promotionCount,
|
|
2330
3719
|
packIds: mode.summary.packIds,
|
|
3720
|
+
scannerEvidence: mode.summary.scannerEvidence,
|
|
2331
3721
|
scoreHash: mode.summary.scoreHash
|
|
2332
3722
|
})));
|
|
2333
3723
|
}
|
|
@@ -2358,6 +3748,17 @@ function recordedSessionReplayBundleBase(bundle) {
|
|
|
2358
3748
|
expectedContextPhrases: [...turn.expectedContextPhrases],
|
|
2359
3749
|
phraseHits: [...turn.phraseHits],
|
|
2360
3750
|
missedPhrases: [...turn.missedPhrases],
|
|
3751
|
+
observability: {
|
|
3752
|
+
scanPolicy: turn.observability.scanPolicy,
|
|
3753
|
+
scanSurfaces: [...turn.observability.scanSurfaces],
|
|
3754
|
+
humanLabelCount: turn.observability.humanLabelCount,
|
|
3755
|
+
selfLabelCount: turn.observability.selfLabelCount,
|
|
3756
|
+
totalEventCount: turn.observability.totalEventCount,
|
|
3757
|
+
attributedEventCount: turn.observability.attributedEventCount,
|
|
3758
|
+
selectionDigestCount: turn.observability.selectionDigestCount,
|
|
3759
|
+
freshestSourceStream: turn.observability.freshestSourceStream,
|
|
3760
|
+
freshestCreatedAt: turn.observability.freshestCreatedAt
|
|
3761
|
+
},
|
|
2361
3762
|
warnings: [...turn.warnings]
|
|
2362
3763
|
}))
|
|
2363
3764
|
})),
|
|
@@ -2749,6 +4150,39 @@ function summarizeCandidateAheadBy(candidateAheadBy) {
|
|
|
2749
4150
|
.map(([field]) => field)
|
|
2750
4151
|
.sort();
|
|
2751
4152
|
}
|
|
4153
|
+
function summarizeManyProfileSupport(policyMode) {
|
|
4154
|
+
if (policyMode === "shared") {
|
|
4155
|
+
return {
|
|
4156
|
+
operatorSurface: "current_profile_only",
|
|
4157
|
+
declaredAttachmentPolicy: "shared",
|
|
4158
|
+
sameGatewayIntent: "shared_attachment_declared",
|
|
4159
|
+
checkedInProofTopology: "two_local_gateways_dedicated_only",
|
|
4160
|
+
sameGatewayProof: false,
|
|
4161
|
+
sharedWriteSafetyProof: false,
|
|
4162
|
+
detail: "The Host declares that multiple Profiles may intentionally attach to this Brain activation root, but the shipped operator read stays current-profile-only and this repo still does not prove same-gateway attachment behavior or shared write safety."
|
|
4163
|
+
};
|
|
4164
|
+
}
|
|
4165
|
+
if (policyMode === "dedicated") {
|
|
4166
|
+
return {
|
|
4167
|
+
operatorSurface: "current_profile_only",
|
|
4168
|
+
declaredAttachmentPolicy: "dedicated",
|
|
4169
|
+
sameGatewayIntent: "dedicated_current_profile_boundary",
|
|
4170
|
+
checkedInProofTopology: "two_local_gateways_dedicated_only",
|
|
4171
|
+
sameGatewayProof: false,
|
|
4172
|
+
sharedWriteSafetyProof: false,
|
|
4173
|
+
detail: "The Host declares one current Profile per Brain activation root. The checked-in many-profile proof is still narrower: two local gateways with dedicated brains, not same-gateway many-profile attachment inside one running host."
|
|
4174
|
+
};
|
|
4175
|
+
}
|
|
4176
|
+
return {
|
|
4177
|
+
operatorSurface: "current_profile_only",
|
|
4178
|
+
declaredAttachmentPolicy: "undeclared",
|
|
4179
|
+
sameGatewayIntent: "undeclared",
|
|
4180
|
+
checkedInProofTopology: "two_local_gateways_dedicated_only",
|
|
4181
|
+
sameGatewayProof: false,
|
|
4182
|
+
sharedWriteSafetyProof: false,
|
|
4183
|
+
detail: "The Host has not declared shared-vs-dedicated attachment policy. Keep the operator read current-profile-only, do not infer profile exclusivity from activation state alone, and do not claim same-gateway many-profile proof."
|
|
4184
|
+
};
|
|
4185
|
+
}
|
|
2752
4186
|
function isAwaitingFirstExportSlot(slot) {
|
|
2753
4187
|
return slot !== null && slot.eventRange.count === 0;
|
|
2754
4188
|
}
|
|
@@ -2833,6 +4267,7 @@ function summarizeGraphObservability(active, observability) {
|
|
|
2833
4267
|
};
|
|
2834
4268
|
}
|
|
2835
4269
|
function summarizeServePath(compile) {
|
|
4270
|
+
const structuralDecision = summarizeStructuralDecisionFromNotes(compile?.notes ?? []);
|
|
2836
4271
|
if (compile === null) {
|
|
2837
4272
|
return {
|
|
2838
4273
|
state: "unprobed",
|
|
@@ -2850,6 +4285,7 @@ function summarizeServePath(compile) {
|
|
|
2850
4285
|
structuralBudgetSource: null,
|
|
2851
4286
|
structuralBudgetEvidence: null,
|
|
2852
4287
|
structuralBudgetPressures: null,
|
|
4288
|
+
structuralDecision,
|
|
2853
4289
|
contextAttribution: buildContextAttributionSummary({
|
|
2854
4290
|
fallbackToStaticContext: false,
|
|
2855
4291
|
hardRequirementViolated: false,
|
|
@@ -2876,11 +4312,11 @@ function summarizeServePath(compile) {
|
|
|
2876
4312
|
structuralBudgetSource: null,
|
|
2877
4313
|
structuralBudgetEvidence: null,
|
|
2878
4314
|
structuralBudgetPressures: null,
|
|
4315
|
+
structuralDecision,
|
|
2879
4316
|
contextAttribution: compile.contextAttribution,
|
|
2880
4317
|
error: compile.error
|
|
2881
4318
|
};
|
|
2882
4319
|
}
|
|
2883
|
-
const resolvedMaxContextBlocksValue = readDiagnosticNoteValue(compile.notes, "resolved_max_context_blocks=");
|
|
2884
4320
|
return {
|
|
2885
4321
|
state: "serving_active_pack",
|
|
2886
4322
|
fallbackToStaticContext: compile.fallbackToStaticContext,
|
|
@@ -2893,11 +4329,12 @@ function summarizeServePath(compile) {
|
|
|
2893
4329
|
freshnessChecksum: readDiagnosticNoteValue(compile.notes, "router_freshness_checksum="),
|
|
2894
4330
|
requestedBudgetStrategy: readDiagnosticNoteValue(compile.notes, "requested_budget_strategy="),
|
|
2895
4331
|
resolvedBudgetStrategy: readDiagnosticNoteValue(compile.notes, "resolved_budget_strategy="),
|
|
2896
|
-
resolvedMaxContextBlocks:
|
|
4332
|
+
resolvedMaxContextBlocks: structuralDecision.resolvedMaxContextBlocks,
|
|
2897
4333
|
structuralBudgetSource: readDiagnosticNoteValue(compile.notes, "structural_budget_source="),
|
|
2898
4334
|
structuralBudgetEvidence: readDiagnosticNoteValue(compile.notes, "structural_budget_compile_evidence=") ??
|
|
2899
4335
|
readDiagnosticNoteValue(compile.notes, "structural_budget_evidence="),
|
|
2900
4336
|
structuralBudgetPressures: readDiagnosticNoteValue(compile.notes, "structural_budget_pressures="),
|
|
4337
|
+
structuralDecision,
|
|
2901
4338
|
contextAttribution: compile.contextAttribution,
|
|
2902
4339
|
error: compile.error
|
|
2903
4340
|
};
|
|
@@ -3046,11 +4483,18 @@ function summarizeSupervision(input) {
|
|
|
3046
4483
|
exportDigest: null,
|
|
3047
4484
|
exportedAt: null,
|
|
3048
4485
|
flowing: null,
|
|
4486
|
+
scanPolicy: null,
|
|
4487
|
+
scanSurfaceCount: 0,
|
|
4488
|
+
scanSurfaces: [],
|
|
3049
4489
|
sourceCount: 0,
|
|
3050
4490
|
freshestSourceStream: null,
|
|
3051
4491
|
freshestCreatedAt: null,
|
|
3052
4492
|
freshestKind: null,
|
|
3053
4493
|
humanLabelCount: null,
|
|
4494
|
+
selfLabelCount: null,
|
|
4495
|
+
attributedEventCount: null,
|
|
4496
|
+
totalEventCount: null,
|
|
4497
|
+
selectionDigestCount: null,
|
|
3054
4498
|
sources: [],
|
|
3055
4499
|
detail: "no event export path supplied"
|
|
3056
4500
|
};
|
|
@@ -3065,11 +4509,18 @@ function summarizeSupervision(input) {
|
|
|
3065
4509
|
exportDigest: observability.exportDigest,
|
|
3066
4510
|
exportedAt: loaded.exportedAt,
|
|
3067
4511
|
flowing,
|
|
4512
|
+
scanPolicy: observability.learningSurface.scanPolicy,
|
|
4513
|
+
scanSurfaceCount: observability.learningSurface.scanSurfaces.length,
|
|
4514
|
+
scanSurfaces: [...observability.learningSurface.scanSurfaces],
|
|
3068
4515
|
sourceCount: observability.supervisionFreshnessBySource.length,
|
|
3069
4516
|
freshestSourceStream: observability.teacherFreshness.sourceStream ?? freshestSource?.sourceStream ?? null,
|
|
3070
4517
|
freshestCreatedAt: observability.teacherFreshness.freshestCreatedAt ?? freshestSource?.freshestCreatedAt ?? null,
|
|
3071
4518
|
freshestKind: observability.teacherFreshness.freshestKind ?? freshestSource?.freshestKind ?? null,
|
|
3072
4519
|
humanLabelCount: observability.teacherFreshness.humanLabelCount,
|
|
4520
|
+
selfLabelCount: observability.learningSurface.selfLabelCount,
|
|
4521
|
+
attributedEventCount: observability.attributionCoverage.attributedEventCount,
|
|
4522
|
+
totalEventCount: observability.attributionCoverage.totalEventCount,
|
|
4523
|
+
selectionDigestCount: observability.attributionCoverage.selectionDigestCount,
|
|
3073
4524
|
sources: [...observability.teacherFreshness.sources],
|
|
3074
4525
|
detail: flowing
|
|
3075
4526
|
? "human supervision is visible in the supplied export"
|
|
@@ -3134,6 +4585,53 @@ function summarizeTeacherLoop(input) {
|
|
|
3134
4585
|
detail: "async teacher diagnostics loaded"
|
|
3135
4586
|
};
|
|
3136
4587
|
}
|
|
4588
|
+
function summarizeLearningBacklogState(plan, principalLagStatus) {
|
|
4589
|
+
if (!plan.bootstrapped && plan.pending.total === 0) {
|
|
4590
|
+
return "awaiting_first_export";
|
|
4591
|
+
}
|
|
4592
|
+
if (plan.nextPriorityBucket === "principal_immediate") {
|
|
4593
|
+
return "principal_live_priority";
|
|
4594
|
+
}
|
|
4595
|
+
if (plan.nextPriorityBucket === "principal_backfill") {
|
|
4596
|
+
return "principal_backfill_priority";
|
|
4597
|
+
}
|
|
4598
|
+
if (plan.nextPriorityBucket === "live") {
|
|
4599
|
+
return "live_priority";
|
|
4600
|
+
}
|
|
4601
|
+
if (plan.nextPriorityBucket === "backfill") {
|
|
4602
|
+
return "backfill_only";
|
|
4603
|
+
}
|
|
4604
|
+
return principalLagStatus === "pending_promotion" ? "principal_live_priority" : "caught_up";
|
|
4605
|
+
}
|
|
4606
|
+
function summarizeLearningWarningStates(input) {
|
|
4607
|
+
const warnings = new Set();
|
|
4608
|
+
if (!input.plan.bootstrapped && input.plan.pending.total === 0) {
|
|
4609
|
+
warnings.add("awaiting_first_export");
|
|
4610
|
+
}
|
|
4611
|
+
if (input.plan.pending.byBucket.principal_immediate > 0) {
|
|
4612
|
+
warnings.add("principal_live_backlog");
|
|
4613
|
+
}
|
|
4614
|
+
if (input.plan.pending.byBucket.principal_backfill > 0) {
|
|
4615
|
+
warnings.add("principal_backfill_pending");
|
|
4616
|
+
}
|
|
4617
|
+
if (input.principalLagStatus === "pending_promotion") {
|
|
4618
|
+
warnings.add("active_pack_behind_latest_principal");
|
|
4619
|
+
}
|
|
4620
|
+
if (input.plan.pending.backfill > 0) {
|
|
4621
|
+
warnings.add("passive_backfill_pending");
|
|
4622
|
+
}
|
|
4623
|
+
if (input.teacherSnapshot.queue.capacity > 0 &&
|
|
4624
|
+
input.teacherSnapshot.queue.depth >= input.teacherSnapshot.queue.capacity) {
|
|
4625
|
+
warnings.add("teacher_queue_full");
|
|
4626
|
+
}
|
|
4627
|
+
if (input.teacherSnapshot.diagnostics.latestFreshness === "stale") {
|
|
4628
|
+
warnings.add("teacher_labels_stale");
|
|
4629
|
+
}
|
|
4630
|
+
if (input.teacherSnapshot.diagnostics.lastNoOpReason === "no_teacher_artifacts") {
|
|
4631
|
+
warnings.add("teacher_no_artifacts");
|
|
4632
|
+
}
|
|
4633
|
+
return [...warnings];
|
|
4634
|
+
}
|
|
3137
4635
|
function summarizeAlwaysOnLearning(input, active) {
|
|
3138
4636
|
const unavailableLag = {
|
|
3139
4637
|
activeEventRangeEnd: active?.eventRange.end ?? null,
|
|
@@ -3149,15 +4647,21 @@ function summarizeAlwaysOnLearning(input, active) {
|
|
|
3149
4647
|
bootstrapped: null,
|
|
3150
4648
|
mode: "unavailable",
|
|
3151
4649
|
nextPriorityLane: "unavailable",
|
|
4650
|
+
nextPriorityBucket: "unavailable",
|
|
4651
|
+
backlogState: "unavailable",
|
|
3152
4652
|
pendingLive: null,
|
|
3153
4653
|
pendingBackfill: null,
|
|
3154
4654
|
pendingTotal: null,
|
|
4655
|
+
pendingByBucket: null,
|
|
3155
4656
|
freshLivePriority: null,
|
|
3156
4657
|
principalCheckpointCount: null,
|
|
3157
4658
|
pendingPrincipalCount: null,
|
|
3158
4659
|
oldestUnlearnedPrincipalEvent: null,
|
|
4660
|
+
newestPendingPrincipalEvent: null,
|
|
4661
|
+
leadingPrincipalCheckpoint: null,
|
|
3159
4662
|
principalCheckpoints: [],
|
|
3160
4663
|
principalLagToPromotion: unavailableLag,
|
|
4664
|
+
warningStates: ["teacher_snapshot_unavailable"],
|
|
3161
4665
|
learnedRange: null,
|
|
3162
4666
|
materializationCount: null,
|
|
3163
4667
|
lastMaterializedAt: null,
|
|
@@ -3176,15 +4680,21 @@ function summarizeAlwaysOnLearning(input, active) {
|
|
|
3176
4680
|
bootstrapped: null,
|
|
3177
4681
|
mode: "unavailable",
|
|
3178
4682
|
nextPriorityLane: "unavailable",
|
|
4683
|
+
nextPriorityBucket: "unavailable",
|
|
4684
|
+
backlogState: "unavailable",
|
|
3179
4685
|
pendingLive: null,
|
|
3180
4686
|
pendingBackfill: null,
|
|
3181
4687
|
pendingTotal: null,
|
|
4688
|
+
pendingByBucket: null,
|
|
3182
4689
|
freshLivePriority: null,
|
|
3183
4690
|
principalCheckpointCount: null,
|
|
3184
4691
|
pendingPrincipalCount: null,
|
|
3185
4692
|
oldestUnlearnedPrincipalEvent: null,
|
|
4693
|
+
newestPendingPrincipalEvent: null,
|
|
4694
|
+
leadingPrincipalCheckpoint: null,
|
|
3186
4695
|
principalCheckpoints: [],
|
|
3187
4696
|
principalLagToPromotion: unavailableLag,
|
|
4697
|
+
warningStates: ["teacher_snapshot_unavailable"],
|
|
3188
4698
|
learnedRange: null,
|
|
3189
4699
|
materializationCount: null,
|
|
3190
4700
|
lastMaterializedAt: null,
|
|
@@ -3207,30 +4717,43 @@ function summarizeAlwaysOnLearning(input, active) {
|
|
|
3207
4717
|
const sequenceLag = latestPrincipalSequence === null || activeEventRangeEnd === null
|
|
3208
4718
|
? null
|
|
3209
4719
|
: Math.max(latestPrincipalSequence - activeEventRangeEnd, 0);
|
|
4720
|
+
const principalLagStatus = sequenceLag === null
|
|
4721
|
+
? "unavailable"
|
|
4722
|
+
: sequenceLag === 0
|
|
4723
|
+
? "caught_up"
|
|
4724
|
+
: "pending_promotion";
|
|
4725
|
+
const backlogState = summarizeLearningBacklogState(plan, principalLagStatus);
|
|
4726
|
+
const warningStates = summarizeLearningWarningStates({
|
|
4727
|
+
plan,
|
|
4728
|
+
principalLagStatus,
|
|
4729
|
+
teacherSnapshot: snapshot
|
|
4730
|
+
});
|
|
3210
4731
|
return {
|
|
3211
4732
|
available: true,
|
|
3212
4733
|
sourcePath: path.resolve(teacherSnapshotPath),
|
|
3213
4734
|
bootstrapped: plan.bootstrapped,
|
|
3214
4735
|
mode: plan.mode,
|
|
3215
4736
|
nextPriorityLane: plan.nextPriorityLane,
|
|
4737
|
+
nextPriorityBucket: plan.nextPriorityBucket,
|
|
4738
|
+
backlogState,
|
|
3216
4739
|
pendingLive: plan.pending.live,
|
|
3217
4740
|
pendingBackfill: plan.pending.backfill,
|
|
3218
4741
|
pendingTotal: plan.pending.total,
|
|
4742
|
+
pendingByBucket: { ...plan.pending.byBucket },
|
|
3219
4743
|
freshLivePriority: plan.pending.freshLivePriority,
|
|
3220
4744
|
principalCheckpointCount: plan.principalBacklog.principalCount,
|
|
3221
4745
|
pendingPrincipalCount: plan.principalBacklog.pendingEventCount,
|
|
3222
4746
|
oldestUnlearnedPrincipalEvent: plan.principalBacklog.oldestUnlearnedEvent,
|
|
4747
|
+
newestPendingPrincipalEvent: plan.principalBacklog.newestPendingEvent,
|
|
4748
|
+
leadingPrincipalCheckpoint: plan.principalBacklog.checkpoints[0] ?? null,
|
|
3223
4749
|
principalCheckpoints: plan.principalBacklog.checkpoints,
|
|
3224
4750
|
principalLagToPromotion: {
|
|
3225
4751
|
activeEventRangeEnd,
|
|
3226
4752
|
latestPrincipalSequence,
|
|
3227
4753
|
sequenceLag,
|
|
3228
|
-
status:
|
|
3229
|
-
? "unavailable"
|
|
3230
|
-
: sequenceLag === 0
|
|
3231
|
-
? "caught_up"
|
|
3232
|
-
: "pending_promotion"
|
|
4754
|
+
status: principalLagStatus
|
|
3233
4755
|
},
|
|
4756
|
+
warningStates,
|
|
3234
4757
|
learnedRange: plan.learnedRange === null ? null : { ...plan.learnedRange },
|
|
3235
4758
|
materializationCount: plan.materialization.count,
|
|
3236
4759
|
lastMaterializedAt: plan.materialization.lastMaterializedAt,
|
|
@@ -3238,13 +4761,17 @@ function summarizeAlwaysOnLearning(input, active) {
|
|
|
3238
4761
|
lastMaterializationLane: plan.materialization.lastLane,
|
|
3239
4762
|
lastMaterializationPriority: plan.materialization.lastPriority,
|
|
3240
4763
|
lastMaterializedPackId: snapshot.learner.lastMaterialization?.candidate.summary.packId ?? null,
|
|
3241
|
-
detail: plan.
|
|
3242
|
-
? "
|
|
3243
|
-
: plan.
|
|
3244
|
-
? "passive backfill
|
|
3245
|
-
: plan.
|
|
3246
|
-
? "
|
|
3247
|
-
:
|
|
4764
|
+
detail: plan.nextPriorityBucket === "principal_immediate"
|
|
4765
|
+
? "principal-priority live slices are next; passive backfill stays behind live intake"
|
|
4766
|
+
: plan.nextPriorityBucket === "live"
|
|
4767
|
+
? "fresh live slices are next; passive backfill stays behind live intake"
|
|
4768
|
+
: plan.nextPriorityBucket === "principal_backfill"
|
|
4769
|
+
? "live intake is clear; principal-priority passive backfill is next"
|
|
4770
|
+
: plan.nextPriorityBucket === "backfill"
|
|
4771
|
+
? "live intake is clear; passive backfill is next"
|
|
4772
|
+
: plan.bootstrapped
|
|
4773
|
+
? "fast-init has handed off to the current learned export without queued backlog"
|
|
4774
|
+
: "learner is waiting for the first export"
|
|
3248
4775
|
};
|
|
3249
4776
|
}
|
|
3250
4777
|
function buildOperatorFindings(report) {
|
|
@@ -3277,7 +4804,7 @@ function buildOperatorFindings(report) {
|
|
|
3277
4804
|
}
|
|
3278
4805
|
if (report.servePath.state === "serving_active_pack") {
|
|
3279
4806
|
push("pass", "serve_path_verified", `serve path compiles from active pack ${report.servePath.activePackId ?? "unknown-pack"}`, `selection=${report.servePath.selectionMode ?? "unknown"}; tiers=${report.servePath.contextAttribution.selectionTiers ?? "unknown"}; router=${report.servePath.routerIdentity ?? "none"}; routeFreshness=${report.servePath.freshnessChecksum ?? "unknown"}`);
|
|
3280
|
-
push("pass", "structural_budget_visible", `structural budget resolves to ${report.servePath.resolvedMaxContextBlocks ?? "unknown"} blocks`, `requested=${report.servePath.requestedBudgetStrategy ?? "unknown"}; resolved=${report.servePath.resolvedBudgetStrategy ?? "unknown"}; source=${report.servePath.structuralBudgetSource ?? "unknown"}; evidence=${report.servePath.structuralBudgetEvidence ?? "none"}; pressures=${report.servePath.structuralBudgetPressures ?? "none"}`);
|
|
4807
|
+
push("pass", "structural_budget_visible", `structural budget resolves to ${report.servePath.resolvedMaxContextBlocks ?? "unknown"} blocks`, `origin=${report.servePath.structuralDecision.origin}; basis=${report.servePath.structuralDecision.basis}; requested=${report.servePath.requestedBudgetStrategy ?? "unknown"}; resolved=${report.servePath.resolvedBudgetStrategy ?? "unknown"}; source=${report.servePath.structuralBudgetSource ?? "unknown"}; evidence=${report.servePath.structuralBudgetEvidence ?? "none"}; pressures=${report.servePath.structuralBudgetPressures ?? "none"}`);
|
|
3281
4808
|
}
|
|
3282
4809
|
else if (report.servePath.state === "fail_open_static_context") {
|
|
3283
4810
|
push("warn", "serve_path_fail_open", "serve path would fail open to static context", report.servePath.error ?? "compile probe fell back to static context");
|
|
@@ -3334,6 +4861,22 @@ function buildOperatorFindings(report) {
|
|
|
3334
4861
|
else {
|
|
3335
4862
|
push("warn", "supervision_not_flowing", "the supplied export does not yet show human supervision", `sourcePath=${report.supervision.sourcePath ?? "unknown"}; exportDigest=${report.supervision.exportDigest ?? "unknown"}`);
|
|
3336
4863
|
}
|
|
4864
|
+
if (report.supervision.available) {
|
|
4865
|
+
if (report.supervision.scanSurfaceCount > 0) {
|
|
4866
|
+
push("pass", "scan_surfaces_visible", `scanner surfaces are visible: ${formatList(report.supervision.scanSurfaces)}`, `scanPolicy=${report.supervision.scanPolicy ?? "unknown"}; humanLabels=${report.supervision.humanLabelCount ?? 0}; selfLabels=${report.supervision.selfLabelCount ?? 0}`);
|
|
4867
|
+
}
|
|
4868
|
+
else {
|
|
4869
|
+
push("warn", "scan_surfaces_missing", "scanner surfaces are not visible in the supplied export", `exportDigest=${report.supervision.exportDigest ?? "unknown"}`);
|
|
4870
|
+
}
|
|
4871
|
+
if (report.supervision.totalEventCount !== null && report.supervision.totalEventCount > 0) {
|
|
4872
|
+
if (report.supervision.attributedEventCount === report.supervision.totalEventCount) {
|
|
4873
|
+
push("pass", "turn_attribution_visible", `all supplied events are attributable: ${report.supervision.attributedEventCount}/${report.supervision.totalEventCount}`, `selectionDigests=${report.supervision.selectionDigestCount ?? 0}`);
|
|
4874
|
+
}
|
|
4875
|
+
else {
|
|
4876
|
+
push("warn", "turn_attribution_partial", `some supplied events are unattributed: ${report.supervision.attributedEventCount ?? 0}/${report.supervision.totalEventCount}`, `selectionDigests=${report.supervision.selectionDigestCount ?? 0}`);
|
|
4877
|
+
}
|
|
4878
|
+
}
|
|
4879
|
+
}
|
|
3337
4880
|
if (!report.teacherLoop.available) {
|
|
3338
4881
|
push("warn", "teacher_snapshot_unavailable", "last async no-op reason is not inspectable yet", "pass `--teacher-snapshot <snapshot.json>` to inspect duplicate/no-op handling");
|
|
3339
4882
|
}
|
|
@@ -3415,7 +4958,7 @@ function summarizeCurrentProfileBrainStatusLevel(input) {
|
|
|
3415
4958
|
}
|
|
3416
4959
|
return input.routeFreshness === "updated" ? "ok" : "warn";
|
|
3417
4960
|
}
|
|
3418
|
-
function buildCurrentProfileBrainStatusFromReport(report, policyMode) {
|
|
4961
|
+
function buildCurrentProfileBrainStatusFromReport(report, policyMode, profileId) {
|
|
3419
4962
|
const attached = report.active !== null;
|
|
3420
4963
|
const awaitingFirstExport = isAwaitingFirstExportSlot(report.active);
|
|
3421
4964
|
const routerIdentity = report.servePath.routerIdentity ?? report.learnedRouting.routerIdentity ?? report.active?.routerIdentity ?? null;
|
|
@@ -3440,6 +4983,7 @@ function buildCurrentProfileBrainStatusFromReport(report, policyMode) {
|
|
|
3440
4983
|
profile: {
|
|
3441
4984
|
noun: "Profile",
|
|
3442
4985
|
selector: "current_profile",
|
|
4986
|
+
profileId,
|
|
3443
4987
|
detail: attached
|
|
3444
4988
|
? "The Host resolves the current Profile through the active Attachment boundary only."
|
|
3445
4989
|
: "The current Profile has no active Brain attachment visible at the Host boundary."
|
|
@@ -3496,6 +5040,7 @@ function buildCurrentProfileBrainStatusFromReport(report, policyMode) {
|
|
|
3496
5040
|
usedLearnedRouteFn: report.servePath.usedLearnedRouteFn,
|
|
3497
5041
|
failOpen: report.servePath.fallbackToStaticContext,
|
|
3498
5042
|
awaitingFirstExport,
|
|
5043
|
+
structuralDecision: report.servePath.structuralDecision,
|
|
3499
5044
|
detail: report.servePath.state === "serving_active_pack"
|
|
3500
5045
|
? awaitingFirstExport
|
|
3501
5046
|
? `current profile is serving seed-state pack ${activePackId ?? "unknown"} while awaiting the first exported turn`
|
|
@@ -3514,6 +5059,7 @@ function buildCurrentProfileBrainStatusFromReport(report, policyMode) {
|
|
|
3514
5059
|
export function buildOperatorSurfaceReport(input) {
|
|
3515
5060
|
const activationRoot = path.resolve(normalizeNonEmptyString(input.activationRoot, "activationRoot"));
|
|
3516
5061
|
const updatedAt = normalizeIsoTimestamp(input.updatedAt, "updatedAt", new Date().toISOString());
|
|
5062
|
+
const brainAttachmentPolicy = normalizeBrainAttachmentPolicy(input.brainAttachmentPolicy);
|
|
3517
5063
|
const inspection = inspectActivationState(activationRoot, updatedAt);
|
|
3518
5064
|
const observability = describeActivationObservability(activationRoot, "active", {
|
|
3519
5065
|
updatedAt
|
|
@@ -3571,7 +5117,8 @@ export function buildOperatorSurfaceReport(input) {
|
|
|
3571
5117
|
supervision: summarizeSupervision(input),
|
|
3572
5118
|
learning: summarizeAlwaysOnLearning(input, active),
|
|
3573
5119
|
teacherLoop: summarizeTeacherLoop(input),
|
|
3574
|
-
principal: summarizePrincipalObservability(input, active)
|
|
5120
|
+
principal: summarizePrincipalObservability(input, active),
|
|
5121
|
+
manyProfile: summarizeManyProfileSupport(brainAttachmentPolicy)
|
|
3575
5122
|
};
|
|
3576
5123
|
const findings = buildOperatorFindings(reportBase);
|
|
3577
5124
|
return {
|
|
@@ -3582,7 +5129,7 @@ export function buildOperatorSurfaceReport(input) {
|
|
|
3582
5129
|
}
|
|
3583
5130
|
export function describeCurrentProfileBrainStatus(input) {
|
|
3584
5131
|
const report = buildOperatorSurfaceReport(input);
|
|
3585
|
-
return buildCurrentProfileBrainStatusFromReport(report,
|
|
5132
|
+
return buildCurrentProfileBrainStatusFromReport(report, report.manyProfile.declaredAttachmentPolicy, normalizeOptionalString(input.profileId) ?? null);
|
|
3586
5133
|
}
|
|
3587
5134
|
export function formatOperatorRollbackReport(result) {
|
|
3588
5135
|
const header = result.allowed ? (result.dryRun ? "ROLLBACK ready" : "ROLLBACK ok") : "ROLLBACK blocked";
|
|
@@ -3650,4 +5197,9 @@ export { CONTRACT_IDS, buildNormalizedEventExport, createFeedbackEvent, createIn
|
|
|
3650
5197
|
export { describeNormalizedEventExportObservability } from "@openclawbrain/event-export";
|
|
3651
5198
|
export { describeCompileFallbackUsage } from "@openclawbrain/compiler";
|
|
3652
5199
|
export { describeActivationObservability, inspectActivationState, rollbackActivePack } from "@openclawbrain/pack-format";
|
|
5200
|
+
export { createOpenClawLocalSessionTail, OpenClawLocalSessionTail } from "./session-tail.js";
|
|
5201
|
+
export { discoverOpenClawMainSessionStores, loadOpenClawSessionIndex, readOpenClawAcpStreamFile, readOpenClawSessionFile } from "./session-store.js";
|
|
5202
|
+
export { buildPassiveLearningSessionExportFromOpenClawSessionStore, buildPassiveLearningStoreExportFromOpenClawSessionIndex } from "./local-session-passive-learning.js";
|
|
5203
|
+
export { resolveActivationRoot } from "./resolve-activation-root.js";
|
|
5204
|
+
export { runDaemonCommand, parseDaemonArgs } from "./daemon.js";
|
|
3653
5205
|
//# sourceMappingURL=index.js.map
|