@openclawbrain/openclaw 0.1.10 → 0.1.12
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 +12 -6
- package/dist/src/cli.d.ts +14 -1
- package/dist/src/cli.js +223 -7
- package/dist/src/cli.js.map +1 -1
- package/dist/src/index.d.ts +420 -5
- package/dist/src/index.js +1909 -76
- package/dist/src/index.js.map +1 -1
- package/dist/src/learning-spine.d.ts +1 -0
- package/dist/src/learning-spine.js +9 -2
- package/dist/src/learning-spine.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/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/package.json +7 -6
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
|
+
};
|
|
854
1864
|
}
|
|
855
|
-
|
|
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;
|
|
1915
|
+
}
|
|
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 = [
|
|
@@ -910,6 +1987,222 @@ function classifyCompileFailure(error, activationRoot) {
|
|
|
910
1987
|
}
|
|
911
1988
|
return failOpenCompileResult(error, resolvedActivationRoot);
|
|
912
1989
|
}
|
|
1990
|
+
function uniqueNotes(notes) {
|
|
1991
|
+
return [...new Set(notes.filter((note) => note.length > 0))];
|
|
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
|
+
}
|
|
1999
|
+
function clampInteger(value, minimum, maximum) {
|
|
2000
|
+
return Math.min(maximum, Math.max(minimum, Math.round(value)));
|
|
2001
|
+
}
|
|
2002
|
+
export function deriveEmpiricalStructuralBudget(input) {
|
|
2003
|
+
const requestedStrategy = input.requestedStrategy ?? "fixed_v1";
|
|
2004
|
+
const defaultMaxContextBlocks = input.defaultMaxContextBlocks ?? 4;
|
|
2005
|
+
const minimumMaxContextBlocks = input.minimumMaxContextBlocks ?? 2;
|
|
2006
|
+
const maximumMaxContextBlocks = input.maximumMaxContextBlocks ?? 6;
|
|
2007
|
+
if (input.requestedMaxContextBlocks !== undefined) {
|
|
2008
|
+
const maxContextBlocks = clampInteger(input.requestedMaxContextBlocks, 0, Number.MAX_SAFE_INTEGER);
|
|
2009
|
+
return {
|
|
2010
|
+
requestedStrategy,
|
|
2011
|
+
effectiveStrategy: requestedStrategy,
|
|
2012
|
+
maxContextBlocks,
|
|
2013
|
+
defaultMaxContextBlocks,
|
|
2014
|
+
evidence: { split: 0, merge: 0, prune: 0, connect: 0 },
|
|
2015
|
+
evidenceTotal: 0,
|
|
2016
|
+
tendencies: { split: 0, merge: 0, prune: 0, connect: 0 },
|
|
2017
|
+
notes: [
|
|
2018
|
+
`requested_budget_strategy=${requestedStrategy}`,
|
|
2019
|
+
`requested_max_context_blocks=${maxContextBlocks}`,
|
|
2020
|
+
`resolved_budget_strategy=${requestedStrategy}`,
|
|
2021
|
+
`resolved_max_context_blocks=${maxContextBlocks}`,
|
|
2022
|
+
"structural_budget_source=caller_override"
|
|
2023
|
+
]
|
|
2024
|
+
};
|
|
2025
|
+
}
|
|
2026
|
+
if (requestedStrategy !== "empirical_v1") {
|
|
2027
|
+
return {
|
|
2028
|
+
requestedStrategy,
|
|
2029
|
+
effectiveStrategy: requestedStrategy,
|
|
2030
|
+
maxContextBlocks: defaultMaxContextBlocks,
|
|
2031
|
+
defaultMaxContextBlocks,
|
|
2032
|
+
evidence: { split: 0, merge: 0, prune: 0, connect: 0 },
|
|
2033
|
+
evidenceTotal: 0,
|
|
2034
|
+
tendencies: { split: 0, merge: 0, prune: 0, connect: 0 },
|
|
2035
|
+
notes: [
|
|
2036
|
+
`requested_budget_strategy=${requestedStrategy}`,
|
|
2037
|
+
`resolved_budget_strategy=${requestedStrategy}`,
|
|
2038
|
+
`resolved_max_context_blocks=${defaultMaxContextBlocks}`,
|
|
2039
|
+
`structural_budget_source=${requestedStrategy === "fixed_v1" ? "fixed_default" : "fixed_fallback"}`
|
|
2040
|
+
]
|
|
2041
|
+
};
|
|
2042
|
+
}
|
|
2043
|
+
const evidence = {
|
|
2044
|
+
split: Math.max(0, input.evolution?.structuralOps.split ?? 0),
|
|
2045
|
+
merge: Math.max(0, input.evolution?.structuralOps.merge ?? 0),
|
|
2046
|
+
prune: Math.max(0, Math.max(input.evolution?.structuralOps.prune ?? 0, input.evolution?.prunedBlockIds.length ?? 0)),
|
|
2047
|
+
connect: Math.max(0, input.evolution?.structuralOps.connect ?? 0)
|
|
2048
|
+
};
|
|
2049
|
+
const evidenceTotal = evidence.split + evidence.merge + evidence.prune + evidence.connect;
|
|
2050
|
+
if (evidenceTotal === 0) {
|
|
2051
|
+
return {
|
|
2052
|
+
requestedStrategy,
|
|
2053
|
+
effectiveStrategy: "fixed_v1",
|
|
2054
|
+
maxContextBlocks: defaultMaxContextBlocks,
|
|
2055
|
+
defaultMaxContextBlocks,
|
|
2056
|
+
evidence,
|
|
2057
|
+
evidenceTotal,
|
|
2058
|
+
tendencies: { split: 0, merge: 0, prune: 0, connect: 0 },
|
|
2059
|
+
notes: [
|
|
2060
|
+
`requested_budget_strategy=${requestedStrategy}`,
|
|
2061
|
+
"resolved_budget_strategy=fixed_v1",
|
|
2062
|
+
`resolved_max_context_blocks=${defaultMaxContextBlocks}`,
|
|
2063
|
+
"structural_budget_source=no_evidence_fallback"
|
|
2064
|
+
]
|
|
2065
|
+
};
|
|
2066
|
+
}
|
|
2067
|
+
const tendencies = {
|
|
2068
|
+
split: evidence.split / evidenceTotal,
|
|
2069
|
+
merge: evidence.merge / evidenceTotal,
|
|
2070
|
+
prune: evidence.prune / evidenceTotal,
|
|
2071
|
+
connect: evidence.connect / evidenceTotal
|
|
2072
|
+
};
|
|
2073
|
+
const expansionPressure = tendencies.split + tendencies.connect;
|
|
2074
|
+
const contractionPressure = tendencies.merge + tendencies.prune;
|
|
2075
|
+
const directionalPressure = expansionPressure - contractionPressure;
|
|
2076
|
+
const maxContextBlocks = clampInteger(defaultMaxContextBlocks + directionalPressure * 2, minimumMaxContextBlocks, maximumMaxContextBlocks);
|
|
2077
|
+
return {
|
|
2078
|
+
requestedStrategy,
|
|
2079
|
+
effectiveStrategy: requestedStrategy,
|
|
2080
|
+
maxContextBlocks,
|
|
2081
|
+
defaultMaxContextBlocks,
|
|
2082
|
+
evidence,
|
|
2083
|
+
evidenceTotal,
|
|
2084
|
+
tendencies,
|
|
2085
|
+
notes: [
|
|
2086
|
+
`requested_budget_strategy=${requestedStrategy}`,
|
|
2087
|
+
`resolved_budget_strategy=${requestedStrategy}`,
|
|
2088
|
+
`resolved_max_context_blocks=${maxContextBlocks}`,
|
|
2089
|
+
"structural_budget_source=graph_evolution_empirical_v1",
|
|
2090
|
+
`structural_budget_evidence=split:${evidence.split},merge:${evidence.merge},prune:${evidence.prune},connect:${evidence.connect},total:${evidenceTotal}`,
|
|
2091
|
+
`structural_budget_tendencies=split:${tendencies.split.toFixed(4)},merge:${tendencies.merge.toFixed(4)},prune:${tendencies.prune.toFixed(4)},connect:${tendencies.connect.toFixed(4)}`,
|
|
2092
|
+
`structural_budget_pressures=expand:${expansionPressure.toFixed(4)},contract:${contractionPressure.toFixed(4)},directional:${directionalPressure.toFixed(4)}`
|
|
2093
|
+
]
|
|
2094
|
+
};
|
|
2095
|
+
}
|
|
2096
|
+
export function deriveEmpiricalStructuralBudgetFromCompileSignals(input) {
|
|
2097
|
+
const requestedStrategy = input.requestedStrategy ?? "fixed_v1";
|
|
2098
|
+
const defaultMaxContextBlocks = input.defaultMaxContextBlocks ?? 4;
|
|
2099
|
+
const minimumMaxContextBlocks = input.minimumMaxContextBlocks ?? 2;
|
|
2100
|
+
const maximumMaxContextBlocks = input.maximumMaxContextBlocks ?? 6;
|
|
2101
|
+
if (input.requestedMaxContextBlocks !== undefined) {
|
|
2102
|
+
const maxContextBlocks = clampInteger(input.requestedMaxContextBlocks, 0, Number.MAX_SAFE_INTEGER);
|
|
2103
|
+
return {
|
|
2104
|
+
requestedStrategy,
|
|
2105
|
+
effectiveStrategy: requestedStrategy,
|
|
2106
|
+
maxContextBlocks,
|
|
2107
|
+
defaultMaxContextBlocks,
|
|
2108
|
+
evidence: { split: 0, merge: 0, prune: 0, connect: 0 },
|
|
2109
|
+
evidenceTotal: 0,
|
|
2110
|
+
tendencies: { split: 0, merge: 0, prune: 0, connect: 0 },
|
|
2111
|
+
notes: [
|
|
2112
|
+
`requested_budget_strategy=${requestedStrategy}`,
|
|
2113
|
+
`requested_max_context_blocks=${maxContextBlocks}`,
|
|
2114
|
+
`resolved_budget_strategy=${requestedStrategy}`,
|
|
2115
|
+
`resolved_max_context_blocks=${maxContextBlocks}`,
|
|
2116
|
+
"structural_budget_source=caller_override"
|
|
2117
|
+
]
|
|
2118
|
+
};
|
|
2119
|
+
}
|
|
2120
|
+
if (requestedStrategy !== "empirical_v1") {
|
|
2121
|
+
return {
|
|
2122
|
+
requestedStrategy,
|
|
2123
|
+
effectiveStrategy: requestedStrategy,
|
|
2124
|
+
maxContextBlocks: defaultMaxContextBlocks,
|
|
2125
|
+
defaultMaxContextBlocks,
|
|
2126
|
+
evidence: { split: 0, merge: 0, prune: 0, connect: 0 },
|
|
2127
|
+
evidenceTotal: 0,
|
|
2128
|
+
tendencies: { split: 0, merge: 0, prune: 0, connect: 0 },
|
|
2129
|
+
notes: [
|
|
2130
|
+
`requested_budget_strategy=${requestedStrategy}`,
|
|
2131
|
+
`resolved_budget_strategy=${requestedStrategy}`,
|
|
2132
|
+
`resolved_max_context_blocks=${defaultMaxContextBlocks}`,
|
|
2133
|
+
`structural_budget_source=${requestedStrategy === "fixed_v1" ? "fixed_default" : "fixed_fallback"}`
|
|
2134
|
+
]
|
|
2135
|
+
};
|
|
2136
|
+
}
|
|
2137
|
+
const compileEvidence = {
|
|
2138
|
+
expansionCandidates: Math.max(0, (input.structuralSignals?.matchedCandidateCount ?? 0) - (input.structuralSignals?.selectedMatchedCount ?? 0)),
|
|
2139
|
+
traversalActivations: Math.max(0, input.structuralSignals?.traversalActivatedCount ?? 0),
|
|
2140
|
+
overlapPruned: Math.max(0, input.structuralSignals?.overlapPrunedCount ?? 0)
|
|
2141
|
+
};
|
|
2142
|
+
const evidence = {
|
|
2143
|
+
split: compileEvidence.expansionCandidates,
|
|
2144
|
+
merge: 0,
|
|
2145
|
+
prune: compileEvidence.overlapPruned,
|
|
2146
|
+
connect: compileEvidence.traversalActivations
|
|
2147
|
+
};
|
|
2148
|
+
const evidenceTotal = evidence.split + evidence.merge + evidence.prune + evidence.connect;
|
|
2149
|
+
if (evidenceTotal === 0) {
|
|
2150
|
+
return {
|
|
2151
|
+
requestedStrategy,
|
|
2152
|
+
effectiveStrategy: "fixed_v1",
|
|
2153
|
+
maxContextBlocks: defaultMaxContextBlocks,
|
|
2154
|
+
defaultMaxContextBlocks,
|
|
2155
|
+
evidence,
|
|
2156
|
+
evidenceTotal,
|
|
2157
|
+
tendencies: { split: 0, merge: 0, prune: 0, connect: 0 },
|
|
2158
|
+
notes: [
|
|
2159
|
+
`requested_budget_strategy=${requestedStrategy}`,
|
|
2160
|
+
"resolved_budget_strategy=fixed_v1",
|
|
2161
|
+
`resolved_max_context_blocks=${defaultMaxContextBlocks}`,
|
|
2162
|
+
"structural_budget_source=no_compile_signal_evidence_fallback"
|
|
2163
|
+
]
|
|
2164
|
+
};
|
|
2165
|
+
}
|
|
2166
|
+
const tendencies = {
|
|
2167
|
+
split: evidence.split / evidenceTotal,
|
|
2168
|
+
merge: 0,
|
|
2169
|
+
prune: evidence.prune / evidenceTotal,
|
|
2170
|
+
connect: evidence.connect / evidenceTotal
|
|
2171
|
+
};
|
|
2172
|
+
const expansionPressure = tendencies.split + tendencies.connect;
|
|
2173
|
+
const contractionPressure = tendencies.prune;
|
|
2174
|
+
const directionalPressure = expansionPressure - contractionPressure;
|
|
2175
|
+
const maxContextBlocks = clampInteger(defaultMaxContextBlocks + directionalPressure * 2, minimumMaxContextBlocks, maximumMaxContextBlocks);
|
|
2176
|
+
return {
|
|
2177
|
+
requestedStrategy,
|
|
2178
|
+
effectiveStrategy: requestedStrategy,
|
|
2179
|
+
maxContextBlocks,
|
|
2180
|
+
defaultMaxContextBlocks,
|
|
2181
|
+
evidence,
|
|
2182
|
+
evidenceTotal,
|
|
2183
|
+
tendencies,
|
|
2184
|
+
notes: [
|
|
2185
|
+
`requested_budget_strategy=${requestedStrategy}`,
|
|
2186
|
+
`resolved_budget_strategy=${requestedStrategy}`,
|
|
2187
|
+
`resolved_max_context_blocks=${maxContextBlocks}`,
|
|
2188
|
+
"structural_budget_source=compile_structural_signals_empirical_v1",
|
|
2189
|
+
`structural_budget_compile_evidence=matched_unselected:${compileEvidence.expansionCandidates},traversal:${compileEvidence.traversalActivations},overlap_pruned:${compileEvidence.overlapPruned},total:${evidenceTotal}`,
|
|
2190
|
+
`structural_budget_tendencies=split:${tendencies.split.toFixed(4)},merge:${tendencies.merge.toFixed(4)},prune:${tendencies.prune.toFixed(4)},connect:${tendencies.connect.toFixed(4)}`,
|
|
2191
|
+
`structural_budget_pressures=expand:${expansionPressure.toFixed(4)},contract:${contractionPressure.toFixed(4)},directional:${directionalPressure.toFixed(4)}`
|
|
2192
|
+
]
|
|
2193
|
+
};
|
|
2194
|
+
}
|
|
2195
|
+
function resolveCompileBudget(target, input) {
|
|
2196
|
+
const pack = loadPackFromActivation(target.activationRoot, "active");
|
|
2197
|
+
return deriveEmpiricalStructuralBudget({
|
|
2198
|
+
requestedStrategy: input.budgetStrategy ?? "empirical_v1",
|
|
2199
|
+
...(input.maxContextBlocks !== undefined ? { requestedMaxContextBlocks: input.maxContextBlocks } : {}),
|
|
2200
|
+
...(pack?.graph.evolution !== undefined ? { evolution: pack.graph.evolution } : {}),
|
|
2201
|
+
defaultMaxContextBlocks: 4,
|
|
2202
|
+
minimumMaxContextBlocks: 2,
|
|
2203
|
+
maximumMaxContextBlocks: 6
|
|
2204
|
+
});
|
|
2205
|
+
}
|
|
913
2206
|
export function resolveActivePackForCompile(activationRoot) {
|
|
914
2207
|
const resolvedActivationRoot = path.resolve(normalizeNonEmptyString(activationRoot, "activationRoot"));
|
|
915
2208
|
const inspection = inspectActivationState(resolvedActivationRoot);
|
|
@@ -932,11 +2225,12 @@ export function compileRuntimeContext(input) {
|
|
|
932
2225
|
const runtimeHints = normalizeRuntimeHints(input.runtimeHints);
|
|
933
2226
|
try {
|
|
934
2227
|
const target = resolveActivePackForCompile(activationRoot);
|
|
2228
|
+
const resolvedBudget = resolveCompileBudget(target, input);
|
|
935
2229
|
const compile = compileRuntimeFromActivation(activationRoot, {
|
|
936
2230
|
contract: CONTRACT_IDS.runtimeCompile,
|
|
937
2231
|
agentId,
|
|
938
2232
|
userMessage: normalizeNonEmptyString(input.message, "message"),
|
|
939
|
-
maxContextBlocks:
|
|
2233
|
+
maxContextBlocks: resolvedBudget.maxContextBlocks,
|
|
940
2234
|
...(input.maxContextChars !== undefined
|
|
941
2235
|
? { maxContextChars: normalizeNonNegativeInteger(input.maxContextChars, "maxContextChars", input.maxContextChars) }
|
|
942
2236
|
: {}),
|
|
@@ -949,7 +2243,7 @@ export function compileRuntimeContext(input) {
|
|
|
949
2243
|
...compile.response,
|
|
950
2244
|
diagnostics: {
|
|
951
2245
|
...compile.response.diagnostics,
|
|
952
|
-
notes: [...compile.response.diagnostics.notes, "OpenClaw remains the runtime owner"]
|
|
2246
|
+
notes: uniqueNotes([...compile.response.diagnostics.notes, ...resolvedBudget.notes, "OpenClaw remains the runtime owner"])
|
|
953
2247
|
}
|
|
954
2248
|
};
|
|
955
2249
|
return {
|
|
@@ -981,6 +2275,97 @@ function readDiagnosticNoteList(notes, prefix) {
|
|
|
981
2275
|
.map((entry) => entry.trim())
|
|
982
2276
|
.filter((entry) => entry.length > 0);
|
|
983
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
|
+
}
|
|
984
2369
|
function sortedUniqueStrings(values) {
|
|
985
2370
|
return [...new Set(values.filter((value) => value.length > 0))].sort((left, right) => left.localeCompare(right));
|
|
986
2371
|
}
|
|
@@ -1147,6 +2532,7 @@ function buildAttachStatusCompileInput(activationRoot, compile) {
|
|
|
1147
2532
|
agentId: normalizeOptionalString(compile?.agentId) ?? `${DEFAULT_AGENT_ID}-attach-status`,
|
|
1148
2533
|
message: normalizeOptionalString(compile?.message) ?? DEFAULT_ATTACH_STATUS_MESSAGE,
|
|
1149
2534
|
...(compile?.maxContextBlocks !== undefined ? { maxContextBlocks: compile.maxContextBlocks } : {}),
|
|
2535
|
+
...(compile?.budgetStrategy !== undefined ? { budgetStrategy: compile.budgetStrategy } : {}),
|
|
1150
2536
|
...(compile?.maxContextChars !== undefined ? { maxContextChars: compile.maxContextChars } : {}),
|
|
1151
2537
|
...(compile?.mode !== undefined ? { mode: compile.mode } : {}),
|
|
1152
2538
|
...(compile?.compactionMode !== undefined ? { compactionMode: compile.compactionMode } : {}),
|
|
@@ -1315,11 +2701,14 @@ export function bootstrapRuntimeAttach(input) {
|
|
|
1315
2701
|
});
|
|
1316
2702
|
const currentProfile = describeCurrentProfileBrainStatus({
|
|
1317
2703
|
activationRoot,
|
|
1318
|
-
updatedAt: activatedAt
|
|
2704
|
+
updatedAt: activatedAt,
|
|
2705
|
+
...(input.brainAttachmentPolicy !== undefined ? { brainAttachmentPolicy: input.brainAttachmentPolicy } : {}),
|
|
2706
|
+
...(input.profileId !== undefined ? { profileId: input.profileId } : {})
|
|
1319
2707
|
});
|
|
1320
2708
|
return {
|
|
1321
2709
|
runtimeOwner: "openclaw",
|
|
1322
2710
|
profileSelector,
|
|
2711
|
+
operatorReadScope: "current_profile_only",
|
|
1323
2712
|
activationRoot,
|
|
1324
2713
|
packRoot,
|
|
1325
2714
|
packId: descriptor.manifest.packId,
|
|
@@ -1328,12 +2717,80 @@ export function bootstrapRuntimeAttach(input) {
|
|
|
1328
2717
|
currentProfile,
|
|
1329
2718
|
nextSteps: buildBootstrapRuntimeAttachNextSteps({
|
|
1330
2719
|
activationRoot,
|
|
2720
|
+
profileSelector,
|
|
1331
2721
|
currentProfile
|
|
1332
2722
|
})
|
|
1333
2723
|
};
|
|
1334
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
|
+
}
|
|
1335
2790
|
function buildRuntimeTurnAttribution(input) {
|
|
2791
|
+
const profileSelector = normalizeRuntimeProfileSelector(input.turn.profileSelector, "profileSelector");
|
|
1336
2792
|
const brainAttachmentPolicy = normalizeBrainAttachmentPolicy(input.turn.brainAttachmentPolicy);
|
|
2793
|
+
const profileId = normalizeOptionalString(input.turn.profileId) ?? null;
|
|
1337
2794
|
if (input.compileResult.ok) {
|
|
1338
2795
|
const notes = input.compileResult.compileResponse.diagnostics.notes;
|
|
1339
2796
|
const contextAttribution = buildContextAttributionSummary({
|
|
@@ -1345,7 +2802,8 @@ function buildRuntimeTurnAttribution(input) {
|
|
|
1345
2802
|
});
|
|
1346
2803
|
return {
|
|
1347
2804
|
hostRuntimeOwner: "openclaw",
|
|
1348
|
-
profileSelector
|
|
2805
|
+
profileSelector,
|
|
2806
|
+
profileId,
|
|
1349
2807
|
brainAttachmentPolicy,
|
|
1350
2808
|
brainStatus: "serving_active_pack",
|
|
1351
2809
|
activePackId: input.compileResult.activePackId,
|
|
@@ -1353,6 +2811,17 @@ function buildRuntimeTurnAttribution(input) {
|
|
|
1353
2811
|
routerIdentity: input.compileResult.compileResponse.diagnostics.routerIdentity,
|
|
1354
2812
|
selectionDigest: input.compileResult.compileResponse.diagnostics.selectionDigest,
|
|
1355
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
|
+
}),
|
|
1356
2825
|
contextEvidence: contextAttribution.evidence === "unprobed" ? null : contextAttribution.evidence
|
|
1357
2826
|
};
|
|
1358
2827
|
}
|
|
@@ -1363,7 +2832,8 @@ function buildRuntimeTurnAttribution(input) {
|
|
|
1363
2832
|
});
|
|
1364
2833
|
return {
|
|
1365
2834
|
hostRuntimeOwner: "openclaw",
|
|
1366
|
-
profileSelector
|
|
2835
|
+
profileSelector,
|
|
2836
|
+
profileId,
|
|
1367
2837
|
brainAttachmentPolicy,
|
|
1368
2838
|
brainStatus: input.compileResult.hardRequirementViolated ? "hard_fail" : "fail_open_static_context",
|
|
1369
2839
|
activePackId: null,
|
|
@@ -1371,6 +2841,17 @@ function buildRuntimeTurnAttribution(input) {
|
|
|
1371
2841
|
routerIdentity: null,
|
|
1372
2842
|
selectionDigest: null,
|
|
1373
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
|
+
}),
|
|
1374
2855
|
contextEvidence: contextAttribution.evidence === "unprobed" ? null : contextAttribution.evidence
|
|
1375
2856
|
};
|
|
1376
2857
|
}
|
|
@@ -1388,10 +2869,6 @@ function buildCompileInteractionEvent(input) {
|
|
|
1388
2869
|
sessionId: input.turn.sessionId,
|
|
1389
2870
|
source: input.sourceStream
|
|
1390
2871
|
});
|
|
1391
|
-
const attribution = buildRuntimeTurnAttribution({
|
|
1392
|
-
turn: input.turn,
|
|
1393
|
-
compileResult: input.compileResult
|
|
1394
|
-
});
|
|
1395
2872
|
return createInteractionEvent({
|
|
1396
2873
|
eventId,
|
|
1397
2874
|
agentId: input.agentId,
|
|
@@ -1405,7 +2882,8 @@ function buildCompileInteractionEvent(input) {
|
|
|
1405
2882
|
stream: input.sourceStream
|
|
1406
2883
|
},
|
|
1407
2884
|
packId: input.compileResult.compileResponse.packId,
|
|
1408
|
-
|
|
2885
|
+
principal: buildRuntimeSelfPrincipal(input.turn),
|
|
2886
|
+
attribution: input.attribution
|
|
1409
2887
|
});
|
|
1410
2888
|
}
|
|
1411
2889
|
function buildDeliveryInteractionEvent(input) {
|
|
@@ -1426,10 +2904,6 @@ function buildDeliveryInteractionEvent(input) {
|
|
|
1426
2904
|
sessionId: input.turn.sessionId,
|
|
1427
2905
|
source: input.sourceStream
|
|
1428
2906
|
});
|
|
1429
|
-
const attribution = buildRuntimeTurnAttribution({
|
|
1430
|
-
turn: input.turn,
|
|
1431
|
-
compileResult: input.compileResult
|
|
1432
|
-
});
|
|
1433
2907
|
return createInteractionEvent({
|
|
1434
2908
|
eventId,
|
|
1435
2909
|
agentId: input.agentId,
|
|
@@ -1442,17 +2916,13 @@ function buildDeliveryInteractionEvent(input) {
|
|
|
1442
2916
|
runtimeOwner: "openclaw",
|
|
1443
2917
|
stream: input.sourceStream
|
|
1444
2918
|
},
|
|
1445
|
-
attribution,
|
|
2919
|
+
attribution: input.attribution,
|
|
1446
2920
|
...(input.compileResult.ok ? { packId: input.compileResult.compileResponse.packId } : {}),
|
|
1447
2921
|
...(messageId !== undefined ? { messageId } : {})
|
|
1448
2922
|
});
|
|
1449
2923
|
}
|
|
1450
2924
|
function buildFeedbackEvents(input) {
|
|
1451
2925
|
const feedbackItems = input.turn.feedback ?? [];
|
|
1452
|
-
const attribution = buildRuntimeTurnAttribution({
|
|
1453
|
-
turn: input.turn,
|
|
1454
|
-
compileResult: input.compileResult
|
|
1455
|
-
});
|
|
1456
2926
|
return feedbackItems.map((item, index) => {
|
|
1457
2927
|
if (item === null) {
|
|
1458
2928
|
throw new Error(`feedback[${index}] must be an object`);
|
|
@@ -1476,6 +2946,12 @@ function buildFeedbackEvents(input) {
|
|
|
1476
2946
|
source: input.sourceStream
|
|
1477
2947
|
});
|
|
1478
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
|
+
});
|
|
1479
2955
|
return createFeedbackEvent({
|
|
1480
2956
|
eventId,
|
|
1481
2957
|
agentId: input.agentId,
|
|
@@ -1489,14 +2965,16 @@ function buildFeedbackEvents(input) {
|
|
|
1489
2965
|
stream: input.sourceStream
|
|
1490
2966
|
},
|
|
1491
2967
|
content,
|
|
1492
|
-
attribution,
|
|
2968
|
+
attribution: input.attribution,
|
|
1493
2969
|
...(messageId !== undefined ? { messageId } : {}),
|
|
2970
|
+
...(principal === undefined ? {} : { principal }),
|
|
1494
2971
|
...(relatedInteractionId !== undefined ? { relatedInteractionId } : {})
|
|
1495
2972
|
});
|
|
1496
2973
|
});
|
|
1497
2974
|
}
|
|
1498
2975
|
export function buildNormalizedRuntimeEventExport(turn, compileResult) {
|
|
1499
2976
|
const agentId = normalizeOptionalString(turn.agentId) ?? process.env.OPENCLAWBRAIN_AGENT_ID ?? DEFAULT_AGENT_ID;
|
|
2977
|
+
const profileSelector = normalizeRuntimeProfileSelector(turn.profileSelector, "profileSelector");
|
|
1500
2978
|
const sessionId = normalizeNonEmptyString(turn.sessionId, "sessionId");
|
|
1501
2979
|
const channel = normalizeNonEmptyString(turn.channel, "channel");
|
|
1502
2980
|
const sourceStream = normalizeOptionalString(turn.sourceStream) ?? `openclaw/runtime/${channel}`;
|
|
@@ -1505,12 +2983,19 @@ export function buildNormalizedRuntimeEventExport(turn, compileResult) {
|
|
|
1505
2983
|
const normalizedTurn = {
|
|
1506
2984
|
...turn,
|
|
1507
2985
|
agentId,
|
|
2986
|
+
profileSelector,
|
|
1508
2987
|
channel,
|
|
1509
2988
|
sessionId
|
|
1510
2989
|
};
|
|
2990
|
+
const attribution = buildRuntimeTurnAttribution({
|
|
2991
|
+
turn: normalizedTurn,
|
|
2992
|
+
compileResult,
|
|
2993
|
+
sourceStream
|
|
2994
|
+
});
|
|
1511
2995
|
const compileInteraction = buildCompileInteractionEvent({
|
|
1512
2996
|
turn: normalizedTurn,
|
|
1513
2997
|
compileResult,
|
|
2998
|
+
attribution,
|
|
1514
2999
|
sourceStream,
|
|
1515
3000
|
nextSequence,
|
|
1516
3001
|
createdAt: compileCreatedAt,
|
|
@@ -1518,6 +3003,7 @@ export function buildNormalizedRuntimeEventExport(turn, compileResult) {
|
|
|
1518
3003
|
});
|
|
1519
3004
|
const feedbackEvents = buildFeedbackEvents({
|
|
1520
3005
|
turn: normalizedTurn,
|
|
3006
|
+
attribution,
|
|
1521
3007
|
sourceStream,
|
|
1522
3008
|
nextSequence,
|
|
1523
3009
|
defaultCreatedAt: compileCreatedAt,
|
|
@@ -1528,6 +3014,7 @@ export function buildNormalizedRuntimeEventExport(turn, compileResult) {
|
|
|
1528
3014
|
const deliveryInteraction = buildDeliveryInteractionEvent({
|
|
1529
3015
|
turn: normalizedTurn,
|
|
1530
3016
|
compileResult,
|
|
3017
|
+
attribution,
|
|
1531
3018
|
sourceStream,
|
|
1532
3019
|
nextSequence,
|
|
1533
3020
|
defaultCreatedAt: compileCreatedAt,
|
|
@@ -1566,21 +3053,33 @@ export function writeRuntimeEventExportBundle(turn, normalizedEventExport) {
|
|
|
1566
3053
|
payloadPath: RUNTIME_EVENT_EXPORT_BUNDLE_LAYOUT.payload,
|
|
1567
3054
|
normalizedEventExport
|
|
1568
3055
|
});
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
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
|
+
});
|
|
1584
3083
|
}
|
|
1585
3084
|
export function runRuntimeTurn(turn, options = {}) {
|
|
1586
3085
|
const agentId = normalizeOptionalString(turn.agentId);
|
|
@@ -1589,6 +3088,7 @@ export function runRuntimeTurn(turn, options = {}) {
|
|
|
1589
3088
|
message: normalizeNonEmptyString(turn.userMessage, "userMessage"),
|
|
1590
3089
|
...(agentId !== undefined ? { agentId } : {}),
|
|
1591
3090
|
...(turn.maxContextBlocks !== undefined ? { maxContextBlocks: turn.maxContextBlocks } : {}),
|
|
3091
|
+
...(turn.budgetStrategy !== undefined ? { budgetStrategy: turn.budgetStrategy } : {}),
|
|
1592
3092
|
...(turn.mode !== undefined ? { mode: turn.mode } : {}),
|
|
1593
3093
|
...(turn.runtimeHints !== undefined ? { runtimeHints: turn.runtimeHints } : {})
|
|
1594
3094
|
};
|
|
@@ -1701,6 +3201,7 @@ export function runContinuousProductLoopTurn(input) {
|
|
|
1701
3201
|
const mergedHistory = mergeRuntimeEventHistory(currentState, normalizedEventExport);
|
|
1702
3202
|
currentState.interactionEvents = mergedHistory.interactionEvents;
|
|
1703
3203
|
currentState.feedbackEvents = mergedHistory.feedbackEvents;
|
|
3204
|
+
const compileStructuralSignals = turnResult.ok ? turnResult.compileResponse.diagnostics.structuralSignals : undefined;
|
|
1704
3205
|
try {
|
|
1705
3206
|
let activeBeforePack = null;
|
|
1706
3207
|
try {
|
|
@@ -1719,6 +3220,7 @@ export function runContinuousProductLoopTurn(input) {
|
|
|
1719
3220
|
builtAt: normalizeIsoTimestamp(input.candidateBuiltAt, "candidateBuiltAt", normalizedEventExport.range.lastCreatedAt ?? normalizedEventExport.range.firstCreatedAt),
|
|
1720
3221
|
...(input.offlineArtifacts !== undefined ? { offlineArtifacts: input.offlineArtifacts } : {}),
|
|
1721
3222
|
...(input.structuralOps !== undefined ? { structuralOps: input.structuralOps } : {}),
|
|
3223
|
+
...(compileStructuralSignals !== undefined ? { compileStructuralSignals } : {}),
|
|
1722
3224
|
...(input.sparseFeedback !== undefined ? { sparseFeedback: input.sparseFeedback } : {}),
|
|
1723
3225
|
...(input.liveSliceSize !== undefined ? { liveSliceSize: input.liveSliceSize } : {}),
|
|
1724
3226
|
...(input.backfillSliceSize !== undefined ? { backfillSliceSize: input.backfillSliceSize } : {}),
|
|
@@ -2029,6 +3531,34 @@ function buildReplayTurnScore(input) {
|
|
|
2029
3531
|
qualityScore: Math.min(100, compileScore + phraseScore)
|
|
2030
3532
|
};
|
|
2031
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
|
+
}
|
|
2032
3562
|
function buildRecordedSessionTurnReport(turnFixture, result, options) {
|
|
2033
3563
|
const compileOk = result.ok;
|
|
2034
3564
|
const selectedContextTexts = compileOk ? result.compileResponse.selectedContext.map((block) => block.text) : [];
|
|
@@ -2038,6 +3568,7 @@ function buildRecordedSessionTurnReport(turnFixture, result, options) {
|
|
|
2038
3568
|
texts: selectedContextTexts,
|
|
2039
3569
|
expectedContextPhrases: turnFixture.expectedContextPhrases
|
|
2040
3570
|
});
|
|
3571
|
+
const observability = buildRecordedSessionTurnObservability(result);
|
|
2041
3572
|
const eventExportDigest = result.eventExport.ok === true ? result.eventExport.normalizedEventExport.provenance.exportDigest : null;
|
|
2042
3573
|
return {
|
|
2043
3574
|
turnId: turnFixture.turnId,
|
|
@@ -2058,9 +3589,78 @@ function buildRecordedSessionTurnReport(turnFixture, result, options) {
|
|
|
2058
3589
|
qualityScore: scoring.qualityScore,
|
|
2059
3590
|
compileActiveVersion: options.compileActiveVersion,
|
|
2060
3591
|
promoted: options.promoted,
|
|
3592
|
+
observability,
|
|
2061
3593
|
warnings: [...result.warnings]
|
|
2062
3594
|
};
|
|
2063
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
|
+
}
|
|
2064
3664
|
function buildRecordedSessionReplayModeSummary(mode, turns) {
|
|
2065
3665
|
const compileOkCount = turns.filter((turn) => turn.compileOk).length;
|
|
2066
3666
|
const phraseHitCount = turns.reduce((sum, turn) => sum + turn.phraseHits.length, 0);
|
|
@@ -2069,6 +3669,7 @@ function buildRecordedSessionReplayModeSummary(mode, turns) {
|
|
|
2069
3669
|
const promotionCount = turns.filter((turn) => turn.promoted).length;
|
|
2070
3670
|
const qualityScore = turns.length === 0 ? 0 : Math.round(turns.reduce((sum, turn) => sum + turn.qualityScore, 0) / turns.length);
|
|
2071
3671
|
const packIds = uniqueStringsInOrder(turns.map((turn) => turn.activePackId).filter(isPresent));
|
|
3672
|
+
const scannerEvidence = buildRecordedSessionReplayScannerEvidence(mode, turns);
|
|
2072
3673
|
const base = {
|
|
2073
3674
|
mode,
|
|
2074
3675
|
qualityScore,
|
|
@@ -2077,7 +3678,8 @@ function buildRecordedSessionReplayModeSummary(mode, turns) {
|
|
|
2077
3678
|
phraseCount,
|
|
2078
3679
|
usedLearnedRouteTurnCount,
|
|
2079
3680
|
promotionCount,
|
|
2080
|
-
packIds
|
|
3681
|
+
packIds,
|
|
3682
|
+
scannerEvidence
|
|
2081
3683
|
};
|
|
2082
3684
|
return {
|
|
2083
3685
|
...base,
|
|
@@ -2115,6 +3717,7 @@ function buildRecordedSessionReplayScoreHash(modes) {
|
|
|
2115
3717
|
usedLearnedRouteTurnCount: mode.summary.usedLearnedRouteTurnCount,
|
|
2116
3718
|
promotionCount: mode.summary.promotionCount,
|
|
2117
3719
|
packIds: mode.summary.packIds,
|
|
3720
|
+
scannerEvidence: mode.summary.scannerEvidence,
|
|
2118
3721
|
scoreHash: mode.summary.scoreHash
|
|
2119
3722
|
})));
|
|
2120
3723
|
}
|
|
@@ -2145,6 +3748,17 @@ function recordedSessionReplayBundleBase(bundle) {
|
|
|
2145
3748
|
expectedContextPhrases: [...turn.expectedContextPhrases],
|
|
2146
3749
|
phraseHits: [...turn.phraseHits],
|
|
2147
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
|
+
},
|
|
2148
3762
|
warnings: [...turn.warnings]
|
|
2149
3763
|
}))
|
|
2150
3764
|
})),
|
|
@@ -2536,6 +4150,39 @@ function summarizeCandidateAheadBy(candidateAheadBy) {
|
|
|
2536
4150
|
.map(([field]) => field)
|
|
2537
4151
|
.sort();
|
|
2538
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
|
+
}
|
|
2539
4186
|
function isAwaitingFirstExportSlot(slot) {
|
|
2540
4187
|
return slot !== null && slot.eventRange.count === 0;
|
|
2541
4188
|
}
|
|
@@ -2571,7 +4218,56 @@ function summarizeBrainState(active, observability) {
|
|
|
2571
4218
|
detail
|
|
2572
4219
|
};
|
|
2573
4220
|
}
|
|
4221
|
+
function summarizeGraphObservability(active, observability) {
|
|
4222
|
+
if (active === null) {
|
|
4223
|
+
return {
|
|
4224
|
+
available: false,
|
|
4225
|
+
runtimePlasticitySource: null,
|
|
4226
|
+
structuralOps: null,
|
|
4227
|
+
changed: null,
|
|
4228
|
+
operationsApplied: [],
|
|
4229
|
+
liveBlockCount: null,
|
|
4230
|
+
prunedBlockCount: null,
|
|
4231
|
+
prePruneBlockCount: null,
|
|
4232
|
+
strongestBlockId: null,
|
|
4233
|
+
operatorSummary: null,
|
|
4234
|
+
detail: "no active pack is pinned, so there is no structural graph surface to inspect"
|
|
4235
|
+
};
|
|
4236
|
+
}
|
|
4237
|
+
const graphEvolution = observability.graphEvolutionLog;
|
|
4238
|
+
if (graphEvolution === null) {
|
|
4239
|
+
return {
|
|
4240
|
+
available: false,
|
|
4241
|
+
runtimePlasticitySource: observability.graphDynamics.runtimePlasticitySource,
|
|
4242
|
+
structuralOps: null,
|
|
4243
|
+
changed: null,
|
|
4244
|
+
operationsApplied: [],
|
|
4245
|
+
liveBlockCount: null,
|
|
4246
|
+
prunedBlockCount: null,
|
|
4247
|
+
prePruneBlockCount: null,
|
|
4248
|
+
strongestBlockId: null,
|
|
4249
|
+
operatorSummary: null,
|
|
4250
|
+
detail: "active pack is present, but the graph evolution log is missing from activation observability"
|
|
4251
|
+
};
|
|
4252
|
+
}
|
|
4253
|
+
return {
|
|
4254
|
+
available: true,
|
|
4255
|
+
runtimePlasticitySource: observability.graphDynamics.runtimePlasticitySource,
|
|
4256
|
+
structuralOps: { ...graphEvolution.structuralOps },
|
|
4257
|
+
changed: graphEvolution.structuralEvolutionSummary.changed,
|
|
4258
|
+
operationsApplied: [...graphEvolution.structuralEvolutionSummary.operationsApplied],
|
|
4259
|
+
liveBlockCount: graphEvolution.structuralEvolutionSummary.liveBlockCount,
|
|
4260
|
+
prunedBlockCount: graphEvolution.structuralEvolutionSummary.prunedBlockCount,
|
|
4261
|
+
prePruneBlockCount: graphEvolution.structuralEvolutionSummary.prePruneBlockCount,
|
|
4262
|
+
strongestBlockId: graphEvolution.strongestBlockId,
|
|
4263
|
+
operatorSummary: graphEvolution.structuralEvolutionSummary.operatorSummary,
|
|
4264
|
+
detail: graphEvolution.structuralEvolutionSummary.changed
|
|
4265
|
+
? graphEvolution.structuralEvolutionSummary.operatorSummary
|
|
4266
|
+
: "active pack graph is stable with no structural evolution beyond the current promoted artifact"
|
|
4267
|
+
};
|
|
4268
|
+
}
|
|
2574
4269
|
function summarizeServePath(compile) {
|
|
4270
|
+
const structuralDecision = summarizeStructuralDecisionFromNotes(compile?.notes ?? []);
|
|
2575
4271
|
if (compile === null) {
|
|
2576
4272
|
return {
|
|
2577
4273
|
state: "unprobed",
|
|
@@ -2583,6 +4279,13 @@ function summarizeServePath(compile) {
|
|
|
2583
4279
|
selectionMode: null,
|
|
2584
4280
|
refreshStatus: null,
|
|
2585
4281
|
freshnessChecksum: null,
|
|
4282
|
+
requestedBudgetStrategy: null,
|
|
4283
|
+
resolvedBudgetStrategy: null,
|
|
4284
|
+
resolvedMaxContextBlocks: null,
|
|
4285
|
+
structuralBudgetSource: null,
|
|
4286
|
+
structuralBudgetEvidence: null,
|
|
4287
|
+
structuralBudgetPressures: null,
|
|
4288
|
+
structuralDecision,
|
|
2586
4289
|
contextAttribution: buildContextAttributionSummary({
|
|
2587
4290
|
fallbackToStaticContext: false,
|
|
2588
4291
|
hardRequirementViolated: false,
|
|
@@ -2603,6 +4306,13 @@ function summarizeServePath(compile) {
|
|
|
2603
4306
|
selectionMode: null,
|
|
2604
4307
|
refreshStatus: null,
|
|
2605
4308
|
freshnessChecksum: null,
|
|
4309
|
+
requestedBudgetStrategy: null,
|
|
4310
|
+
resolvedBudgetStrategy: null,
|
|
4311
|
+
resolvedMaxContextBlocks: null,
|
|
4312
|
+
structuralBudgetSource: null,
|
|
4313
|
+
structuralBudgetEvidence: null,
|
|
4314
|
+
structuralBudgetPressures: null,
|
|
4315
|
+
structuralDecision,
|
|
2606
4316
|
contextAttribution: compile.contextAttribution,
|
|
2607
4317
|
error: compile.error
|
|
2608
4318
|
};
|
|
@@ -2617,6 +4327,14 @@ function summarizeServePath(compile) {
|
|
|
2617
4327
|
selectionMode: readDiagnosticNoteValue(compile.notes, "selection_mode="),
|
|
2618
4328
|
refreshStatus: readDiagnosticNoteValue(compile.notes, "router_refresh_status="),
|
|
2619
4329
|
freshnessChecksum: readDiagnosticNoteValue(compile.notes, "router_freshness_checksum="),
|
|
4330
|
+
requestedBudgetStrategy: readDiagnosticNoteValue(compile.notes, "requested_budget_strategy="),
|
|
4331
|
+
resolvedBudgetStrategy: readDiagnosticNoteValue(compile.notes, "resolved_budget_strategy="),
|
|
4332
|
+
resolvedMaxContextBlocks: structuralDecision.resolvedMaxContextBlocks,
|
|
4333
|
+
structuralBudgetSource: readDiagnosticNoteValue(compile.notes, "structural_budget_source="),
|
|
4334
|
+
structuralBudgetEvidence: readDiagnosticNoteValue(compile.notes, "structural_budget_compile_evidence=") ??
|
|
4335
|
+
readDiagnosticNoteValue(compile.notes, "structural_budget_evidence="),
|
|
4336
|
+
structuralBudgetPressures: readDiagnosticNoteValue(compile.notes, "structural_budget_pressures="),
|
|
4337
|
+
structuralDecision,
|
|
2620
4338
|
contextAttribution: compile.contextAttribution,
|
|
2621
4339
|
error: compile.error
|
|
2622
4340
|
};
|
|
@@ -2765,11 +4483,18 @@ function summarizeSupervision(input) {
|
|
|
2765
4483
|
exportDigest: null,
|
|
2766
4484
|
exportedAt: null,
|
|
2767
4485
|
flowing: null,
|
|
4486
|
+
scanPolicy: null,
|
|
4487
|
+
scanSurfaceCount: 0,
|
|
4488
|
+
scanSurfaces: [],
|
|
2768
4489
|
sourceCount: 0,
|
|
2769
4490
|
freshestSourceStream: null,
|
|
2770
4491
|
freshestCreatedAt: null,
|
|
2771
4492
|
freshestKind: null,
|
|
2772
4493
|
humanLabelCount: null,
|
|
4494
|
+
selfLabelCount: null,
|
|
4495
|
+
attributedEventCount: null,
|
|
4496
|
+
totalEventCount: null,
|
|
4497
|
+
selectionDigestCount: null,
|
|
2773
4498
|
sources: [],
|
|
2774
4499
|
detail: "no event export path supplied"
|
|
2775
4500
|
};
|
|
@@ -2784,11 +4509,18 @@ function summarizeSupervision(input) {
|
|
|
2784
4509
|
exportDigest: observability.exportDigest,
|
|
2785
4510
|
exportedAt: loaded.exportedAt,
|
|
2786
4511
|
flowing,
|
|
4512
|
+
scanPolicy: observability.learningSurface.scanPolicy,
|
|
4513
|
+
scanSurfaceCount: observability.learningSurface.scanSurfaces.length,
|
|
4514
|
+
scanSurfaces: [...observability.learningSurface.scanSurfaces],
|
|
2787
4515
|
sourceCount: observability.supervisionFreshnessBySource.length,
|
|
2788
4516
|
freshestSourceStream: observability.teacherFreshness.sourceStream ?? freshestSource?.sourceStream ?? null,
|
|
2789
4517
|
freshestCreatedAt: observability.teacherFreshness.freshestCreatedAt ?? freshestSource?.freshestCreatedAt ?? null,
|
|
2790
4518
|
freshestKind: observability.teacherFreshness.freshestKind ?? freshestSource?.freshestKind ?? null,
|
|
2791
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,
|
|
2792
4524
|
sources: [...observability.teacherFreshness.sources],
|
|
2793
4525
|
detail: flowing
|
|
2794
4526
|
? "human supervision is visible in the supplied export"
|
|
@@ -2853,6 +4585,53 @@ function summarizeTeacherLoop(input) {
|
|
|
2853
4585
|
detail: "async teacher diagnostics loaded"
|
|
2854
4586
|
};
|
|
2855
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
|
+
}
|
|
2856
4635
|
function summarizeAlwaysOnLearning(input, active) {
|
|
2857
4636
|
const unavailableLag = {
|
|
2858
4637
|
activeEventRangeEnd: active?.eventRange.end ?? null,
|
|
@@ -2868,15 +4647,21 @@ function summarizeAlwaysOnLearning(input, active) {
|
|
|
2868
4647
|
bootstrapped: null,
|
|
2869
4648
|
mode: "unavailable",
|
|
2870
4649
|
nextPriorityLane: "unavailable",
|
|
4650
|
+
nextPriorityBucket: "unavailable",
|
|
4651
|
+
backlogState: "unavailable",
|
|
2871
4652
|
pendingLive: null,
|
|
2872
4653
|
pendingBackfill: null,
|
|
2873
4654
|
pendingTotal: null,
|
|
4655
|
+
pendingByBucket: null,
|
|
2874
4656
|
freshLivePriority: null,
|
|
2875
4657
|
principalCheckpointCount: null,
|
|
2876
4658
|
pendingPrincipalCount: null,
|
|
2877
4659
|
oldestUnlearnedPrincipalEvent: null,
|
|
4660
|
+
newestPendingPrincipalEvent: null,
|
|
4661
|
+
leadingPrincipalCheckpoint: null,
|
|
2878
4662
|
principalCheckpoints: [],
|
|
2879
4663
|
principalLagToPromotion: unavailableLag,
|
|
4664
|
+
warningStates: ["teacher_snapshot_unavailable"],
|
|
2880
4665
|
learnedRange: null,
|
|
2881
4666
|
materializationCount: null,
|
|
2882
4667
|
lastMaterializedAt: null,
|
|
@@ -2895,15 +4680,21 @@ function summarizeAlwaysOnLearning(input, active) {
|
|
|
2895
4680
|
bootstrapped: null,
|
|
2896
4681
|
mode: "unavailable",
|
|
2897
4682
|
nextPriorityLane: "unavailable",
|
|
4683
|
+
nextPriorityBucket: "unavailable",
|
|
4684
|
+
backlogState: "unavailable",
|
|
2898
4685
|
pendingLive: null,
|
|
2899
4686
|
pendingBackfill: null,
|
|
2900
4687
|
pendingTotal: null,
|
|
4688
|
+
pendingByBucket: null,
|
|
2901
4689
|
freshLivePriority: null,
|
|
2902
4690
|
principalCheckpointCount: null,
|
|
2903
4691
|
pendingPrincipalCount: null,
|
|
2904
4692
|
oldestUnlearnedPrincipalEvent: null,
|
|
4693
|
+
newestPendingPrincipalEvent: null,
|
|
4694
|
+
leadingPrincipalCheckpoint: null,
|
|
2905
4695
|
principalCheckpoints: [],
|
|
2906
4696
|
principalLagToPromotion: unavailableLag,
|
|
4697
|
+
warningStates: ["teacher_snapshot_unavailable"],
|
|
2907
4698
|
learnedRange: null,
|
|
2908
4699
|
materializationCount: null,
|
|
2909
4700
|
lastMaterializedAt: null,
|
|
@@ -2926,30 +4717,43 @@ function summarizeAlwaysOnLearning(input, active) {
|
|
|
2926
4717
|
const sequenceLag = latestPrincipalSequence === null || activeEventRangeEnd === null
|
|
2927
4718
|
? null
|
|
2928
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
|
+
});
|
|
2929
4731
|
return {
|
|
2930
4732
|
available: true,
|
|
2931
4733
|
sourcePath: path.resolve(teacherSnapshotPath),
|
|
2932
4734
|
bootstrapped: plan.bootstrapped,
|
|
2933
4735
|
mode: plan.mode,
|
|
2934
4736
|
nextPriorityLane: plan.nextPriorityLane,
|
|
4737
|
+
nextPriorityBucket: plan.nextPriorityBucket,
|
|
4738
|
+
backlogState,
|
|
2935
4739
|
pendingLive: plan.pending.live,
|
|
2936
4740
|
pendingBackfill: plan.pending.backfill,
|
|
2937
4741
|
pendingTotal: plan.pending.total,
|
|
4742
|
+
pendingByBucket: { ...plan.pending.byBucket },
|
|
2938
4743
|
freshLivePriority: plan.pending.freshLivePriority,
|
|
2939
4744
|
principalCheckpointCount: plan.principalBacklog.principalCount,
|
|
2940
4745
|
pendingPrincipalCount: plan.principalBacklog.pendingEventCount,
|
|
2941
4746
|
oldestUnlearnedPrincipalEvent: plan.principalBacklog.oldestUnlearnedEvent,
|
|
4747
|
+
newestPendingPrincipalEvent: plan.principalBacklog.newestPendingEvent,
|
|
4748
|
+
leadingPrincipalCheckpoint: plan.principalBacklog.checkpoints[0] ?? null,
|
|
2942
4749
|
principalCheckpoints: plan.principalBacklog.checkpoints,
|
|
2943
4750
|
principalLagToPromotion: {
|
|
2944
4751
|
activeEventRangeEnd,
|
|
2945
4752
|
latestPrincipalSequence,
|
|
2946
4753
|
sequenceLag,
|
|
2947
|
-
status:
|
|
2948
|
-
? "unavailable"
|
|
2949
|
-
: sequenceLag === 0
|
|
2950
|
-
? "caught_up"
|
|
2951
|
-
: "pending_promotion"
|
|
4754
|
+
status: principalLagStatus
|
|
2952
4755
|
},
|
|
4756
|
+
warningStates,
|
|
2953
4757
|
learnedRange: plan.learnedRange === null ? null : { ...plan.learnedRange },
|
|
2954
4758
|
materializationCount: plan.materialization.count,
|
|
2955
4759
|
lastMaterializedAt: plan.materialization.lastMaterializedAt,
|
|
@@ -2957,13 +4761,17 @@ function summarizeAlwaysOnLearning(input, active) {
|
|
|
2957
4761
|
lastMaterializationLane: plan.materialization.lastLane,
|
|
2958
4762
|
lastMaterializationPriority: plan.materialization.lastPriority,
|
|
2959
4763
|
lastMaterializedPackId: snapshot.learner.lastMaterialization?.candidate.summary.packId ?? null,
|
|
2960
|
-
detail: plan.
|
|
2961
|
-
? "
|
|
2962
|
-
: plan.
|
|
2963
|
-
? "passive backfill
|
|
2964
|
-
: plan.
|
|
2965
|
-
? "
|
|
2966
|
-
:
|
|
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"
|
|
2967
4775
|
};
|
|
2968
4776
|
}
|
|
2969
4777
|
function buildOperatorFindings(report) {
|
|
@@ -2996,6 +4804,7 @@ function buildOperatorFindings(report) {
|
|
|
2996
4804
|
}
|
|
2997
4805
|
if (report.servePath.state === "serving_active_pack") {
|
|
2998
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"}`);
|
|
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"}`);
|
|
2999
4808
|
}
|
|
3000
4809
|
else if (report.servePath.state === "fail_open_static_context") {
|
|
3001
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");
|
|
@@ -3052,6 +4861,22 @@ function buildOperatorFindings(report) {
|
|
|
3052
4861
|
else {
|
|
3053
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"}`);
|
|
3054
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
|
+
}
|
|
3055
4880
|
if (!report.teacherLoop.available) {
|
|
3056
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");
|
|
3057
4882
|
}
|
|
@@ -3133,7 +4958,7 @@ function summarizeCurrentProfileBrainStatusLevel(input) {
|
|
|
3133
4958
|
}
|
|
3134
4959
|
return input.routeFreshness === "updated" ? "ok" : "warn";
|
|
3135
4960
|
}
|
|
3136
|
-
function buildCurrentProfileBrainStatusFromReport(report, policyMode) {
|
|
4961
|
+
function buildCurrentProfileBrainStatusFromReport(report, policyMode, profileId) {
|
|
3137
4962
|
const attached = report.active !== null;
|
|
3138
4963
|
const awaitingFirstExport = isAwaitingFirstExportSlot(report.active);
|
|
3139
4964
|
const routerIdentity = report.servePath.routerIdentity ?? report.learnedRouting.routerIdentity ?? report.active?.routerIdentity ?? null;
|
|
@@ -3158,6 +4983,7 @@ function buildCurrentProfileBrainStatusFromReport(report, policyMode) {
|
|
|
3158
4983
|
profile: {
|
|
3159
4984
|
noun: "Profile",
|
|
3160
4985
|
selector: "current_profile",
|
|
4986
|
+
profileId,
|
|
3161
4987
|
detail: attached
|
|
3162
4988
|
? "The Host resolves the current Profile through the active Attachment boundary only."
|
|
3163
4989
|
: "The current Profile has no active Brain attachment visible at the Host boundary."
|
|
@@ -3214,6 +5040,7 @@ function buildCurrentProfileBrainStatusFromReport(report, policyMode) {
|
|
|
3214
5040
|
usedLearnedRouteFn: report.servePath.usedLearnedRouteFn,
|
|
3215
5041
|
failOpen: report.servePath.fallbackToStaticContext,
|
|
3216
5042
|
awaitingFirstExport,
|
|
5043
|
+
structuralDecision: report.servePath.structuralDecision,
|
|
3217
5044
|
detail: report.servePath.state === "serving_active_pack"
|
|
3218
5045
|
? awaitingFirstExport
|
|
3219
5046
|
? `current profile is serving seed-state pack ${activePackId ?? "unknown"} while awaiting the first exported turn`
|
|
@@ -3232,6 +5059,7 @@ function buildCurrentProfileBrainStatusFromReport(report, policyMode) {
|
|
|
3232
5059
|
export function buildOperatorSurfaceReport(input) {
|
|
3233
5060
|
const activationRoot = path.resolve(normalizeNonEmptyString(input.activationRoot, "activationRoot"));
|
|
3234
5061
|
const updatedAt = normalizeIsoTimestamp(input.updatedAt, "updatedAt", new Date().toISOString());
|
|
5062
|
+
const brainAttachmentPolicy = normalizeBrainAttachmentPolicy(input.brainAttachmentPolicy);
|
|
3235
5063
|
const inspection = inspectActivationState(activationRoot, updatedAt);
|
|
3236
5064
|
const observability = describeActivationObservability(activationRoot, "active", {
|
|
3237
5065
|
updatedAt
|
|
@@ -3249,6 +5077,7 @@ export function buildOperatorSurfaceReport(input) {
|
|
|
3249
5077
|
candidateAheadBy: summarizeCandidateAheadBy(observability.promotionFreshness.candidateAheadBy)
|
|
3250
5078
|
},
|
|
3251
5079
|
brain: summarizeBrainState(active, observability),
|
|
5080
|
+
graph: summarizeGraphObservability(active, observability),
|
|
3252
5081
|
learnedRouting: {
|
|
3253
5082
|
required: observability.learnedRouteFn.required,
|
|
3254
5083
|
available: observability.learnedRouteFn.available,
|
|
@@ -3288,7 +5117,8 @@ export function buildOperatorSurfaceReport(input) {
|
|
|
3288
5117
|
supervision: summarizeSupervision(input),
|
|
3289
5118
|
learning: summarizeAlwaysOnLearning(input, active),
|
|
3290
5119
|
teacherLoop: summarizeTeacherLoop(input),
|
|
3291
|
-
principal: summarizePrincipalObservability(input, active)
|
|
5120
|
+
principal: summarizePrincipalObservability(input, active),
|
|
5121
|
+
manyProfile: summarizeManyProfileSupport(brainAttachmentPolicy)
|
|
3292
5122
|
};
|
|
3293
5123
|
const findings = buildOperatorFindings(reportBase);
|
|
3294
5124
|
return {
|
|
@@ -3299,7 +5129,7 @@ export function buildOperatorSurfaceReport(input) {
|
|
|
3299
5129
|
}
|
|
3300
5130
|
export function describeCurrentProfileBrainStatus(input) {
|
|
3301
5131
|
const report = buildOperatorSurfaceReport(input);
|
|
3302
|
-
return buildCurrentProfileBrainStatusFromReport(report,
|
|
5132
|
+
return buildCurrentProfileBrainStatusFromReport(report, report.manyProfile.declaredAttachmentPolicy, normalizeOptionalString(input.profileId) ?? null);
|
|
3303
5133
|
}
|
|
3304
5134
|
export function formatOperatorRollbackReport(result) {
|
|
3305
5135
|
const header = result.allowed ? (result.dryRun ? "ROLLBACK ready" : "ROLLBACK ok") : "ROLLBACK blocked";
|
|
@@ -3367,4 +5197,7 @@ export { CONTRACT_IDS, buildNormalizedEventExport, createFeedbackEvent, createIn
|
|
|
3367
5197
|
export { describeNormalizedEventExportObservability } from "@openclawbrain/event-export";
|
|
3368
5198
|
export { describeCompileFallbackUsage } from "@openclawbrain/compiler";
|
|
3369
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";
|
|
3370
5203
|
//# sourceMappingURL=index.js.map
|