@openclawbrain/openclaw 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/README.md +120 -5
- package/dist/src/cli.d.ts +12 -0
- package/dist/src/cli.js +152 -0
- package/dist/src/cli.js.map +1 -0
- package/dist/src/index.d.ts +692 -4
- package/dist/src/index.js +2243 -8
- package/dist/src/index.js.map +1 -1
- package/package.json +15 -11
package/dist/src/index.js
CHANGED
|
@@ -1,20 +1,569 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
-
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { mkdirSync, readFileSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import process from "node:process";
|
|
5
|
-
import {
|
|
6
|
-
import { CONTRACT_IDS, buildNormalizedEventExport, canonicalJson, checksumJsonPayload, createFeedbackEvent, createInteractionEvent, validateNormalizedEventExport } from "@openclawbrain/contracts";
|
|
7
|
-
import {
|
|
5
|
+
import { compileRuntimeFromActivation } from "@openclawbrain/compiler";
|
|
6
|
+
import { CONTRACT_IDS, buildNormalizedEventExport, canonicalJson, checksumJsonPayload, createFeedbackEvent, createInteractionEvent, sortNormalizedEvents, validateKernelSurface, validateNormalizedEventExport } from "@openclawbrain/contracts";
|
|
7
|
+
import { describeNormalizedEventExportObservability } from "@openclawbrain/event-export";
|
|
8
|
+
import { DEFAULT_TEACHER_SUPERVISION_STALE_AFTER_MS, advanceAlwaysOnLearningRuntime, buildTeacherSupervisionArtifactsFromNormalizedEventExport, createAlwaysOnLearningRuntimeState, describeAlwaysOnLearningRuntimeState, materializeAlwaysOnLearningCandidatePack, materializeCandidatePackFromNormalizedEventExport } from "@openclawbrain/learner";
|
|
9
|
+
import { activatePack, describeActivationObservability, describeActivationTarget, describePackCompileTarget, inspectActivationState, promoteCandidatePack, rollbackActivePack, stageCandidatePack } from "@openclawbrain/pack-format";
|
|
8
10
|
const DEFAULT_AGENT_ID = "openclaw-runtime";
|
|
9
11
|
const FEEDBACK_KINDS = new Set(["correction", "teaching", "approval", "suppression"]);
|
|
12
|
+
export const DEFAULT_ASYNC_TEACHER_QUEUE_CAPACITY = 8;
|
|
13
|
+
const RECORDED_SESSION_TRACE_CONTRACT = "recorded_session_trace.v1";
|
|
14
|
+
const RECORDED_SESSION_FIXTURE_CONTRACT = "recorded_session_replay_fixture.v1";
|
|
15
|
+
const RECORDED_SESSION_BUNDLE_CONTRACT = "recorded_session_replay_bundle.v1";
|
|
10
16
|
const RUNTIME_EVENT_EXPORT_BUNDLE_CONTRACT = "normalized_event_export_bundle.v1";
|
|
17
|
+
const DEFAULT_ATTACH_STATUS_MESSAGE = "openclaw attach status probe";
|
|
18
|
+
const DEFAULT_ATTACH_STATUS_RUNTIME_HINTS = ["attach", "status", "probe"];
|
|
11
19
|
export const RUNTIME_EVENT_EXPORT_BUNDLE_LAYOUT = {
|
|
12
20
|
manifest: "manifest.json",
|
|
13
21
|
payload: "normalized-event-export.json"
|
|
14
22
|
};
|
|
23
|
+
const OPENCLAW_LANDING_BOUNDARIES_V1 = {
|
|
24
|
+
compileBoundary: {
|
|
25
|
+
contract: CONTRACT_IDS.runtimeCompile,
|
|
26
|
+
activationSlot: "active",
|
|
27
|
+
entrypoint: "compileRuntimeContext",
|
|
28
|
+
servedFromCandidateBeforePromotion: false,
|
|
29
|
+
learnedRouteEvidenceRequiredWhenManifestRequiresIt: true
|
|
30
|
+
},
|
|
31
|
+
eventExportBoundary: {
|
|
32
|
+
emittedContracts: [CONTRACT_IDS.interactionEvents, CONTRACT_IDS.feedbackEvents],
|
|
33
|
+
entrypoint: "runRuntimeTurn",
|
|
34
|
+
bundleWriteOptional: true,
|
|
35
|
+
writeFailuresEraseSuccessfulCompile: false,
|
|
36
|
+
learningHandoffStaysOffHotPath: true
|
|
37
|
+
},
|
|
38
|
+
activePackBoundary: {
|
|
39
|
+
servingSlot: "active",
|
|
40
|
+
inspectableSlots: ["active", "candidate", "previous"],
|
|
41
|
+
candidateServedBeforePromotion: false,
|
|
42
|
+
previousSlotUsedForRollback: true
|
|
43
|
+
},
|
|
44
|
+
promotionBoundary: {
|
|
45
|
+
candidateSlot: "candidate",
|
|
46
|
+
activeSlot: "active",
|
|
47
|
+
previousSlot: "previous",
|
|
48
|
+
requiresActivationReadyCandidate: true,
|
|
49
|
+
compileSeesCandidateOnlyAfterPromotion: true,
|
|
50
|
+
promotionHappensOffHotPath: true
|
|
51
|
+
},
|
|
52
|
+
failOpenSemantics: {
|
|
53
|
+
missingActivePackFallsBackToStaticContext: true,
|
|
54
|
+
learnedRequiredRouteArtifactDriftHardFails: true,
|
|
55
|
+
hardFailuresDisableStaticFallback: true,
|
|
56
|
+
eventExportWriteFailurePreservesCompile: true
|
|
57
|
+
},
|
|
58
|
+
runtimeResponsibilities: [
|
|
59
|
+
"runtime orchestration and session flow",
|
|
60
|
+
"prompt assembly and response delivery",
|
|
61
|
+
"guarded serve-path fail-open decisions"
|
|
62
|
+
],
|
|
63
|
+
brainResponsibilities: [
|
|
64
|
+
"normalized event emission and export handoff",
|
|
65
|
+
"candidate pack materialization and learned route refresh",
|
|
66
|
+
"activation staging promotion and rollback",
|
|
67
|
+
"promoted-pack compilation diagnostics"
|
|
68
|
+
]
|
|
69
|
+
};
|
|
15
70
|
function toErrorMessage(error) {
|
|
16
71
|
return error instanceof Error ? error.message : String(error);
|
|
17
72
|
}
|
|
73
|
+
function buildAsyncTeacherLoopNotes(input) {
|
|
74
|
+
return [
|
|
75
|
+
`teacher_queue_depth=${input.queueDepth}`,
|
|
76
|
+
`teacher_freshness=${input.latestFreshness}`,
|
|
77
|
+
`teacher_artifacts_total=${input.artifactCount}`,
|
|
78
|
+
`teacher_artifacts_emitted=${input.emittedArtifactCount}`,
|
|
79
|
+
`teacher_artifacts_deduped=${input.dedupedArtifactCount}`,
|
|
80
|
+
`teacher_budget=${input.sparseFeedback.teacherBudget}`,
|
|
81
|
+
`teacher_delay_ms=${input.sparseFeedback.teacherDelayMs}`,
|
|
82
|
+
`teacher_feedback_mask=correction:${input.sparseFeedback.feedbackMask.correction},teaching:${input.sparseFeedback.feedbackMask.teaching},approval:${input.sparseFeedback.feedbackMask.approval},suppression:${input.sparseFeedback.feedbackMask.suppression}`,
|
|
83
|
+
`teacher_feedback_eligible=${input.sparseFeedback.eligibleFeedbackCount}`,
|
|
84
|
+
`teacher_feedback_masked=${input.sparseFeedback.maskedFeedbackCount}`,
|
|
85
|
+
`teacher_feedback_delayed=${input.sparseFeedback.delayedFeedbackCount}`,
|
|
86
|
+
`teacher_feedback_budgeted_out=${input.sparseFeedback.budgetedOutFeedbackCount}`,
|
|
87
|
+
`teacher_background_amplified=${input.sparseFeedback.amplifiedBackgroundLabelCount}`,
|
|
88
|
+
`teacher_noop=${input.noOpReason}`,
|
|
89
|
+
input.materialization === null ? "teacher_materialization=noop" : `teacher_materialized_pack=${input.materialization.candidate.summary.packId}`
|
|
90
|
+
];
|
|
91
|
+
}
|
|
92
|
+
function cloneAlwaysOnLearningMaterializationJobOrNull(value) {
|
|
93
|
+
return value === null ? null : structuredClone(value);
|
|
94
|
+
}
|
|
95
|
+
function cloneTeacherSupervisionArtifacts(value) {
|
|
96
|
+
return [...structuredClone(value)];
|
|
97
|
+
}
|
|
98
|
+
function cloneCanonicalSupervision(value) {
|
|
99
|
+
return structuredClone(value);
|
|
100
|
+
}
|
|
101
|
+
function cloneContinuousProductLoopPackVersion(value) {
|
|
102
|
+
return structuredClone(value);
|
|
103
|
+
}
|
|
104
|
+
function cloneContinuousProductLoopState(value) {
|
|
105
|
+
return structuredClone(value);
|
|
106
|
+
}
|
|
107
|
+
function buildNormalizedEventDedupId(event) {
|
|
108
|
+
return checksumJsonPayload({
|
|
109
|
+
contract: event.contract,
|
|
110
|
+
eventId: event.eventId,
|
|
111
|
+
agentId: event.agentId,
|
|
112
|
+
sessionId: event.sessionId,
|
|
113
|
+
channel: event.channel,
|
|
114
|
+
sequence: event.sequence,
|
|
115
|
+
kind: event.kind,
|
|
116
|
+
createdAt: event.createdAt,
|
|
117
|
+
source: event.source,
|
|
118
|
+
packId: "packId" in event ? event.packId ?? null : null,
|
|
119
|
+
content: "content" in event ? event.content : null,
|
|
120
|
+
messageId: event.messageId ?? null,
|
|
121
|
+
relatedInteractionId: "relatedInteractionId" in event ? event.relatedInteractionId ?? null : null
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
function mergeRuntimeEventHistory(current, incoming) {
|
|
125
|
+
const merged = sortNormalizedEvents([
|
|
126
|
+
...current.interactionEvents,
|
|
127
|
+
...current.feedbackEvents,
|
|
128
|
+
...incoming.interactionEvents,
|
|
129
|
+
...incoming.feedbackEvents
|
|
130
|
+
]);
|
|
131
|
+
const deduped = [];
|
|
132
|
+
const seen = new Set();
|
|
133
|
+
for (const event of merged) {
|
|
134
|
+
const dedupId = buildNormalizedEventDedupId(event);
|
|
135
|
+
if (seen.has(dedupId)) {
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
seen.add(dedupId);
|
|
139
|
+
deduped.push(event);
|
|
140
|
+
}
|
|
141
|
+
return {
|
|
142
|
+
interactionEvents: deduped.filter((event) => event.contract === CONTRACT_IDS.interactionEvents),
|
|
143
|
+
feedbackEvents: deduped.filter((event) => event.contract === CONTRACT_IDS.feedbackEvents)
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
function buildContinuousTurnExport(turn, loopRoot) {
|
|
147
|
+
const exportSeed = checksumJsonPayload({
|
|
148
|
+
sessionId: turn.sessionId,
|
|
149
|
+
channel: turn.channel,
|
|
150
|
+
sourceStream: turn.sourceStream ?? null,
|
|
151
|
+
userMessage: turn.userMessage,
|
|
152
|
+
createdAt: turn.createdAt ?? null,
|
|
153
|
+
sequenceStart: turn.sequenceStart ?? null,
|
|
154
|
+
compileCreatedAt: turn.compile?.createdAt ?? null,
|
|
155
|
+
delivery: turn.delivery === false
|
|
156
|
+
? false
|
|
157
|
+
: turn.delivery === undefined || turn.delivery === null
|
|
158
|
+
? null
|
|
159
|
+
: turn.delivery === true
|
|
160
|
+
? true
|
|
161
|
+
: {
|
|
162
|
+
createdAt: turn.delivery.createdAt ?? null,
|
|
163
|
+
messageId: turn.delivery.messageId ?? null,
|
|
164
|
+
sequence: turn.delivery.sequence ?? null
|
|
165
|
+
},
|
|
166
|
+
feedback: (turn.feedback ?? [])
|
|
167
|
+
.filter((item) => item !== null)
|
|
168
|
+
.map((item) => ({
|
|
169
|
+
content: item.content,
|
|
170
|
+
createdAt: item.createdAt ?? null,
|
|
171
|
+
sequence: item.sequence ?? null,
|
|
172
|
+
kind: item.kind ?? null,
|
|
173
|
+
messageId: item.messageId ?? null,
|
|
174
|
+
relatedInteractionId: item.relatedInteractionId ?? null
|
|
175
|
+
}))
|
|
176
|
+
})
|
|
177
|
+
.replace(/^sha256-/u, "")
|
|
178
|
+
.slice(0, 12);
|
|
179
|
+
const exportName = `${turn.sessionId}-${exportSeed}`;
|
|
180
|
+
return {
|
|
181
|
+
rootDir: path.join(loopRoot, "event-exports", exportName),
|
|
182
|
+
exportName
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
function withContinuousTurnExport(turn, loopRoot) {
|
|
186
|
+
if (turn.export !== undefined && turn.export !== null) {
|
|
187
|
+
return {
|
|
188
|
+
...turn,
|
|
189
|
+
export: {
|
|
190
|
+
...turn.export
|
|
191
|
+
}
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
return {
|
|
195
|
+
...turn,
|
|
196
|
+
export: buildContinuousTurnExport(turn, loopRoot)
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
function buildPackVersion(version, target) {
|
|
200
|
+
return {
|
|
201
|
+
version,
|
|
202
|
+
packId: target.packId,
|
|
203
|
+
routePolicy: target.routePolicy,
|
|
204
|
+
routerIdentity: target.routerIdentity,
|
|
205
|
+
workspaceSnapshot: target.workspaceSnapshot,
|
|
206
|
+
workspaceRevision: target.workspaceRevision,
|
|
207
|
+
eventRange: {
|
|
208
|
+
start: target.eventRange.start,
|
|
209
|
+
end: target.eventRange.end,
|
|
210
|
+
count: target.eventRange.count
|
|
211
|
+
},
|
|
212
|
+
eventExportDigest: target.eventExportDigest,
|
|
213
|
+
builtAt: target.builtAt
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
function buildLearningCandidateTarget(candidate) {
|
|
217
|
+
return {
|
|
218
|
+
packId: candidate.summary.packId,
|
|
219
|
+
routePolicy: candidate.summary.routePolicy,
|
|
220
|
+
routerIdentity: candidate.payloads.router?.routerIdentity ?? null,
|
|
221
|
+
workspaceSnapshot: candidate.summary.workspaceSnapshot,
|
|
222
|
+
workspaceRevision: candidate.manifest.provenance.workspace.revision,
|
|
223
|
+
eventRange: {
|
|
224
|
+
start: candidate.summary.eventRange.start,
|
|
225
|
+
end: candidate.summary.eventRange.end,
|
|
226
|
+
count: candidate.summary.eventRange.count
|
|
227
|
+
},
|
|
228
|
+
eventExportDigest: candidate.summary.eventExportDigest,
|
|
229
|
+
builtAt: candidate.manifest.provenance.builtAt
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
function registerPackVersion(state, target) {
|
|
233
|
+
const existing = state.packLineage.find((entry) => entry.packId === target.packId);
|
|
234
|
+
if (existing !== undefined) {
|
|
235
|
+
return cloneContinuousProductLoopPackVersion(existing);
|
|
236
|
+
}
|
|
237
|
+
const created = buildPackVersion(state.nextPackVersion, target);
|
|
238
|
+
state.packLineage.push(cloneContinuousProductLoopPackVersion(created));
|
|
239
|
+
state.nextPackVersion += 1;
|
|
240
|
+
return created;
|
|
241
|
+
}
|
|
242
|
+
function tryReadActivePackTarget(rootDir) {
|
|
243
|
+
try {
|
|
244
|
+
return describeActivationTarget(rootDir, "active", { requireActivationReady: true });
|
|
245
|
+
}
|
|
246
|
+
catch {
|
|
247
|
+
return null;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
function syncContinuousActivePack(state) {
|
|
251
|
+
const activeTarget = tryReadActivePackTarget(state.activationRoot);
|
|
252
|
+
if (activeTarget === null) {
|
|
253
|
+
state.currentActivePack = null;
|
|
254
|
+
state.activePackVersion = 0;
|
|
255
|
+
return null;
|
|
256
|
+
}
|
|
257
|
+
const activePack = registerPackVersion(state, activeTarget);
|
|
258
|
+
state.currentActivePack = cloneContinuousProductLoopPackVersion(activePack);
|
|
259
|
+
state.activePackVersion = activePack.version;
|
|
260
|
+
return activePack;
|
|
261
|
+
}
|
|
262
|
+
function buildContinuousPackRoot(loopRoot, packVersion) {
|
|
263
|
+
return path.join(loopRoot, "packs", `v${String(packVersion.version).padStart(4, "0")}-${packVersion.packId}`);
|
|
264
|
+
}
|
|
265
|
+
export function buildCanonicalSupervision(normalizedEventExport) {
|
|
266
|
+
const feedback = normalizedEventExport.feedbackEvents.map((event) => ({
|
|
267
|
+
eventId: event.eventId,
|
|
268
|
+
kind: event.kind,
|
|
269
|
+
sequence: event.sequence,
|
|
270
|
+
createdAt: event.createdAt,
|
|
271
|
+
content: event.content,
|
|
272
|
+
relatedInteractionId: event.relatedInteractionId ?? null
|
|
273
|
+
}));
|
|
274
|
+
const compilePackIds = [
|
|
275
|
+
...new Set(normalizedEventExport.interactionEvents.flatMap((event) => event.kind === "memory_compiled" && event.packId ? [event.packId] : []))
|
|
276
|
+
];
|
|
277
|
+
const relatedInteractionIds = [...new Set(feedback.flatMap((event) => (event.relatedInteractionId ? [event.relatedInteractionId] : [])))];
|
|
278
|
+
const feedbackCounts = {
|
|
279
|
+
corrections: feedback.filter((event) => event.kind === "correction").length,
|
|
280
|
+
teachings: feedback.filter((event) => event.kind === "teaching").length,
|
|
281
|
+
approvals: feedback.filter((event) => event.kind === "approval").length,
|
|
282
|
+
suppressions: feedback.filter((event) => event.kind === "suppression").length
|
|
283
|
+
};
|
|
284
|
+
const supervisionDigest = checksumJsonPayload({
|
|
285
|
+
exportDigest: normalizedEventExport.provenance.exportDigest,
|
|
286
|
+
eventRange: {
|
|
287
|
+
start: normalizedEventExport.range.start,
|
|
288
|
+
end: normalizedEventExport.range.end,
|
|
289
|
+
count: normalizedEventExport.range.count
|
|
290
|
+
},
|
|
291
|
+
sourceStreams: normalizedEventExport.provenance.sourceStreams,
|
|
292
|
+
humanLabelCount: normalizedEventExport.provenance.learningSurface.labelHarvest.humanLabels,
|
|
293
|
+
selfLabelCount: normalizedEventExport.provenance.learningSurface.labelHarvest.selfLabels,
|
|
294
|
+
feedback,
|
|
295
|
+
compilePackIds,
|
|
296
|
+
relatedInteractionIds
|
|
297
|
+
});
|
|
298
|
+
return {
|
|
299
|
+
runtimeOwner: "openclaw",
|
|
300
|
+
exportDigest: normalizedEventExport.provenance.exportDigest,
|
|
301
|
+
supervisionDigest,
|
|
302
|
+
sessionId: normalizedEventExport.provenance.sessionId,
|
|
303
|
+
channel: normalizedEventExport.provenance.channel,
|
|
304
|
+
eventRange: {
|
|
305
|
+
start: normalizedEventExport.range.start,
|
|
306
|
+
end: normalizedEventExport.range.end,
|
|
307
|
+
count: normalizedEventExport.range.count
|
|
308
|
+
},
|
|
309
|
+
sourceStreams: [...normalizedEventExport.provenance.sourceStreams],
|
|
310
|
+
humanLabelCount: normalizedEventExport.provenance.learningSurface.labelHarvest.humanLabels,
|
|
311
|
+
selfLabelCount: normalizedEventExport.provenance.learningSurface.labelHarvest.selfLabels,
|
|
312
|
+
feedbackCounts,
|
|
313
|
+
compilePackIds,
|
|
314
|
+
relatedInteractionIds,
|
|
315
|
+
feedback
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
export function createContinuousProductLoopState(input) {
|
|
319
|
+
const activationRoot = path.resolve(normalizeNonEmptyString(input.activationRoot, "activationRoot"));
|
|
320
|
+
const loopRoot = path.resolve(normalizeNonEmptyString(input.loopRoot, "loopRoot"));
|
|
321
|
+
const activeTarget = tryReadActivePackTarget(activationRoot);
|
|
322
|
+
const activePack = activeTarget === null ? null : buildPackVersion(1, activeTarget);
|
|
323
|
+
return {
|
|
324
|
+
runtimeOwner: "openclaw",
|
|
325
|
+
activationRoot,
|
|
326
|
+
loopRoot,
|
|
327
|
+
interactionEvents: [],
|
|
328
|
+
feedbackEvents: [],
|
|
329
|
+
learner: createAlwaysOnLearningRuntimeState(),
|
|
330
|
+
runtimePlasticity: null,
|
|
331
|
+
activePackVersion: activePack?.version ?? 0,
|
|
332
|
+
currentActivePack: activePack === null ? null : cloneContinuousProductLoopPackVersion(activePack),
|
|
333
|
+
candidatePack: null,
|
|
334
|
+
packLineage: activePack === null ? [] : [cloneContinuousProductLoopPackVersion(activePack)],
|
|
335
|
+
nextPackVersion: activePack === null ? 1 : 2,
|
|
336
|
+
promotionCount: 0,
|
|
337
|
+
lastSupervision: null
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
function mergeUniqueEvents(current, additions) {
|
|
341
|
+
const merged = new Map();
|
|
342
|
+
for (const event of [...current, ...additions]) {
|
|
343
|
+
merged.set(buildNormalizedEventDedupId(event), structuredClone(event));
|
|
344
|
+
}
|
|
345
|
+
return [...merged.values()].sort((left, right) => left.sequence - right.sequence || left.createdAt.localeCompare(right.createdAt));
|
|
346
|
+
}
|
|
347
|
+
function mergeTeacherArtifacts(current, additions) {
|
|
348
|
+
const merged = new Map();
|
|
349
|
+
for (const artifact of [...current, ...additions]) {
|
|
350
|
+
const existing = merged.get(artifact.dedupId);
|
|
351
|
+
if (existing === undefined ||
|
|
352
|
+
Date.parse(artifact.freshness.observedAt) > Date.parse(existing.freshness.observedAt) ||
|
|
353
|
+
(artifact.freshness.observedAt === existing.freshness.observedAt && artifact.artifactId.localeCompare(existing.artifactId) < 0)) {
|
|
354
|
+
merged.set(artifact.dedupId, structuredClone(artifact));
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
return [...merged.values()].sort((left, right) => {
|
|
358
|
+
if (left.freshness.status !== right.freshness.status) {
|
|
359
|
+
return left.freshness.status === "fresh" ? -1 : 1;
|
|
360
|
+
}
|
|
361
|
+
if (left.createdAt !== right.createdAt) {
|
|
362
|
+
return Date.parse(right.createdAt) - Date.parse(left.createdAt);
|
|
363
|
+
}
|
|
364
|
+
return left.artifactId.localeCompare(right.artifactId);
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
function latestTeacherFreshness(artifacts) {
|
|
368
|
+
return artifacts[0]?.freshness.status ?? "none";
|
|
369
|
+
}
|
|
370
|
+
export class AsyncTeacherLiveLoop {
|
|
371
|
+
input;
|
|
372
|
+
queueCapacity;
|
|
373
|
+
staleAfterMs;
|
|
374
|
+
queuedExportDigests = new Set();
|
|
375
|
+
seenExportDigests = new Set();
|
|
376
|
+
queue = [];
|
|
377
|
+
drainPromise = null;
|
|
378
|
+
interactionEvents = [];
|
|
379
|
+
feedbackEvents = [];
|
|
380
|
+
teacherArtifacts = [];
|
|
381
|
+
learnerState = createAlwaysOnLearningRuntimeState();
|
|
382
|
+
lastMaterialization = null;
|
|
383
|
+
diagnostics = {
|
|
384
|
+
acceptedExportCount: 0,
|
|
385
|
+
processedExportCount: 0,
|
|
386
|
+
duplicateExportCount: 0,
|
|
387
|
+
droppedExportCount: 0,
|
|
388
|
+
emittedArtifactCount: 0,
|
|
389
|
+
dedupedArtifactCount: 0,
|
|
390
|
+
lastProcessedAt: null,
|
|
391
|
+
latestFreshness: "none",
|
|
392
|
+
lastNoOpReason: "none",
|
|
393
|
+
notes: buildAsyncTeacherLoopNotes({
|
|
394
|
+
queueDepth: 0,
|
|
395
|
+
latestFreshness: "none",
|
|
396
|
+
artifactCount: 0,
|
|
397
|
+
emittedArtifactCount: 0,
|
|
398
|
+
dedupedArtifactCount: 0,
|
|
399
|
+
sparseFeedback: this.learnerState.sparseFeedback,
|
|
400
|
+
noOpReason: "none",
|
|
401
|
+
materialization: null
|
|
402
|
+
})
|
|
403
|
+
};
|
|
404
|
+
constructor(input) {
|
|
405
|
+
this.input = input;
|
|
406
|
+
this.queueCapacity = input.maxQueuedExports ?? DEFAULT_ASYNC_TEACHER_QUEUE_CAPACITY;
|
|
407
|
+
this.staleAfterMs = input.staleAfterMs ?? DEFAULT_TEACHER_SUPERVISION_STALE_AFTER_MS;
|
|
408
|
+
if (!Number.isInteger(this.queueCapacity) || this.queueCapacity <= 0) {
|
|
409
|
+
throw new Error("maxQueuedExports must be a positive integer");
|
|
410
|
+
}
|
|
411
|
+
if (!Number.isInteger(this.staleAfterMs) || this.staleAfterMs <= 0) {
|
|
412
|
+
throw new Error("staleAfterMs must be a positive integer");
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
enqueueNormalizedEventExport(normalizedEventExport, options = {}) {
|
|
416
|
+
const validationErrors = validateNormalizedEventExport(normalizedEventExport);
|
|
417
|
+
if (validationErrors.length > 0) {
|
|
418
|
+
throw new Error(`normalized event export is invalid: ${validationErrors.join("; ")}`);
|
|
419
|
+
}
|
|
420
|
+
const exportDigest = normalizedEventExport.provenance.exportDigest;
|
|
421
|
+
if (this.seenExportDigests.has(exportDigest) || this.queuedExportDigests.has(exportDigest)) {
|
|
422
|
+
this.diagnostics.duplicateExportCount += 1;
|
|
423
|
+
this.diagnostics.lastNoOpReason = "duplicate_export";
|
|
424
|
+
this.refreshNotes();
|
|
425
|
+
return {
|
|
426
|
+
accepted: false,
|
|
427
|
+
exportDigest,
|
|
428
|
+
queueDepth: this.queue.length,
|
|
429
|
+
notes: [...this.diagnostics.notes],
|
|
430
|
+
reason: "duplicate_export"
|
|
431
|
+
};
|
|
432
|
+
}
|
|
433
|
+
if (this.queue.length >= this.queueCapacity) {
|
|
434
|
+
this.diagnostics.droppedExportCount += 1;
|
|
435
|
+
this.diagnostics.lastNoOpReason = "queue_full";
|
|
436
|
+
this.refreshNotes();
|
|
437
|
+
return {
|
|
438
|
+
accepted: false,
|
|
439
|
+
exportDigest,
|
|
440
|
+
queueDepth: this.queue.length,
|
|
441
|
+
notes: [...this.diagnostics.notes],
|
|
442
|
+
reason: "queue_full"
|
|
443
|
+
};
|
|
444
|
+
}
|
|
445
|
+
const observedAt = options.observedAt ?? normalizedEventExport.range.lastCreatedAt ?? normalizedEventExport.range.firstCreatedAt ?? new Date().toISOString();
|
|
446
|
+
this.queue.push({
|
|
447
|
+
jobId: `teacher-loop-${createHash("sha256").update(`${exportDigest}:${observedAt}`).digest("hex")}`,
|
|
448
|
+
exportDigest,
|
|
449
|
+
observedAt,
|
|
450
|
+
normalizedEventExport: structuredClone(normalizedEventExport)
|
|
451
|
+
});
|
|
452
|
+
this.queuedExportDigests.add(exportDigest);
|
|
453
|
+
this.diagnostics.acceptedExportCount += 1;
|
|
454
|
+
this.refreshNotes();
|
|
455
|
+
void this.ensureDrain();
|
|
456
|
+
return {
|
|
457
|
+
accepted: true,
|
|
458
|
+
exportDigest,
|
|
459
|
+
queueDepth: this.queue.length,
|
|
460
|
+
notes: [...this.diagnostics.notes],
|
|
461
|
+
reason: null
|
|
462
|
+
};
|
|
463
|
+
}
|
|
464
|
+
async flush() {
|
|
465
|
+
await this.ensureDrain();
|
|
466
|
+
return this.snapshot();
|
|
467
|
+
}
|
|
468
|
+
snapshot() {
|
|
469
|
+
return {
|
|
470
|
+
runtimeOwner: "openclaw",
|
|
471
|
+
queue: {
|
|
472
|
+
capacity: this.queueCapacity,
|
|
473
|
+
depth: this.queue.length,
|
|
474
|
+
running: this.drainPromise !== null
|
|
475
|
+
},
|
|
476
|
+
teacher: {
|
|
477
|
+
artifactCount: this.teacherArtifacts.length,
|
|
478
|
+
artifacts: cloneTeacherSupervisionArtifacts(this.teacherArtifacts),
|
|
479
|
+
latestFreshness: this.diagnostics.latestFreshness
|
|
480
|
+
},
|
|
481
|
+
learner: {
|
|
482
|
+
state: structuredClone(this.learnerState),
|
|
483
|
+
lastMaterialization: cloneAlwaysOnLearningMaterializationJobOrNull(this.lastMaterialization)
|
|
484
|
+
},
|
|
485
|
+
diagnostics: {
|
|
486
|
+
...this.diagnostics,
|
|
487
|
+
notes: [...this.diagnostics.notes]
|
|
488
|
+
}
|
|
489
|
+
};
|
|
490
|
+
}
|
|
491
|
+
async ensureDrain() {
|
|
492
|
+
if (this.drainPromise === null) {
|
|
493
|
+
this.drainPromise = this.drain().finally(() => {
|
|
494
|
+
this.drainPromise = null;
|
|
495
|
+
});
|
|
496
|
+
}
|
|
497
|
+
await this.drainPromise;
|
|
498
|
+
if (this.queue.length > 0) {
|
|
499
|
+
await this.ensureDrain();
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
async drain() {
|
|
503
|
+
while (this.queue.length > 0) {
|
|
504
|
+
const job = this.queue.shift();
|
|
505
|
+
this.queuedExportDigests.delete(job.exportDigest);
|
|
506
|
+
this.seenExportDigests.add(job.exportDigest);
|
|
507
|
+
this.interactionEvents = mergeUniqueEvents(this.interactionEvents, job.normalizedEventExport.interactionEvents);
|
|
508
|
+
this.feedbackEvents = mergeUniqueEvents(this.feedbackEvents, job.normalizedEventExport.feedbackEvents);
|
|
509
|
+
const mergedNormalizedEventExport = buildNormalizedEventExport({
|
|
510
|
+
interactionEvents: this.interactionEvents,
|
|
511
|
+
feedbackEvents: this.feedbackEvents
|
|
512
|
+
});
|
|
513
|
+
const builtArtifacts = buildTeacherSupervisionArtifactsFromNormalizedEventExport({
|
|
514
|
+
normalizedEventExport: mergedNormalizedEventExport,
|
|
515
|
+
observedAt: job.observedAt,
|
|
516
|
+
staleAfterMs: this.staleAfterMs,
|
|
517
|
+
...(this.input.sparseFeedback !== undefined ? { sparseFeedback: this.input.sparseFeedback } : {})
|
|
518
|
+
});
|
|
519
|
+
const currentDedupIds = new Set(this.teacherArtifacts.map((artifact) => artifact.dedupId));
|
|
520
|
+
const nextTeacherArtifacts = mergeTeacherArtifacts(this.teacherArtifacts, builtArtifacts);
|
|
521
|
+
const emittedArtifactCount = builtArtifacts.filter((artifact) => !currentDedupIds.has(artifact.dedupId)).length;
|
|
522
|
+
const dedupedArtifactCount = builtArtifacts.length - emittedArtifactCount;
|
|
523
|
+
this.teacherArtifacts = nextTeacherArtifacts;
|
|
524
|
+
const learnerResult = advanceAlwaysOnLearningRuntime({
|
|
525
|
+
packLabel: this.input.packLabel,
|
|
526
|
+
workspace: this.input.workspace,
|
|
527
|
+
interactionEvents: this.interactionEvents,
|
|
528
|
+
feedbackEvents: this.feedbackEvents,
|
|
529
|
+
teacherSupervisionArtifacts: this.teacherArtifacts,
|
|
530
|
+
learnedRouting: this.input.learnedRouting,
|
|
531
|
+
state: this.learnerState,
|
|
532
|
+
builtAt: this.input.builtAt ?? job.observedAt,
|
|
533
|
+
...(this.input.offlineArtifacts !== undefined ? { offlineArtifacts: this.input.offlineArtifacts } : {}),
|
|
534
|
+
...(this.input.structuralOps !== undefined ? { structuralOps: this.input.structuralOps } : {}),
|
|
535
|
+
...(this.input.sparseFeedback !== undefined ? { sparseFeedback: this.input.sparseFeedback } : {}),
|
|
536
|
+
...(this.input.liveSliceSize !== undefined ? { liveSliceSize: this.input.liveSliceSize } : {}),
|
|
537
|
+
...(this.input.backfillSliceSize !== undefined ? { backfillSliceSize: this.input.backfillSliceSize } : {}),
|
|
538
|
+
...(this.input.cadence !== undefined ? { cadence: this.input.cadence } : {})
|
|
539
|
+
});
|
|
540
|
+
this.learnerState = structuredClone(learnerResult.state);
|
|
541
|
+
this.lastMaterialization = cloneAlwaysOnLearningMaterializationJobOrNull(learnerResult.materialization);
|
|
542
|
+
this.diagnostics.processedExportCount += 1;
|
|
543
|
+
this.diagnostics.emittedArtifactCount += emittedArtifactCount;
|
|
544
|
+
this.diagnostics.dedupedArtifactCount += dedupedArtifactCount;
|
|
545
|
+
this.diagnostics.lastProcessedAt = job.observedAt;
|
|
546
|
+
this.diagnostics.latestFreshness = latestTeacherFreshness(this.teacherArtifacts);
|
|
547
|
+
this.diagnostics.lastNoOpReason = emittedArtifactCount === 0 ? "no_teacher_artifacts" : "none";
|
|
548
|
+
this.refreshNotes();
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
refreshNotes() {
|
|
552
|
+
this.diagnostics.notes = buildAsyncTeacherLoopNotes({
|
|
553
|
+
queueDepth: this.queue.length,
|
|
554
|
+
latestFreshness: this.diagnostics.latestFreshness,
|
|
555
|
+
artifactCount: this.teacherArtifacts.length,
|
|
556
|
+
emittedArtifactCount: this.diagnostics.emittedArtifactCount,
|
|
557
|
+
dedupedArtifactCount: this.diagnostics.dedupedArtifactCount,
|
|
558
|
+
sparseFeedback: this.learnerState.sparseFeedback,
|
|
559
|
+
noOpReason: this.diagnostics.lastNoOpReason,
|
|
560
|
+
materialization: this.lastMaterialization
|
|
561
|
+
});
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
export function createAsyncTeacherLiveLoop(input) {
|
|
565
|
+
return new AsyncTeacherLiveLoop(input);
|
|
566
|
+
}
|
|
18
567
|
function readJsonFile(filePath) {
|
|
19
568
|
return JSON.parse(readFileSync(filePath, "utf8"));
|
|
20
569
|
}
|
|
@@ -209,15 +758,38 @@ export function formatPromptContext(compileResponse) {
|
|
|
209
758
|
lines.push("[/BRAIN_CONTEXT]");
|
|
210
759
|
return `${lines.join("\n")}\n`;
|
|
211
760
|
}
|
|
212
|
-
function
|
|
761
|
+
function failOpenCompileResult(error, activationRoot) {
|
|
213
762
|
return {
|
|
214
763
|
ok: false,
|
|
215
764
|
fallbackToStaticContext: true,
|
|
765
|
+
hardRequirementViolated: false,
|
|
216
766
|
activationRoot: path.resolve(activationRoot),
|
|
217
767
|
error: toErrorMessage(error),
|
|
218
768
|
brainContext: ""
|
|
219
769
|
};
|
|
220
770
|
}
|
|
771
|
+
function classifyCompileFailure(error, activationRoot) {
|
|
772
|
+
const resolvedActivationRoot = path.resolve(activationRoot);
|
|
773
|
+
try {
|
|
774
|
+
const inspection = inspectActivationState(resolvedActivationRoot);
|
|
775
|
+
const active = inspection.active;
|
|
776
|
+
if (active !== null && active.routePolicy === "requires_learned_routing") {
|
|
777
|
+
const failureReason = active.findings.length > 0 ? active.findings.join("; ") : toErrorMessage(error);
|
|
778
|
+
return {
|
|
779
|
+
ok: false,
|
|
780
|
+
fallbackToStaticContext: false,
|
|
781
|
+
hardRequirementViolated: true,
|
|
782
|
+
activationRoot: resolvedActivationRoot,
|
|
783
|
+
error: `Learned-routing hotpath hard requirement violated for active pack ${active.packId} (routerIdentity=${active.routerIdentity ?? "null"}): ${failureReason}`,
|
|
784
|
+
brainContext: ""
|
|
785
|
+
};
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
catch {
|
|
789
|
+
return failOpenCompileResult(error, resolvedActivationRoot);
|
|
790
|
+
}
|
|
791
|
+
return failOpenCompileResult(error, resolvedActivationRoot);
|
|
792
|
+
}
|
|
221
793
|
export function resolveActivePackForCompile(activationRoot) {
|
|
222
794
|
const resolvedActivationRoot = path.resolve(normalizeNonEmptyString(activationRoot, "activationRoot"));
|
|
223
795
|
const inspection = inspectActivationState(resolvedActivationRoot);
|
|
@@ -240,29 +812,348 @@ export function compileRuntimeContext(input) {
|
|
|
240
812
|
const runtimeHints = normalizeRuntimeHints(input.runtimeHints);
|
|
241
813
|
try {
|
|
242
814
|
const target = resolveActivePackForCompile(activationRoot);
|
|
243
|
-
const
|
|
815
|
+
const compile = compileRuntimeFromActivation(activationRoot, {
|
|
244
816
|
contract: CONTRACT_IDS.runtimeCompile,
|
|
245
817
|
agentId,
|
|
246
818
|
userMessage: normalizeNonEmptyString(input.message, "message"),
|
|
247
819
|
maxContextBlocks: normalizeNonNegativeInteger(input.maxContextBlocks, "maxContextBlocks", 4),
|
|
820
|
+
...(input.maxContextChars !== undefined
|
|
821
|
+
? { maxContextChars: normalizeNonNegativeInteger(input.maxContextChars, "maxContextChars", input.maxContextChars) }
|
|
822
|
+
: {}),
|
|
248
823
|
modeRequested: normalizeMode(input.mode),
|
|
249
824
|
activePackId: target.activePointer.packId,
|
|
825
|
+
...(input.compactionMode !== undefined ? { compactionMode: input.compactionMode } : {}),
|
|
250
826
|
...(runtimeHints.length > 0 ? { runtimeHints } : {})
|
|
251
827
|
});
|
|
828
|
+
const compileResponse = {
|
|
829
|
+
...compile.response,
|
|
830
|
+
diagnostics: {
|
|
831
|
+
...compile.response.diagnostics,
|
|
832
|
+
notes: [...compile.response.diagnostics.notes, "OpenClaw remains the runtime owner"]
|
|
833
|
+
}
|
|
834
|
+
};
|
|
252
835
|
return {
|
|
253
836
|
ok: true,
|
|
254
837
|
fallbackToStaticContext: false,
|
|
838
|
+
hardRequirementViolated: false,
|
|
255
839
|
activationRoot,
|
|
256
|
-
activePackId: target.
|
|
840
|
+
activePackId: compile.target.packId,
|
|
257
841
|
packRootDir: path.resolve(target.activePointer.packRootDir),
|
|
258
842
|
compileResponse,
|
|
259
843
|
brainContext: formatPromptContext(compileResponse)
|
|
260
844
|
};
|
|
261
845
|
}
|
|
262
846
|
catch (error) {
|
|
263
|
-
return
|
|
847
|
+
return classifyCompileFailure(error, activationRoot);
|
|
264
848
|
}
|
|
265
849
|
}
|
|
850
|
+
function readDiagnosticNoteValue(notes, prefix) {
|
|
851
|
+
const note = notes.find((entry) => entry.startsWith(prefix));
|
|
852
|
+
return note === undefined ? null : note.slice(prefix.length);
|
|
853
|
+
}
|
|
854
|
+
function readDiagnosticNoteList(notes, prefix) {
|
|
855
|
+
const value = readDiagnosticNoteValue(notes, prefix);
|
|
856
|
+
if (value === null || value.length === 0) {
|
|
857
|
+
return [];
|
|
858
|
+
}
|
|
859
|
+
return value
|
|
860
|
+
.split("|")
|
|
861
|
+
.map((entry) => entry.trim())
|
|
862
|
+
.filter((entry) => entry.length > 0);
|
|
863
|
+
}
|
|
864
|
+
function sortedUniqueStrings(values) {
|
|
865
|
+
return [...new Set(values.filter((value) => value.length > 0))].sort((left, right) => left.localeCompare(right));
|
|
866
|
+
}
|
|
867
|
+
function isStableKernelContextBlock(block) {
|
|
868
|
+
if (block.id.includes(":event:") || block.id.includes(":teacher:")) {
|
|
869
|
+
return false;
|
|
870
|
+
}
|
|
871
|
+
if (block.source.startsWith("split:") || block.source.startsWith("merge:")) {
|
|
872
|
+
return false;
|
|
873
|
+
}
|
|
874
|
+
return true;
|
|
875
|
+
}
|
|
876
|
+
function buildContextAttributionSummary(input) {
|
|
877
|
+
const selectionTiers = readDiagnosticNoteValue(input.notes ?? [], "selection_tiers=");
|
|
878
|
+
const selectedContext = [...(input.selectedContext ?? [])];
|
|
879
|
+
const stableKernelBlocks = selectedContext.filter((block) => isStableKernelContextBlock(block));
|
|
880
|
+
const brainCompiledBlocks = selectedContext.filter((block) => !isStableKernelContextBlock(block));
|
|
881
|
+
if (input.unprobed) {
|
|
882
|
+
return {
|
|
883
|
+
selectedContextCount: 0,
|
|
884
|
+
stableKernelBlockCount: 0,
|
|
885
|
+
brainCompiledBlockCount: 0,
|
|
886
|
+
stableKernelSources: [],
|
|
887
|
+
brainCompiledSources: [],
|
|
888
|
+
selectionTiers,
|
|
889
|
+
evidence: "unprobed",
|
|
890
|
+
detail: "compile probe was not run, so kernel-vs-brain attribution is unknown"
|
|
891
|
+
};
|
|
892
|
+
}
|
|
893
|
+
if (input.hardRequirementViolated) {
|
|
894
|
+
return {
|
|
895
|
+
selectedContextCount: 0,
|
|
896
|
+
stableKernelBlockCount: 0,
|
|
897
|
+
brainCompiledBlockCount: 0,
|
|
898
|
+
stableKernelSources: [],
|
|
899
|
+
brainCompiledSources: [],
|
|
900
|
+
selectionTiers,
|
|
901
|
+
evidence: "hard_fail",
|
|
902
|
+
detail: "learned-routing hard requirement failed before any compiled brain context could be selected"
|
|
903
|
+
};
|
|
904
|
+
}
|
|
905
|
+
if (input.fallbackToStaticContext) {
|
|
906
|
+
return {
|
|
907
|
+
selectedContextCount: 0,
|
|
908
|
+
stableKernelBlockCount: 0,
|
|
909
|
+
brainCompiledBlockCount: 0,
|
|
910
|
+
stableKernelSources: [],
|
|
911
|
+
brainCompiledSources: [],
|
|
912
|
+
selectionTiers,
|
|
913
|
+
evidence: "fail_open_static_context",
|
|
914
|
+
detail: "serve probe fell back to static context, so no compiled brain context was selected"
|
|
915
|
+
};
|
|
916
|
+
}
|
|
917
|
+
const evidence = brainCompiledBlocks.length > 0
|
|
918
|
+
? input.usedLearnedRouteFn === true
|
|
919
|
+
? "route_fn_and_brain_context"
|
|
920
|
+
: "brain_context_only"
|
|
921
|
+
: input.usedLearnedRouteFn === true
|
|
922
|
+
? "route_fn_only"
|
|
923
|
+
: "stable_kernel_only";
|
|
924
|
+
const detailPrefix = `selected=${selectedContext.length}; tiers=${selectionTiers ?? "unknown"}; kernel=${stableKernelBlocks.length}; brain=${brainCompiledBlocks.length}`;
|
|
925
|
+
const detail = evidence === "route_fn_and_brain_context"
|
|
926
|
+
? `${detailPrefix}; learned route ran and non-seed brain-compiled context was selected`
|
|
927
|
+
: evidence === "brain_context_only"
|
|
928
|
+
? `${detailPrefix}; non-seed brain-compiled context was selected without learned-route evidence`
|
|
929
|
+
: evidence === "route_fn_only"
|
|
930
|
+
? `${detailPrefix}; learned route ran but selected context stayed inside the stable kernel`
|
|
931
|
+
: `${detailPrefix}; selected context stayed inside the stable kernel`;
|
|
932
|
+
return {
|
|
933
|
+
selectedContextCount: selectedContext.length,
|
|
934
|
+
stableKernelBlockCount: stableKernelBlocks.length,
|
|
935
|
+
brainCompiledBlockCount: brainCompiledBlocks.length,
|
|
936
|
+
stableKernelSources: sortedUniqueStrings(stableKernelBlocks.map((block) => block.source)),
|
|
937
|
+
brainCompiledSources: sortedUniqueStrings(brainCompiledBlocks.map((block) => block.source)),
|
|
938
|
+
selectionTiers,
|
|
939
|
+
evidence,
|
|
940
|
+
detail
|
|
941
|
+
};
|
|
942
|
+
}
|
|
943
|
+
function buildAttachCompileStatus(result, observability, activePackId) {
|
|
944
|
+
if (!result.ok) {
|
|
945
|
+
return {
|
|
946
|
+
ok: false,
|
|
947
|
+
fallbackToStaticContext: result.fallbackToStaticContext,
|
|
948
|
+
hardRequirementViolated: result.hardRequirementViolated,
|
|
949
|
+
activePackId,
|
|
950
|
+
usedLearnedRouteFn: null,
|
|
951
|
+
routerIdentity: observability?.learnedRouteFn.routerIdentity ?? null,
|
|
952
|
+
initMode: observability?.initHandoff.initMode ?? null,
|
|
953
|
+
handoffState: observability?.initHandoff.handoffState ?? null,
|
|
954
|
+
seedSources: observability?.initHandoff.seedSources ?? [],
|
|
955
|
+
contextAttribution: buildContextAttributionSummary({
|
|
956
|
+
fallbackToStaticContext: result.fallbackToStaticContext,
|
|
957
|
+
hardRequirementViolated: result.hardRequirementViolated,
|
|
958
|
+
usedLearnedRouteFn: null
|
|
959
|
+
}),
|
|
960
|
+
notes: [],
|
|
961
|
+
error: result.error
|
|
962
|
+
};
|
|
963
|
+
}
|
|
964
|
+
const notes = [...result.compileResponse.diagnostics.notes];
|
|
965
|
+
const contextAttribution = buildContextAttributionSummary({
|
|
966
|
+
fallbackToStaticContext: false,
|
|
967
|
+
hardRequirementViolated: false,
|
|
968
|
+
usedLearnedRouteFn: result.compileResponse.diagnostics.usedLearnedRouteFn,
|
|
969
|
+
selectedContext: result.compileResponse.selectedContext,
|
|
970
|
+
notes
|
|
971
|
+
});
|
|
972
|
+
return {
|
|
973
|
+
ok: true,
|
|
974
|
+
fallbackToStaticContext: false,
|
|
975
|
+
hardRequirementViolated: false,
|
|
976
|
+
activePackId: result.activePackId,
|
|
977
|
+
usedLearnedRouteFn: result.compileResponse.diagnostics.usedLearnedRouteFn,
|
|
978
|
+
routerIdentity: result.compileResponse.diagnostics.routerIdentity,
|
|
979
|
+
initMode: readDiagnosticNoteValue(notes, "init_mode=") ?? observability?.initHandoff.initMode ?? null,
|
|
980
|
+
handoffState: readDiagnosticNoteValue(notes, "handoff_state=") ?? observability?.initHandoff.handoffState ?? null,
|
|
981
|
+
seedSources: readDiagnosticNoteList(notes, "seed_sources=").length > 0
|
|
982
|
+
? readDiagnosticNoteList(notes, "seed_sources=")
|
|
983
|
+
: observability?.initHandoff.seedSources ?? [],
|
|
984
|
+
contextAttribution,
|
|
985
|
+
notes,
|
|
986
|
+
error: null
|
|
987
|
+
};
|
|
988
|
+
}
|
|
989
|
+
function buildAttachSuccessSignals(input) {
|
|
990
|
+
const signals = [];
|
|
991
|
+
const activeTarget = input.observability?.target ?? null;
|
|
992
|
+
if (input.inspection.active?.activationReady) {
|
|
993
|
+
signals.push(`active_pack_ready:${input.inspection.active.packId}`);
|
|
994
|
+
}
|
|
995
|
+
if (activeTarget !== null) {
|
|
996
|
+
signals.push(`active_workspace_snapshot:${activeTarget.workspaceSnapshot}`);
|
|
997
|
+
}
|
|
998
|
+
if (input.compile?.ok) {
|
|
999
|
+
signals.push(`compile_ok:${input.compile.activePackId ?? "unknown"}`);
|
|
1000
|
+
}
|
|
1001
|
+
if (input.compile !== null) {
|
|
1002
|
+
signals.push(`context:${input.compile.contextAttribution.evidence}`);
|
|
1003
|
+
signals.push(`context_blocks:kernel=${input.compile.contextAttribution.stableKernelBlockCount},brain=${input.compile.contextAttribution.brainCompiledBlockCount}`);
|
|
1004
|
+
}
|
|
1005
|
+
if (input.compile?.handoffState !== null && input.compile?.handoffState !== undefined) {
|
|
1006
|
+
signals.push(`handoff:${input.compile.handoffState}`);
|
|
1007
|
+
}
|
|
1008
|
+
if (input.inspection.active?.routePolicy === "requires_learned_routing") {
|
|
1009
|
+
if (input.compile?.ok && input.compile.usedLearnedRouteFn === true && input.compile.routerIdentity !== null) {
|
|
1010
|
+
signals.push(`learned_route_compile_verified:${input.compile.routerIdentity}`);
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
else if (input.compile?.ok) {
|
|
1014
|
+
signals.push("heuristic_compile_verified");
|
|
1015
|
+
}
|
|
1016
|
+
return signals;
|
|
1017
|
+
}
|
|
1018
|
+
function buildAttachStatusCompileInput(activationRoot, compile) {
|
|
1019
|
+
if (compile === false) {
|
|
1020
|
+
return null;
|
|
1021
|
+
}
|
|
1022
|
+
return {
|
|
1023
|
+
activationRoot,
|
|
1024
|
+
agentId: normalizeOptionalString(compile?.agentId) ?? `${DEFAULT_AGENT_ID}-attach-status`,
|
|
1025
|
+
message: normalizeOptionalString(compile?.message) ?? DEFAULT_ATTACH_STATUS_MESSAGE,
|
|
1026
|
+
...(compile?.maxContextBlocks !== undefined ? { maxContextBlocks: compile.maxContextBlocks } : {}),
|
|
1027
|
+
...(compile?.maxContextChars !== undefined ? { maxContextChars: compile.maxContextChars } : {}),
|
|
1028
|
+
...(compile?.mode !== undefined ? { mode: compile.mode } : {}),
|
|
1029
|
+
...(compile?.compactionMode !== undefined ? { compactionMode: compile.compactionMode } : {}),
|
|
1030
|
+
runtimeHints: compile?.runtimeHints ?? [...DEFAULT_ATTACH_STATUS_RUNTIME_HINTS]
|
|
1031
|
+
};
|
|
1032
|
+
}
|
|
1033
|
+
export function describeAttachStatus(input) {
|
|
1034
|
+
const activationRoot = path.resolve(normalizeNonEmptyString(input.activationRoot, "activationRoot"));
|
|
1035
|
+
const inspection = inspectActivationState(activationRoot);
|
|
1036
|
+
const activeObservability = inspection.active === null ? null : describeActivationObservability(activationRoot, "active");
|
|
1037
|
+
const compileInput = buildAttachStatusCompileInput(activationRoot, input.compile);
|
|
1038
|
+
const compile = compileInput === null
|
|
1039
|
+
? null
|
|
1040
|
+
: buildAttachCompileStatus(compileRuntimeContext(compileInput), activeObservability, inspection.active?.packId ?? null);
|
|
1041
|
+
return {
|
|
1042
|
+
runtimeOwner: "openclaw",
|
|
1043
|
+
activationRoot,
|
|
1044
|
+
inspection,
|
|
1045
|
+
activeObservability,
|
|
1046
|
+
compile,
|
|
1047
|
+
landingBoundaries: structuredClone(OPENCLAW_LANDING_BOUNDARIES_V1),
|
|
1048
|
+
successSignals: buildAttachSuccessSignals({
|
|
1049
|
+
inspection,
|
|
1050
|
+
observability: activeObservability,
|
|
1051
|
+
compile
|
|
1052
|
+
})
|
|
1053
|
+
};
|
|
1054
|
+
}
|
|
1055
|
+
export function rollbackRuntimeAttach(input) {
|
|
1056
|
+
const activationRoot = path.resolve(normalizeNonEmptyString(input.activationRoot, "activationRoot"));
|
|
1057
|
+
const updatedAt = normalizeIsoTimestamp(input.updatedAt, "updatedAt", new Date().toISOString());
|
|
1058
|
+
const dryRun = input.dryRun === true;
|
|
1059
|
+
const before = inspectActivationState(activationRoot, updatedAt);
|
|
1060
|
+
const findings = [...before.rollback.findings];
|
|
1061
|
+
const allowed = before.rollback.allowed;
|
|
1062
|
+
if (!allowed) {
|
|
1063
|
+
return {
|
|
1064
|
+
runtimeOwner: "openclaw",
|
|
1065
|
+
activationRoot,
|
|
1066
|
+
updatedAt,
|
|
1067
|
+
dryRun,
|
|
1068
|
+
allowed,
|
|
1069
|
+
findings,
|
|
1070
|
+
before: {
|
|
1071
|
+
activePackId: before.active?.packId ?? before.pointers.active?.packId ?? null,
|
|
1072
|
+
candidatePackId: before.candidate?.packId ?? before.pointers.candidate?.packId ?? null,
|
|
1073
|
+
previousPackId: before.previous?.packId ?? before.pointers.previous?.packId ?? null
|
|
1074
|
+
},
|
|
1075
|
+
after: null,
|
|
1076
|
+
restoredPackId: null,
|
|
1077
|
+
parkedCandidatePackId: null
|
|
1078
|
+
};
|
|
1079
|
+
}
|
|
1080
|
+
const after = dryRun
|
|
1081
|
+
? before.rollback.nextPointers
|
|
1082
|
+
: rollbackActivePack(activationRoot, updatedAt).pointers;
|
|
1083
|
+
return {
|
|
1084
|
+
runtimeOwner: "openclaw",
|
|
1085
|
+
activationRoot,
|
|
1086
|
+
updatedAt,
|
|
1087
|
+
dryRun,
|
|
1088
|
+
allowed,
|
|
1089
|
+
findings,
|
|
1090
|
+
before: {
|
|
1091
|
+
activePackId: before.active?.packId ?? before.pointers.active?.packId ?? null,
|
|
1092
|
+
candidatePackId: before.candidate?.packId ?? before.pointers.candidate?.packId ?? null,
|
|
1093
|
+
previousPackId: before.previous?.packId ?? before.pointers.previous?.packId ?? null
|
|
1094
|
+
},
|
|
1095
|
+
after: after === null ? null : {
|
|
1096
|
+
activePackId: after.active?.packId ?? null,
|
|
1097
|
+
candidatePackId: after.candidate?.packId ?? null,
|
|
1098
|
+
previousPackId: after.previous?.packId ?? null
|
|
1099
|
+
},
|
|
1100
|
+
restoredPackId: after?.active?.packId ?? null,
|
|
1101
|
+
parkedCandidatePackId: after?.candidate?.packId ?? null
|
|
1102
|
+
};
|
|
1103
|
+
}
|
|
1104
|
+
function resolveBootstrapNormalizedEventExport(input) {
|
|
1105
|
+
const interactionEvents = [...(input.interactionEvents ?? [])];
|
|
1106
|
+
const feedbackEvents = [...(input.feedbackEvents ?? [])];
|
|
1107
|
+
if (input.normalizedEventExport !== undefined && (interactionEvents.length > 0 || feedbackEvents.length > 0)) {
|
|
1108
|
+
throw new Error("Provide normalizedEventExport or interactionEvents/feedbackEvents, not both");
|
|
1109
|
+
}
|
|
1110
|
+
const normalizedEventExport = input.normalizedEventExport !== undefined
|
|
1111
|
+
? structuredClone(input.normalizedEventExport)
|
|
1112
|
+
: buildNormalizedEventExport({
|
|
1113
|
+
interactionEvents,
|
|
1114
|
+
feedbackEvents
|
|
1115
|
+
});
|
|
1116
|
+
const validationErrors = validateNormalizedEventExport(normalizedEventExport);
|
|
1117
|
+
if (validationErrors.length > 0) {
|
|
1118
|
+
throw new Error(`normalized event export is invalid: ${validationErrors.join("; ")}`);
|
|
1119
|
+
}
|
|
1120
|
+
return normalizedEventExport;
|
|
1121
|
+
}
|
|
1122
|
+
export function bootstrapRuntimeAttach(input) {
|
|
1123
|
+
const activationRoot = path.resolve(normalizeNonEmptyString(input.activationRoot, "activationRoot"));
|
|
1124
|
+
const packRoot = path.resolve(normalizeNonEmptyString(input.packRoot, "packRoot"));
|
|
1125
|
+
const normalizedEventExport = resolveBootstrapNormalizedEventExport(input);
|
|
1126
|
+
const builtAt = normalizeIsoTimestamp(input.builtAt, "builtAt", normalizedEventExport.range.lastCreatedAt ?? normalizedEventExport.range.firstCreatedAt ?? new Date().toISOString());
|
|
1127
|
+
const activatedAt = normalizeIsoTimestamp(input.activatedAt, "activatedAt", builtAt);
|
|
1128
|
+
const teacherSupervisionArtifacts = buildTeacherSupervisionArtifactsFromNormalizedEventExport({
|
|
1129
|
+
normalizedEventExport,
|
|
1130
|
+
observedAt: activatedAt,
|
|
1131
|
+
...(input.sparseFeedback !== undefined ? { sparseFeedback: input.sparseFeedback } : {})
|
|
1132
|
+
});
|
|
1133
|
+
const descriptor = materializeCandidatePackFromNormalizedEventExport(packRoot, {
|
|
1134
|
+
packLabel: normalizeNonEmptyString(input.packLabel, "packLabel"),
|
|
1135
|
+
workspace: input.workspace,
|
|
1136
|
+
normalizedEventExport,
|
|
1137
|
+
teacherSupervisionArtifacts,
|
|
1138
|
+
learnedRouting: input.learnedRouting ?? true,
|
|
1139
|
+
builtAt,
|
|
1140
|
+
...(input.offlineArtifacts !== undefined ? { offlineArtifacts: input.offlineArtifacts } : {}),
|
|
1141
|
+
...(input.structuralOps !== undefined ? { structuralOps: input.structuralOps } : {}),
|
|
1142
|
+
...(input.sparseFeedback !== undefined ? { sparseFeedback: input.sparseFeedback } : {})
|
|
1143
|
+
});
|
|
1144
|
+
activatePack(activationRoot, packRoot, activatedAt);
|
|
1145
|
+
return {
|
|
1146
|
+
runtimeOwner: "openclaw",
|
|
1147
|
+
activationRoot,
|
|
1148
|
+
packRoot,
|
|
1149
|
+
packId: descriptor.manifest.packId,
|
|
1150
|
+
normalizedEventExport,
|
|
1151
|
+
status: describeAttachStatus({
|
|
1152
|
+
activationRoot,
|
|
1153
|
+
...(input.compile !== undefined ? { compile: input.compile } : {})
|
|
1154
|
+
})
|
|
1155
|
+
};
|
|
1156
|
+
}
|
|
266
1157
|
function buildCompileInteractionEvent(input) {
|
|
267
1158
|
if (!input.compileResult.ok) {
|
|
268
1159
|
return null;
|
|
@@ -466,6 +1357,9 @@ export function runRuntimeTurn(turn, options = {}) {
|
|
|
466
1357
|
...(turn.runtimeHints !== undefined ? { runtimeHints: turn.runtimeHints } : {})
|
|
467
1358
|
};
|
|
468
1359
|
const compileResult = compileRuntimeContext(compileInput);
|
|
1360
|
+
if (!compileResult.ok && compileResult.hardRequirementViolated) {
|
|
1361
|
+
throw new Error(compileResult.error);
|
|
1362
|
+
}
|
|
469
1363
|
const warnings = [];
|
|
470
1364
|
try {
|
|
471
1365
|
const normalizedEventExport = buildNormalizedRuntimeEventExport(turn, compileResult);
|
|
@@ -492,4 +1386,1345 @@ export function runRuntimeTurn(turn, options = {}) {
|
|
|
492
1386
|
};
|
|
493
1387
|
}
|
|
494
1388
|
}
|
|
1389
|
+
export function runContinuousProductLoopTurn(input) {
|
|
1390
|
+
const activationRoot = path.resolve(normalizeNonEmptyString(input.activationRoot, "activationRoot"));
|
|
1391
|
+
const loopRoot = path.resolve(normalizeNonEmptyString(input.loopRoot, "loopRoot"));
|
|
1392
|
+
const failOpen = input.failOpen !== false;
|
|
1393
|
+
const currentState = cloneContinuousProductLoopState(input.state ??
|
|
1394
|
+
createContinuousProductLoopState({
|
|
1395
|
+
activationRoot,
|
|
1396
|
+
loopRoot
|
|
1397
|
+
}));
|
|
1398
|
+
currentState.activationRoot = activationRoot;
|
|
1399
|
+
currentState.loopRoot = loopRoot;
|
|
1400
|
+
const activeBeforeTurn = syncContinuousActivePack(currentState);
|
|
1401
|
+
const compileActiveVersion = activeBeforeTurn?.version ?? 0;
|
|
1402
|
+
const compileActivePackId = activeBeforeTurn?.packId ?? null;
|
|
1403
|
+
const turn = withContinuousTurnExport(input.turn, loopRoot);
|
|
1404
|
+
const turnResult = runRuntimeTurn(turn, {
|
|
1405
|
+
activationRoot,
|
|
1406
|
+
failOpen
|
|
1407
|
+
});
|
|
1408
|
+
const learningWarnings = [];
|
|
1409
|
+
let supervision = null;
|
|
1410
|
+
const learning = {
|
|
1411
|
+
warnings: learningWarnings,
|
|
1412
|
+
supervisionDigest: null,
|
|
1413
|
+
bridgeDigest: null,
|
|
1414
|
+
selectedSliceIds: [],
|
|
1415
|
+
materializationJobId: null,
|
|
1416
|
+
materializationReason: null,
|
|
1417
|
+
materializationLane: null,
|
|
1418
|
+
candidateRootDir: null,
|
|
1419
|
+
candidatePack: currentState.candidatePack === null ? null : cloneContinuousProductLoopPackVersion(currentState.candidatePack),
|
|
1420
|
+
runtimePlasticity: currentState.runtimePlasticity === null ? null : structuredClone(currentState.runtimePlasticity),
|
|
1421
|
+
promotionAllowed: false,
|
|
1422
|
+
promotionFindings: [],
|
|
1423
|
+
promoted: false
|
|
1424
|
+
};
|
|
1425
|
+
if (!turnResult.eventExport.ok) {
|
|
1426
|
+
learningWarnings.push(`continuous learner skipped: ${turnResult.eventExport.error}`);
|
|
1427
|
+
return {
|
|
1428
|
+
runtimeOwner: "openclaw",
|
|
1429
|
+
compileActiveVersion,
|
|
1430
|
+
compileActivePackId,
|
|
1431
|
+
turn: turnResult,
|
|
1432
|
+
supervision,
|
|
1433
|
+
learning,
|
|
1434
|
+
state: cloneContinuousProductLoopState(currentState)
|
|
1435
|
+
};
|
|
1436
|
+
}
|
|
1437
|
+
const normalizedEventExport = turnResult.eventExport.normalizedEventExport;
|
|
1438
|
+
supervision = buildCanonicalSupervision(normalizedEventExport);
|
|
1439
|
+
learning.supervisionDigest = supervision.supervisionDigest;
|
|
1440
|
+
currentState.lastSupervision = cloneCanonicalSupervision(supervision);
|
|
1441
|
+
const mergedHistory = mergeRuntimeEventHistory(currentState, normalizedEventExport);
|
|
1442
|
+
currentState.interactionEvents = mergedHistory.interactionEvents;
|
|
1443
|
+
currentState.feedbackEvents = mergedHistory.feedbackEvents;
|
|
1444
|
+
try {
|
|
1445
|
+
const learnerResult = advanceAlwaysOnLearningRuntime({
|
|
1446
|
+
packLabel: input.packLabel,
|
|
1447
|
+
workspace: input.workspace,
|
|
1448
|
+
interactionEvents: currentState.interactionEvents,
|
|
1449
|
+
feedbackEvents: currentState.feedbackEvents,
|
|
1450
|
+
learnedRouting: input.learnedRouting ?? true,
|
|
1451
|
+
state: currentState.learner,
|
|
1452
|
+
builtAt: normalizeIsoTimestamp(input.candidateBuiltAt, "candidateBuiltAt", normalizedEventExport.range.lastCreatedAt ?? normalizedEventExport.range.firstCreatedAt),
|
|
1453
|
+
...(input.offlineArtifacts !== undefined ? { offlineArtifacts: input.offlineArtifacts } : {}),
|
|
1454
|
+
...(input.structuralOps !== undefined ? { structuralOps: input.structuralOps } : {}),
|
|
1455
|
+
...(input.sparseFeedback !== undefined ? { sparseFeedback: input.sparseFeedback } : {}),
|
|
1456
|
+
...(input.liveSliceSize !== undefined ? { liveSliceSize: input.liveSliceSize } : {}),
|
|
1457
|
+
...(input.backfillSliceSize !== undefined ? { backfillSliceSize: input.backfillSliceSize } : {}),
|
|
1458
|
+
...(input.cadence !== undefined ? { cadence: input.cadence } : {})
|
|
1459
|
+
});
|
|
1460
|
+
currentState.learner = structuredClone(learnerResult.state);
|
|
1461
|
+
currentState.runtimePlasticity = learnerResult.state.runtimePlasticity === null ? null : structuredClone(learnerResult.state.runtimePlasticity);
|
|
1462
|
+
learning.runtimePlasticity = currentState.runtimePlasticity === null ? null : structuredClone(currentState.runtimePlasticity);
|
|
1463
|
+
learning.bridgeDigest = learnerResult.bridge.bridgeDigest;
|
|
1464
|
+
learning.selectedSliceIds = learnerResult.selectedSlices.map((slice) => slice.sliceId);
|
|
1465
|
+
learning.materializationJobId = learnerResult.materialization?.jobId ?? null;
|
|
1466
|
+
learning.materializationReason = learnerResult.materialization?.reason ?? null;
|
|
1467
|
+
learning.materializationLane = learnerResult.materialization?.lane ?? null;
|
|
1468
|
+
if (learnerResult.materialization !== null) {
|
|
1469
|
+
const candidatePack = registerPackVersion(currentState, buildLearningCandidateTarget(learnerResult.materialization.candidate));
|
|
1470
|
+
const candidateRootDir = buildContinuousPackRoot(loopRoot, candidatePack);
|
|
1471
|
+
const descriptor = materializeAlwaysOnLearningCandidatePack(candidateRootDir, learnerResult.materialization);
|
|
1472
|
+
const candidateTarget = describePackCompileTarget(descriptor);
|
|
1473
|
+
learning.candidateRootDir = candidateRootDir;
|
|
1474
|
+
learning.candidatePack = cloneContinuousProductLoopPackVersion(candidatePack);
|
|
1475
|
+
currentState.candidatePack = cloneContinuousProductLoopPackVersion(candidatePack);
|
|
1476
|
+
const stagedAt = normalizeIsoTimestamp(input.stageUpdatedAt, "stageUpdatedAt", descriptor.manifest.provenance.builtAt);
|
|
1477
|
+
stageCandidatePack(activationRoot, candidateRootDir, stagedAt);
|
|
1478
|
+
const stagedInspection = inspectActivationState(activationRoot, stagedAt);
|
|
1479
|
+
learning.promotionAllowed = stagedInspection.promotion.allowed;
|
|
1480
|
+
learning.promotionFindings = [...stagedInspection.promotion.findings];
|
|
1481
|
+
if ((input.autoPromote ?? true) && stagedInspection.promotion.allowed) {
|
|
1482
|
+
const promotedAt = normalizeIsoTimestamp(input.promoteUpdatedAt, "promoteUpdatedAt", stagedAt);
|
|
1483
|
+
promoteCandidatePack(activationRoot, promotedAt);
|
|
1484
|
+
currentState.promotionCount += 1;
|
|
1485
|
+
currentState.candidatePack = null;
|
|
1486
|
+
learning.promoted = true;
|
|
1487
|
+
const activePack = registerPackVersion(currentState, candidateTarget);
|
|
1488
|
+
currentState.currentActivePack = cloneContinuousProductLoopPackVersion(activePack);
|
|
1489
|
+
currentState.activePackVersion = activePack.version;
|
|
1490
|
+
syncContinuousActivePack(currentState);
|
|
1491
|
+
}
|
|
1492
|
+
}
|
|
1493
|
+
}
|
|
1494
|
+
catch (error) {
|
|
1495
|
+
if (!failOpen) {
|
|
1496
|
+
throw error;
|
|
1497
|
+
}
|
|
1498
|
+
learningWarnings.push(`continuous learner failed open: ${toErrorMessage(error)}`);
|
|
1499
|
+
}
|
|
1500
|
+
return {
|
|
1501
|
+
runtimeOwner: "openclaw",
|
|
1502
|
+
compileActiveVersion,
|
|
1503
|
+
compileActivePackId,
|
|
1504
|
+
turn: turnResult,
|
|
1505
|
+
supervision,
|
|
1506
|
+
learning,
|
|
1507
|
+
state: cloneContinuousProductLoopState(currentState)
|
|
1508
|
+
};
|
|
1509
|
+
}
|
|
1510
|
+
function ensureRecordedSessionTrace(trace) {
|
|
1511
|
+
if (trace.contract !== RECORDED_SESSION_TRACE_CONTRACT) {
|
|
1512
|
+
throw new Error(`${RECORDED_SESSION_TRACE_CONTRACT} contract is required`);
|
|
1513
|
+
}
|
|
1514
|
+
normalizeNonEmptyString(trace.traceId, "traceId");
|
|
1515
|
+
normalizeIsoTimestamp(trace.recordedAt, "recordedAt");
|
|
1516
|
+
normalizeIsoTimestamp(trace.bundleBuiltAt, "bundleBuiltAt");
|
|
1517
|
+
normalizeNonEmptyString(trace.sessionId, "sessionId");
|
|
1518
|
+
normalizeNonEmptyString(trace.channel, "channel");
|
|
1519
|
+
normalizeNonEmptyString(trace.sourceStream, "sourceStream");
|
|
1520
|
+
normalizeIsoTimestamp(trace.seedBuiltAt, "seedBuiltAt");
|
|
1521
|
+
normalizeIsoTimestamp(trace.seedActivatedAt, "seedActivatedAt");
|
|
1522
|
+
if (trace.privacy.sanitized !== true) {
|
|
1523
|
+
throw new Error("recorded session trace must be explicitly sanitized");
|
|
1524
|
+
}
|
|
1525
|
+
if (trace.seedCues.length === 0) {
|
|
1526
|
+
throw new Error("recorded session trace requires at least one seed cue");
|
|
1527
|
+
}
|
|
1528
|
+
if (trace.turns.length === 0) {
|
|
1529
|
+
throw new Error("recorded session trace requires at least one turn");
|
|
1530
|
+
}
|
|
1531
|
+
for (const [index, cue] of trace.seedCues.entries()) {
|
|
1532
|
+
normalizeNonEmptyString(cue.cueId, `seedCues[${index}].cueId`);
|
|
1533
|
+
normalizeIsoTimestamp(cue.createdAt, `seedCues[${index}].createdAt`);
|
|
1534
|
+
normalizeNonEmptyString(cue.content, `seedCues[${index}].content`);
|
|
1535
|
+
}
|
|
1536
|
+
for (const [index, turn] of trace.turns.entries()) {
|
|
1537
|
+
normalizeIsoTimestamp(turn.createdAt, `turns[${index}].createdAt`);
|
|
1538
|
+
normalizeNonEmptyString(turn.userMessage, `turns[${index}].userMessage`);
|
|
1539
|
+
if ((turn.expectedContextPhrases ?? []).length === 0) {
|
|
1540
|
+
throw new Error(`turns[${index}].expectedContextPhrases must include at least one phrase`);
|
|
1541
|
+
}
|
|
1542
|
+
for (const [feedbackIndex, feedback] of (turn.feedback ?? []).entries()) {
|
|
1543
|
+
normalizeIsoTimestamp(feedback.createdAt, `turns[${index}].feedback[${feedbackIndex}].createdAt`);
|
|
1544
|
+
normalizeNonEmptyString(feedback.content, `turns[${index}].feedback[${feedbackIndex}].content`);
|
|
1545
|
+
}
|
|
1546
|
+
}
|
|
1547
|
+
}
|
|
1548
|
+
function padReplayNumber(value) {
|
|
1549
|
+
return String(value).padStart(2, "0");
|
|
1550
|
+
}
|
|
1551
|
+
function replayTurnId(traceId, index, explicitValue) {
|
|
1552
|
+
return normalizeOptionalString(explicitValue) ?? `${traceId}-turn-${padReplayNumber(index + 1)}`;
|
|
1553
|
+
}
|
|
1554
|
+
function replaySequenceStart(index) {
|
|
1555
|
+
return 1_000 + index * 10;
|
|
1556
|
+
}
|
|
1557
|
+
function replayMessageId(turnId) {
|
|
1558
|
+
return `msg-${turnId}`;
|
|
1559
|
+
}
|
|
1560
|
+
function addMinutes(value, minutes) {
|
|
1561
|
+
return new Date(Date.parse(value) + minutes * 60_000).toISOString();
|
|
1562
|
+
}
|
|
1563
|
+
function normalizeReplayPhrase(value) {
|
|
1564
|
+
return value.toLowerCase().replace(/\s+/gu, " ").trim();
|
|
1565
|
+
}
|
|
1566
|
+
function hasReplayPhrase(texts, phrase) {
|
|
1567
|
+
const normalizedPhrase = normalizeReplayPhrase(phrase);
|
|
1568
|
+
return texts.some((text) => normalizeReplayPhrase(text).includes(normalizedPhrase));
|
|
1569
|
+
}
|
|
1570
|
+
function uniqueStringsInOrder(values) {
|
|
1571
|
+
const seen = new Set();
|
|
1572
|
+
const unique = [];
|
|
1573
|
+
for (const value of values) {
|
|
1574
|
+
if (seen.has(value)) {
|
|
1575
|
+
continue;
|
|
1576
|
+
}
|
|
1577
|
+
seen.add(value);
|
|
1578
|
+
unique.push(value);
|
|
1579
|
+
}
|
|
1580
|
+
return unique;
|
|
1581
|
+
}
|
|
1582
|
+
function buildRecordedSessionSeedExport(trace) {
|
|
1583
|
+
const agentId = normalizeOptionalString(trace.agentId) ?? DEFAULT_AGENT_ID;
|
|
1584
|
+
const seedSessionId = `${trace.sessionId}-seed`;
|
|
1585
|
+
const sourceStream = `${trace.sourceStream}/seed`;
|
|
1586
|
+
const interactionEvents = [];
|
|
1587
|
+
const feedbackEvents = [];
|
|
1588
|
+
let sequence = 1;
|
|
1589
|
+
for (const cue of trace.seedCues) {
|
|
1590
|
+
const interactionEventId = `${seedSessionId}:${cue.cueId}:interaction`;
|
|
1591
|
+
const feedbackEventId = `${seedSessionId}:${cue.cueId}:feedback`;
|
|
1592
|
+
const interaction = createInteractionEvent({
|
|
1593
|
+
eventId: interactionEventId,
|
|
1594
|
+
agentId,
|
|
1595
|
+
sessionId: seedSessionId,
|
|
1596
|
+
channel: trace.channel,
|
|
1597
|
+
sequence,
|
|
1598
|
+
kind: "operator_override",
|
|
1599
|
+
createdAt: cue.createdAt,
|
|
1600
|
+
source: {
|
|
1601
|
+
runtimeOwner: "openclaw",
|
|
1602
|
+
stream: sourceStream
|
|
1603
|
+
},
|
|
1604
|
+
messageId: `${cue.cueId}-seed-message`
|
|
1605
|
+
});
|
|
1606
|
+
sequence += 1;
|
|
1607
|
+
const feedback = createFeedbackEvent({
|
|
1608
|
+
eventId: feedbackEventId,
|
|
1609
|
+
agentId,
|
|
1610
|
+
sessionId: seedSessionId,
|
|
1611
|
+
channel: trace.channel,
|
|
1612
|
+
sequence,
|
|
1613
|
+
kind: cue.kind ?? "teaching",
|
|
1614
|
+
createdAt: addMinutes(cue.createdAt, 1),
|
|
1615
|
+
source: {
|
|
1616
|
+
runtimeOwner: "openclaw",
|
|
1617
|
+
stream: sourceStream
|
|
1618
|
+
},
|
|
1619
|
+
content: cue.content,
|
|
1620
|
+
relatedInteractionId: interaction.eventId
|
|
1621
|
+
});
|
|
1622
|
+
sequence += 1;
|
|
1623
|
+
interactionEvents.push(interaction);
|
|
1624
|
+
feedbackEvents.push(feedback);
|
|
1625
|
+
}
|
|
1626
|
+
return buildNormalizedEventExport({
|
|
1627
|
+
interactionEvents,
|
|
1628
|
+
feedbackEvents
|
|
1629
|
+
});
|
|
1630
|
+
}
|
|
1631
|
+
function recordedSessionFixtureBase(trace) {
|
|
1632
|
+
const traceHash = checksumJsonPayload(trace);
|
|
1633
|
+
const turns = trace.turns.map((turn, index) => {
|
|
1634
|
+
const turnId = replayTurnId(trace.traceId, index, turn.turnId);
|
|
1635
|
+
const sequenceStart = replaySequenceStart(index);
|
|
1636
|
+
return {
|
|
1637
|
+
turnId,
|
|
1638
|
+
turn: {
|
|
1639
|
+
...(trace.agentId !== undefined && trace.agentId !== null ? { agentId: trace.agentId } : {}),
|
|
1640
|
+
sessionId: trace.sessionId,
|
|
1641
|
+
channel: trace.channel,
|
|
1642
|
+
sourceStream: trace.sourceStream,
|
|
1643
|
+
userMessage: turn.userMessage,
|
|
1644
|
+
createdAt: turn.createdAt,
|
|
1645
|
+
sequenceStart,
|
|
1646
|
+
maxContextBlocks: 3,
|
|
1647
|
+
mode: "heuristic",
|
|
1648
|
+
...(turn.runtimeHints !== undefined ? { runtimeHints: [...turn.runtimeHints] } : {}),
|
|
1649
|
+
compile: {
|
|
1650
|
+
createdAt: turn.createdAt,
|
|
1651
|
+
sequence: sequenceStart
|
|
1652
|
+
},
|
|
1653
|
+
delivery: turn.deliveredAt === undefined || turn.deliveredAt === null
|
|
1654
|
+
? false
|
|
1655
|
+
: {
|
|
1656
|
+
createdAt: turn.deliveredAt,
|
|
1657
|
+
sequence: sequenceStart + 1,
|
|
1658
|
+
messageId: replayMessageId(turnId)
|
|
1659
|
+
},
|
|
1660
|
+
feedback: (turn.feedback ?? []).map((feedback, feedbackIndex) => ({
|
|
1661
|
+
createdAt: feedback.createdAt,
|
|
1662
|
+
content: feedback.content,
|
|
1663
|
+
sequence: sequenceStart + 2 + feedbackIndex,
|
|
1664
|
+
kind: feedback.kind ?? null
|
|
1665
|
+
}))
|
|
1666
|
+
},
|
|
1667
|
+
expectedContextPhrases: [...turn.expectedContextPhrases],
|
|
1668
|
+
minimumPhraseHits: Math.max(1, turn.minimumPhraseHits ?? turn.expectedContextPhrases.length)
|
|
1669
|
+
};
|
|
1670
|
+
});
|
|
1671
|
+
return {
|
|
1672
|
+
contract: RECORDED_SESSION_FIXTURE_CONTRACT,
|
|
1673
|
+
traceId: trace.traceId,
|
|
1674
|
+
source: trace.source,
|
|
1675
|
+
recordedAt: trace.recordedAt,
|
|
1676
|
+
bundleBuiltAt: trace.bundleBuiltAt,
|
|
1677
|
+
traceHash,
|
|
1678
|
+
privacy: {
|
|
1679
|
+
sanitized: true,
|
|
1680
|
+
notes: [...trace.privacy.notes]
|
|
1681
|
+
},
|
|
1682
|
+
workspace: {
|
|
1683
|
+
workspaceId: trace.workspace.workspaceId,
|
|
1684
|
+
snapshotId: trace.workspace.snapshotId,
|
|
1685
|
+
capturedAt: trace.workspace.capturedAt,
|
|
1686
|
+
rootDir: trace.workspace.rootDir,
|
|
1687
|
+
...(trace.workspace.branch !== undefined ? { branch: trace.workspace.branch } : {}),
|
|
1688
|
+
revision: trace.workspace.revision,
|
|
1689
|
+
...(trace.workspace.labels !== undefined ? { labels: [...trace.workspace.labels] } : {})
|
|
1690
|
+
},
|
|
1691
|
+
seedBuiltAt: trace.seedBuiltAt,
|
|
1692
|
+
seedActivatedAt: trace.seedActivatedAt,
|
|
1693
|
+
seedExport: buildRecordedSessionSeedExport(trace),
|
|
1694
|
+
turns
|
|
1695
|
+
};
|
|
1696
|
+
}
|
|
1697
|
+
export function buildRecordedSessionReplayFixture(trace) {
|
|
1698
|
+
ensureRecordedSessionTrace(trace);
|
|
1699
|
+
const base = recordedSessionFixtureBase(trace);
|
|
1700
|
+
return {
|
|
1701
|
+
...base,
|
|
1702
|
+
fixtureHash: checksumJsonPayload(base)
|
|
1703
|
+
};
|
|
1704
|
+
}
|
|
1705
|
+
function recordedSessionReplayFixtureBase(fixture) {
|
|
1706
|
+
return {
|
|
1707
|
+
contract: RECORDED_SESSION_FIXTURE_CONTRACT,
|
|
1708
|
+
traceId: fixture.traceId,
|
|
1709
|
+
source: fixture.source,
|
|
1710
|
+
recordedAt: fixture.recordedAt,
|
|
1711
|
+
bundleBuiltAt: fixture.bundleBuiltAt,
|
|
1712
|
+
traceHash: fixture.traceHash,
|
|
1713
|
+
privacy: {
|
|
1714
|
+
sanitized: true,
|
|
1715
|
+
notes: [...fixture.privacy.notes]
|
|
1716
|
+
},
|
|
1717
|
+
workspace: {
|
|
1718
|
+
workspaceId: fixture.workspace.workspaceId,
|
|
1719
|
+
snapshotId: fixture.workspace.snapshotId,
|
|
1720
|
+
capturedAt: fixture.workspace.capturedAt,
|
|
1721
|
+
rootDir: fixture.workspace.rootDir,
|
|
1722
|
+
...(fixture.workspace.branch !== undefined ? { branch: fixture.workspace.branch } : {}),
|
|
1723
|
+
revision: fixture.workspace.revision,
|
|
1724
|
+
...(fixture.workspace.labels !== undefined ? { labels: [...fixture.workspace.labels] } : {})
|
|
1725
|
+
},
|
|
1726
|
+
seedBuiltAt: fixture.seedBuiltAt,
|
|
1727
|
+
seedActivatedAt: fixture.seedActivatedAt,
|
|
1728
|
+
seedExport: fixture.seedExport,
|
|
1729
|
+
turns: fixture.turns.map((turn) => ({
|
|
1730
|
+
turnId: turn.turnId,
|
|
1731
|
+
turn: structuredClone(turn.turn),
|
|
1732
|
+
expectedContextPhrases: [...turn.expectedContextPhrases],
|
|
1733
|
+
minimumPhraseHits: turn.minimumPhraseHits
|
|
1734
|
+
}))
|
|
1735
|
+
};
|
|
1736
|
+
}
|
|
1737
|
+
function buildReplayTurnScore(input) {
|
|
1738
|
+
const phraseHits = input.expectedContextPhrases.filter((phrase) => hasReplayPhrase(input.texts, phrase));
|
|
1739
|
+
const missedPhrases = input.expectedContextPhrases.filter((phrase) => !phraseHits.includes(phrase));
|
|
1740
|
+
const compileScore = input.compileOk ? 40 : 0;
|
|
1741
|
+
const phraseScore = input.expectedContextPhrases.length === 0 ? 60 : Math.round((phraseHits.length / input.expectedContextPhrases.length) * 60);
|
|
1742
|
+
return {
|
|
1743
|
+
phraseHits,
|
|
1744
|
+
missedPhrases,
|
|
1745
|
+
qualityScore: Math.min(100, compileScore + phraseScore)
|
|
1746
|
+
};
|
|
1747
|
+
}
|
|
1748
|
+
function buildRecordedSessionTurnReport(turnFixture, result, options) {
|
|
1749
|
+
const compileOk = result.ok;
|
|
1750
|
+
const selectedContextTexts = compileOk ? result.compileResponse.selectedContext.map((block) => block.text) : [];
|
|
1751
|
+
const selectedContextIds = compileOk ? result.compileResponse.selectedContext.map((block) => block.id) : [];
|
|
1752
|
+
const scoring = buildReplayTurnScore({
|
|
1753
|
+
compileOk,
|
|
1754
|
+
texts: selectedContextTexts,
|
|
1755
|
+
expectedContextPhrases: turnFixture.expectedContextPhrases
|
|
1756
|
+
});
|
|
1757
|
+
const eventExportDigest = result.eventExport.ok === true ? result.eventExport.normalizedEventExport.provenance.exportDigest : null;
|
|
1758
|
+
return {
|
|
1759
|
+
turnId: turnFixture.turnId,
|
|
1760
|
+
compileOk,
|
|
1761
|
+
fallbackToStaticContext: result.fallbackToStaticContext,
|
|
1762
|
+
hardRequirementViolated: result.hardRequirementViolated,
|
|
1763
|
+
activePackId: result.ok ? result.activePackId : null,
|
|
1764
|
+
usedLearnedRouteFn: result.ok ? result.compileResponse.diagnostics.usedLearnedRouteFn : false,
|
|
1765
|
+
routerIdentity: result.ok ? result.compileResponse.diagnostics.routerIdentity : null,
|
|
1766
|
+
selectionDigest: result.ok ? result.compileResponse.diagnostics.selectionDigest : null,
|
|
1767
|
+
selectedContextIds,
|
|
1768
|
+
selectedContextTexts,
|
|
1769
|
+
eventExportDigest,
|
|
1770
|
+
expectedContextPhrases: [...turnFixture.expectedContextPhrases],
|
|
1771
|
+
minimumPhraseHits: turnFixture.minimumPhraseHits,
|
|
1772
|
+
phraseHits: scoring.phraseHits,
|
|
1773
|
+
missedPhrases: scoring.missedPhrases,
|
|
1774
|
+
qualityScore: scoring.qualityScore,
|
|
1775
|
+
compileActiveVersion: options.compileActiveVersion,
|
|
1776
|
+
promoted: options.promoted,
|
|
1777
|
+
warnings: [...result.warnings]
|
|
1778
|
+
};
|
|
1779
|
+
}
|
|
1780
|
+
function buildRecordedSessionReplayModeSummary(mode, turns) {
|
|
1781
|
+
const compileOkCount = turns.filter((turn) => turn.compileOk).length;
|
|
1782
|
+
const phraseHitCount = turns.reduce((sum, turn) => sum + turn.phraseHits.length, 0);
|
|
1783
|
+
const phraseCount = turns.reduce((sum, turn) => sum + turn.expectedContextPhrases.length, 0);
|
|
1784
|
+
const usedLearnedRouteTurnCount = turns.filter((turn) => turn.usedLearnedRouteFn).length;
|
|
1785
|
+
const promotionCount = turns.filter((turn) => turn.promoted).length;
|
|
1786
|
+
const qualityScore = turns.length === 0 ? 0 : Math.round(turns.reduce((sum, turn) => sum + turn.qualityScore, 0) / turns.length);
|
|
1787
|
+
const packIds = uniqueStringsInOrder(turns.map((turn) => turn.activePackId).filter(isPresent));
|
|
1788
|
+
const base = {
|
|
1789
|
+
mode,
|
|
1790
|
+
qualityScore,
|
|
1791
|
+
compileOkCount,
|
|
1792
|
+
phraseHitCount,
|
|
1793
|
+
phraseCount,
|
|
1794
|
+
usedLearnedRouteTurnCount,
|
|
1795
|
+
promotionCount,
|
|
1796
|
+
packIds
|
|
1797
|
+
};
|
|
1798
|
+
return {
|
|
1799
|
+
...base,
|
|
1800
|
+
scoreHash: checksumJsonPayload({
|
|
1801
|
+
summary: base,
|
|
1802
|
+
turns: turns.map((turn) => ({
|
|
1803
|
+
turnId: turn.turnId,
|
|
1804
|
+
qualityScore: turn.qualityScore,
|
|
1805
|
+
phraseHits: turn.phraseHits,
|
|
1806
|
+
missedPhrases: turn.missedPhrases,
|
|
1807
|
+
compileOk: turn.compileOk,
|
|
1808
|
+
usedLearnedRouteFn: turn.usedLearnedRouteFn,
|
|
1809
|
+
activePackId: turn.activePackId,
|
|
1810
|
+
selectionDigest: turn.selectionDigest,
|
|
1811
|
+
promoted: turn.promoted,
|
|
1812
|
+
compileActiveVersion: turn.compileActiveVersion
|
|
1813
|
+
}))
|
|
1814
|
+
})
|
|
1815
|
+
};
|
|
1816
|
+
}
|
|
1817
|
+
function buildRecordedSessionReplayModeReport(mode, turns) {
|
|
1818
|
+
return {
|
|
1819
|
+
mode,
|
|
1820
|
+
summary: buildRecordedSessionReplayModeSummary(mode, turns),
|
|
1821
|
+
turns: [...turns]
|
|
1822
|
+
};
|
|
1823
|
+
}
|
|
1824
|
+
function buildRecordedSessionReplayScoreHash(modes) {
|
|
1825
|
+
return checksumJsonPayload(modes.map((mode) => ({
|
|
1826
|
+
mode: mode.mode,
|
|
1827
|
+
qualityScore: mode.summary.qualityScore,
|
|
1828
|
+
compileOkCount: mode.summary.compileOkCount,
|
|
1829
|
+
phraseHitCount: mode.summary.phraseHitCount,
|
|
1830
|
+
phraseCount: mode.summary.phraseCount,
|
|
1831
|
+
usedLearnedRouteTurnCount: mode.summary.usedLearnedRouteTurnCount,
|
|
1832
|
+
promotionCount: mode.summary.promotionCount,
|
|
1833
|
+
packIds: mode.summary.packIds,
|
|
1834
|
+
scoreHash: mode.summary.scoreHash
|
|
1835
|
+
})));
|
|
1836
|
+
}
|
|
1837
|
+
function recordedSessionReplayBundleBase(bundle) {
|
|
1838
|
+
return {
|
|
1839
|
+
contract: RECORDED_SESSION_BUNDLE_CONTRACT,
|
|
1840
|
+
traceId: bundle.traceId,
|
|
1841
|
+
source: bundle.source,
|
|
1842
|
+
recordedAt: bundle.recordedAt,
|
|
1843
|
+
generatedAt: bundle.generatedAt,
|
|
1844
|
+
traceHash: bundle.traceHash,
|
|
1845
|
+
fixtureHash: bundle.fixtureHash,
|
|
1846
|
+
scoreHash: bundle.scoreHash,
|
|
1847
|
+
privacy: {
|
|
1848
|
+
sanitized: true,
|
|
1849
|
+
notes: [...bundle.privacy.notes]
|
|
1850
|
+
},
|
|
1851
|
+
modes: bundle.modes.map((mode) => ({
|
|
1852
|
+
mode: mode.mode,
|
|
1853
|
+
summary: {
|
|
1854
|
+
...mode.summary,
|
|
1855
|
+
packIds: [...mode.summary.packIds]
|
|
1856
|
+
},
|
|
1857
|
+
turns: mode.turns.map((turn) => ({
|
|
1858
|
+
...turn,
|
|
1859
|
+
selectedContextIds: [...turn.selectedContextIds],
|
|
1860
|
+
selectedContextTexts: [...turn.selectedContextTexts],
|
|
1861
|
+
expectedContextPhrases: [...turn.expectedContextPhrases],
|
|
1862
|
+
phraseHits: [...turn.phraseHits],
|
|
1863
|
+
missedPhrases: [...turn.missedPhrases],
|
|
1864
|
+
warnings: [...turn.warnings]
|
|
1865
|
+
}))
|
|
1866
|
+
})),
|
|
1867
|
+
summary: {
|
|
1868
|
+
winnerMode: bundle.summary.winnerMode,
|
|
1869
|
+
ranking: bundle.summary.ranking.map((entry) => ({ ...entry }))
|
|
1870
|
+
}
|
|
1871
|
+
};
|
|
1872
|
+
}
|
|
1873
|
+
function buildRecordedSessionTurnExportRoot(modeRoot, turnId) {
|
|
1874
|
+
return {
|
|
1875
|
+
rootDir: path.join(modeRoot, "exports", turnId),
|
|
1876
|
+
exportName: turnId
|
|
1877
|
+
};
|
|
1878
|
+
}
|
|
1879
|
+
function prepareReplayModeRoot(rootDir, mode) {
|
|
1880
|
+
const modeRoot = path.resolve(path.join(rootDir, mode));
|
|
1881
|
+
rmSync(modeRoot, { recursive: true, force: true });
|
|
1882
|
+
mkdirSync(modeRoot, { recursive: true });
|
|
1883
|
+
return modeRoot;
|
|
1884
|
+
}
|
|
1885
|
+
function prepareSeedActivation(rootDir, fixture) {
|
|
1886
|
+
const activationRoot = path.join(rootDir, "activation");
|
|
1887
|
+
const seedPackRoot = path.join(rootDir, "seed-pack");
|
|
1888
|
+
const seedPack = materializeCandidatePackFromNormalizedEventExport(seedPackRoot, {
|
|
1889
|
+
packLabel: `${fixture.traceId}-seed`,
|
|
1890
|
+
workspace: fixture.workspace,
|
|
1891
|
+
normalizedEventExport: fixture.seedExport,
|
|
1892
|
+
learnedRouting: false,
|
|
1893
|
+
builtAt: fixture.seedBuiltAt,
|
|
1894
|
+
offlineArtifacts: ["recorded-session-replay-seed"],
|
|
1895
|
+
structuralOps: {
|
|
1896
|
+
connect: 1
|
|
1897
|
+
}
|
|
1898
|
+
});
|
|
1899
|
+
activatePack(activationRoot, seedPackRoot, fixture.seedActivatedAt);
|
|
1900
|
+
return {
|
|
1901
|
+
activationRoot,
|
|
1902
|
+
seedPackId: seedPack.manifest.packId
|
|
1903
|
+
};
|
|
1904
|
+
}
|
|
1905
|
+
function runRecordedSessionNoBrainMode(rootDir, fixture) {
|
|
1906
|
+
const modeRoot = prepareReplayModeRoot(rootDir, "no_brain");
|
|
1907
|
+
const activationRoot = path.join(modeRoot, "activation");
|
|
1908
|
+
const turns = fixture.turns.map((turnFixture) => {
|
|
1909
|
+
const result = runRuntimeTurn({
|
|
1910
|
+
...turnFixture.turn,
|
|
1911
|
+
export: buildRecordedSessionTurnExportRoot(modeRoot, turnFixture.turnId)
|
|
1912
|
+
}, {
|
|
1913
|
+
activationRoot,
|
|
1914
|
+
failOpen: true
|
|
1915
|
+
});
|
|
1916
|
+
return buildRecordedSessionTurnReport(turnFixture, result, {
|
|
1917
|
+
compileActiveVersion: null,
|
|
1918
|
+
promoted: false
|
|
1919
|
+
});
|
|
1920
|
+
});
|
|
1921
|
+
return buildRecordedSessionReplayModeReport("no_brain", turns);
|
|
1922
|
+
}
|
|
1923
|
+
function runRecordedSessionSeedPackMode(rootDir, fixture) {
|
|
1924
|
+
const modeRoot = prepareReplayModeRoot(rootDir, "seed_pack");
|
|
1925
|
+
const { activationRoot } = prepareSeedActivation(modeRoot, fixture);
|
|
1926
|
+
const turns = fixture.turns.map((turnFixture) => {
|
|
1927
|
+
const result = runRuntimeTurn({
|
|
1928
|
+
...turnFixture.turn,
|
|
1929
|
+
export: buildRecordedSessionTurnExportRoot(modeRoot, turnFixture.turnId)
|
|
1930
|
+
}, {
|
|
1931
|
+
activationRoot,
|
|
1932
|
+
failOpen: false
|
|
1933
|
+
});
|
|
1934
|
+
return buildRecordedSessionTurnReport(turnFixture, result, {
|
|
1935
|
+
compileActiveVersion: 1,
|
|
1936
|
+
promoted: false
|
|
1937
|
+
});
|
|
1938
|
+
});
|
|
1939
|
+
return buildRecordedSessionReplayModeReport("seed_pack", turns);
|
|
1940
|
+
}
|
|
1941
|
+
function runRecordedSessionLearnedReplayMode(rootDir, fixture) {
|
|
1942
|
+
const modeRoot = prepareReplayModeRoot(rootDir, "learned_replay");
|
|
1943
|
+
const { activationRoot } = prepareSeedActivation(modeRoot, fixture);
|
|
1944
|
+
const loopRoot = path.join(modeRoot, "loop");
|
|
1945
|
+
let state;
|
|
1946
|
+
const turns = [];
|
|
1947
|
+
for (const turnFixture of fixture.turns) {
|
|
1948
|
+
const compileCreatedAt = normalizeIsoTimestamp(turnFixture.turn.compile?.createdAt, "turn.compile.createdAt", turnFixture.turn.createdAt);
|
|
1949
|
+
const result = runContinuousProductLoopTurn({
|
|
1950
|
+
activationRoot,
|
|
1951
|
+
loopRoot,
|
|
1952
|
+
packLabel: `${fixture.traceId}-learned`,
|
|
1953
|
+
workspace: fixture.workspace,
|
|
1954
|
+
...(state !== undefined ? { state } : {}),
|
|
1955
|
+
learnedRouting: true,
|
|
1956
|
+
failOpen: false,
|
|
1957
|
+
turn: {
|
|
1958
|
+
...turnFixture.turn,
|
|
1959
|
+
export: buildRecordedSessionTurnExportRoot(modeRoot, turnFixture.turnId)
|
|
1960
|
+
},
|
|
1961
|
+
candidateBuiltAt: addMinutes(compileCreatedAt, 2),
|
|
1962
|
+
stageUpdatedAt: addMinutes(compileCreatedAt, 3),
|
|
1963
|
+
promoteUpdatedAt: addMinutes(compileCreatedAt, 4)
|
|
1964
|
+
});
|
|
1965
|
+
state = result.state;
|
|
1966
|
+
turns.push(buildRecordedSessionTurnReport(turnFixture, result.turn, {
|
|
1967
|
+
compileActiveVersion: result.compileActiveVersion,
|
|
1968
|
+
promoted: result.learning.promoted
|
|
1969
|
+
}));
|
|
1970
|
+
}
|
|
1971
|
+
return buildRecordedSessionReplayModeReport("learned_replay", turns);
|
|
1972
|
+
}
|
|
1973
|
+
export function runRecordedSessionReplay(rootDir, fixture) {
|
|
1974
|
+
const resolvedRoot = path.resolve(normalizeNonEmptyString(rootDir, "rootDir"));
|
|
1975
|
+
const seedExportErrors = validateNormalizedEventExport(fixture.seedExport);
|
|
1976
|
+
if (seedExportErrors.length > 0) {
|
|
1977
|
+
throw new Error(`recorded session replay seed export is invalid: ${seedExportErrors.join("; ")}`);
|
|
1978
|
+
}
|
|
1979
|
+
const expectedFixtureHash = checksumJsonPayload(recordedSessionReplayFixtureBase(fixture));
|
|
1980
|
+
if (fixture.fixtureHash !== expectedFixtureHash) {
|
|
1981
|
+
throw new Error(`recorded session replay fixtureHash mismatch: expected ${expectedFixtureHash}, received ${fixture.fixtureHash}`);
|
|
1982
|
+
}
|
|
1983
|
+
const modes = [
|
|
1984
|
+
runRecordedSessionNoBrainMode(resolvedRoot, fixture),
|
|
1985
|
+
runRecordedSessionSeedPackMode(resolvedRoot, fixture),
|
|
1986
|
+
runRecordedSessionLearnedReplayMode(resolvedRoot, fixture)
|
|
1987
|
+
];
|
|
1988
|
+
const ranking = modes
|
|
1989
|
+
.map((mode) => ({
|
|
1990
|
+
mode: mode.mode,
|
|
1991
|
+
qualityScore: mode.summary.qualityScore
|
|
1992
|
+
}))
|
|
1993
|
+
.sort((left, right) => right.qualityScore - left.qualityScore || left.mode.localeCompare(right.mode));
|
|
1994
|
+
const scoreHash = buildRecordedSessionReplayScoreHash(modes);
|
|
1995
|
+
const base = {
|
|
1996
|
+
contract: RECORDED_SESSION_BUNDLE_CONTRACT,
|
|
1997
|
+
traceId: fixture.traceId,
|
|
1998
|
+
source: fixture.source,
|
|
1999
|
+
recordedAt: fixture.recordedAt,
|
|
2000
|
+
generatedAt: fixture.bundleBuiltAt,
|
|
2001
|
+
traceHash: fixture.traceHash,
|
|
2002
|
+
fixtureHash: fixture.fixtureHash,
|
|
2003
|
+
scoreHash,
|
|
2004
|
+
privacy: {
|
|
2005
|
+
sanitized: true,
|
|
2006
|
+
notes: [...fixture.privacy.notes]
|
|
2007
|
+
},
|
|
2008
|
+
modes,
|
|
2009
|
+
summary: {
|
|
2010
|
+
winnerMode: ranking[0]?.mode ?? null,
|
|
2011
|
+
ranking
|
|
2012
|
+
}
|
|
2013
|
+
};
|
|
2014
|
+
return {
|
|
2015
|
+
...base,
|
|
2016
|
+
bundleHash: checksumJsonPayload(base)
|
|
2017
|
+
};
|
|
2018
|
+
}
|
|
2019
|
+
function rescoreRecordedSessionReplayTurn(turn) {
|
|
2020
|
+
const scoring = buildReplayTurnScore({
|
|
2021
|
+
compileOk: turn.compileOk,
|
|
2022
|
+
texts: turn.selectedContextTexts,
|
|
2023
|
+
expectedContextPhrases: turn.expectedContextPhrases
|
|
2024
|
+
});
|
|
2025
|
+
return {
|
|
2026
|
+
...turn,
|
|
2027
|
+
phraseHits: scoring.phraseHits,
|
|
2028
|
+
missedPhrases: scoring.missedPhrases,
|
|
2029
|
+
qualityScore: scoring.qualityScore,
|
|
2030
|
+
selectedContextIds: [...turn.selectedContextIds],
|
|
2031
|
+
selectedContextTexts: [...turn.selectedContextTexts],
|
|
2032
|
+
expectedContextPhrases: [...turn.expectedContextPhrases],
|
|
2033
|
+
warnings: [...turn.warnings]
|
|
2034
|
+
};
|
|
2035
|
+
}
|
|
2036
|
+
function rescoreRecordedSessionReplayMode(mode) {
|
|
2037
|
+
const turns = mode.turns.map((turn) => rescoreRecordedSessionReplayTurn(turn));
|
|
2038
|
+
return buildRecordedSessionReplayModeReport(mode.mode, turns);
|
|
2039
|
+
}
|
|
2040
|
+
export function rescoreRecordedSessionReplayBundle(bundle) {
|
|
2041
|
+
const modes = bundle.modes.map((mode) => rescoreRecordedSessionReplayMode(mode));
|
|
2042
|
+
return {
|
|
2043
|
+
scoreHash: buildRecordedSessionReplayScoreHash(modes),
|
|
2044
|
+
modes: modes.map((mode) => ({
|
|
2045
|
+
mode: mode.mode,
|
|
2046
|
+
qualityScore: mode.summary.qualityScore,
|
|
2047
|
+
scoreHash: mode.summary.scoreHash
|
|
2048
|
+
}))
|
|
2049
|
+
};
|
|
2050
|
+
}
|
|
2051
|
+
export function verifyRecordedSessionReplayBundleHashes(bundle) {
|
|
2052
|
+
const rescored = rescoreRecordedSessionReplayBundle(bundle);
|
|
2053
|
+
const rebuiltBundleHash = checksumJsonPayload(recordedSessionReplayBundleBase(bundle));
|
|
2054
|
+
return {
|
|
2055
|
+
bundleHashMatches: rebuiltBundleHash === bundle.bundleHash,
|
|
2056
|
+
scoreHashMatches: rescored.scoreHash === bundle.scoreHash
|
|
2057
|
+
};
|
|
2058
|
+
}
|
|
2059
|
+
function summarizeOperatorSlot(slot, updatedAt) {
|
|
2060
|
+
if (slot === null) {
|
|
2061
|
+
return null;
|
|
2062
|
+
}
|
|
2063
|
+
return {
|
|
2064
|
+
slot: slot.slot,
|
|
2065
|
+
packId: slot.packId,
|
|
2066
|
+
activationReady: slot.activationReady,
|
|
2067
|
+
routePolicy: slot.routePolicy,
|
|
2068
|
+
routerIdentity: slot.routerIdentity,
|
|
2069
|
+
workspaceSnapshot: slot.workspaceSnapshot,
|
|
2070
|
+
workspaceRevision: slot.workspaceRevision,
|
|
2071
|
+
eventRange: { ...slot.eventRange },
|
|
2072
|
+
eventExportDigest: slot.eventExportDigest,
|
|
2073
|
+
builtAt: slot.builtAt,
|
|
2074
|
+
updatedAt,
|
|
2075
|
+
findings: [...slot.findings]
|
|
2076
|
+
};
|
|
2077
|
+
}
|
|
2078
|
+
function summarizeLastPromotion(inspection) {
|
|
2079
|
+
if (inspection.active === null) {
|
|
2080
|
+
return {
|
|
2081
|
+
known: false,
|
|
2082
|
+
at: null,
|
|
2083
|
+
confidence: "no_active_pack",
|
|
2084
|
+
note: "active slot is empty, so no promotion can be proven"
|
|
2085
|
+
};
|
|
2086
|
+
}
|
|
2087
|
+
if (inspection.previous !== null) {
|
|
2088
|
+
return {
|
|
2089
|
+
known: inspection.pointers.active?.updatedAt !== null,
|
|
2090
|
+
at: inspection.pointers.active?.updatedAt ?? null,
|
|
2091
|
+
confidence: "proven_from_previous_pointer",
|
|
2092
|
+
note: "previous pointer is retained, so the current active pack is the last promoted pack"
|
|
2093
|
+
};
|
|
2094
|
+
}
|
|
2095
|
+
return {
|
|
2096
|
+
known: false,
|
|
2097
|
+
at: null,
|
|
2098
|
+
confidence: "unknown_from_local_pointers",
|
|
2099
|
+
note: "no previous pointer is retained, so local activation pointers cannot prove the last promotion time"
|
|
2100
|
+
};
|
|
2101
|
+
}
|
|
2102
|
+
function summarizeCandidateAheadBy(candidateAheadBy) {
|
|
2103
|
+
if (candidateAheadBy === null) {
|
|
2104
|
+
return [];
|
|
2105
|
+
}
|
|
2106
|
+
return Object.entries(candidateAheadBy)
|
|
2107
|
+
.filter(([, changed]) => changed === true)
|
|
2108
|
+
.map(([field]) => field)
|
|
2109
|
+
.sort();
|
|
2110
|
+
}
|
|
2111
|
+
function summarizeBrainState(active, observability) {
|
|
2112
|
+
if (active === null) {
|
|
2113
|
+
return {
|
|
2114
|
+
state: "no_active_pack",
|
|
2115
|
+
initMode: null,
|
|
2116
|
+
runtimePlasticitySource: null,
|
|
2117
|
+
seedStateVisible: false,
|
|
2118
|
+
seedBlockCount: 0,
|
|
2119
|
+
activePackId: null,
|
|
2120
|
+
activeWorkspaceSnapshot: null,
|
|
2121
|
+
activeEventExportDigest: null,
|
|
2122
|
+
detail: "no active pack is pinned, so the serve path can only fail open or hard fail"
|
|
2123
|
+
};
|
|
2124
|
+
}
|
|
2125
|
+
const state = observability.initHandoff.handoffState;
|
|
2126
|
+
const detail = state === "pg_promoted_pack_authoritative"
|
|
2127
|
+
? "serving is pinned to a PG-promoted pack rather than seed-state authority"
|
|
2128
|
+
: state === "seed_state_authoritative"
|
|
2129
|
+
? "serving is still pinned to the current seed-state authority"
|
|
2130
|
+
: "init/handoff metadata is missing for the active pack";
|
|
2131
|
+
return {
|
|
2132
|
+
state,
|
|
2133
|
+
initMode: observability.initHandoff.initMode,
|
|
2134
|
+
runtimePlasticitySource: observability.graphDynamics.runtimePlasticitySource,
|
|
2135
|
+
seedStateVisible: observability.initHandoff.seedStateVisible,
|
|
2136
|
+
seedBlockCount: observability.initHandoff.seedBlockCount,
|
|
2137
|
+
activePackId: active.packId,
|
|
2138
|
+
activeWorkspaceSnapshot: active.workspaceSnapshot,
|
|
2139
|
+
activeEventExportDigest: active.eventExportDigest,
|
|
2140
|
+
detail
|
|
2141
|
+
};
|
|
2142
|
+
}
|
|
2143
|
+
function summarizeServePath(compile) {
|
|
2144
|
+
if (compile === null) {
|
|
2145
|
+
return {
|
|
2146
|
+
state: "unprobed",
|
|
2147
|
+
fallbackToStaticContext: false,
|
|
2148
|
+
hardRequirementViolated: false,
|
|
2149
|
+
activePackId: null,
|
|
2150
|
+
usedLearnedRouteFn: null,
|
|
2151
|
+
routerIdentity: null,
|
|
2152
|
+
selectionMode: null,
|
|
2153
|
+
refreshStatus: null,
|
|
2154
|
+
freshnessChecksum: null,
|
|
2155
|
+
contextAttribution: buildContextAttributionSummary({
|
|
2156
|
+
fallbackToStaticContext: false,
|
|
2157
|
+
hardRequirementViolated: false,
|
|
2158
|
+
usedLearnedRouteFn: null,
|
|
2159
|
+
unprobed: true
|
|
2160
|
+
}),
|
|
2161
|
+
error: null
|
|
2162
|
+
};
|
|
2163
|
+
}
|
|
2164
|
+
if (!compile.ok) {
|
|
2165
|
+
return {
|
|
2166
|
+
state: compile.hardRequirementViolated ? "hard_fail" : "fail_open_static_context",
|
|
2167
|
+
fallbackToStaticContext: compile.fallbackToStaticContext,
|
|
2168
|
+
hardRequirementViolated: compile.hardRequirementViolated,
|
|
2169
|
+
activePackId: compile.activePackId,
|
|
2170
|
+
usedLearnedRouteFn: compile.usedLearnedRouteFn,
|
|
2171
|
+
routerIdentity: compile.routerIdentity,
|
|
2172
|
+
selectionMode: null,
|
|
2173
|
+
refreshStatus: null,
|
|
2174
|
+
freshnessChecksum: null,
|
|
2175
|
+
contextAttribution: compile.contextAttribution,
|
|
2176
|
+
error: compile.error
|
|
2177
|
+
};
|
|
2178
|
+
}
|
|
2179
|
+
return {
|
|
2180
|
+
state: "serving_active_pack",
|
|
2181
|
+
fallbackToStaticContext: compile.fallbackToStaticContext,
|
|
2182
|
+
hardRequirementViolated: compile.hardRequirementViolated,
|
|
2183
|
+
activePackId: compile.activePackId,
|
|
2184
|
+
usedLearnedRouteFn: compile.usedLearnedRouteFn,
|
|
2185
|
+
routerIdentity: compile.routerIdentity,
|
|
2186
|
+
selectionMode: readDiagnosticNoteValue(compile.notes, "selection_mode="),
|
|
2187
|
+
refreshStatus: readDiagnosticNoteValue(compile.notes, "router_refresh_status="),
|
|
2188
|
+
freshnessChecksum: readDiagnosticNoteValue(compile.notes, "router_freshness_checksum="),
|
|
2189
|
+
contextAttribution: compile.contextAttribution,
|
|
2190
|
+
error: compile.error
|
|
2191
|
+
};
|
|
2192
|
+
}
|
|
2193
|
+
function loadOperatorEventExport(input) {
|
|
2194
|
+
const eventExportPath = normalizeOptionalString(input.eventExportPath);
|
|
2195
|
+
if (eventExportPath === undefined) {
|
|
2196
|
+
return null;
|
|
2197
|
+
}
|
|
2198
|
+
const resolvedPath = path.resolve(eventExportPath);
|
|
2199
|
+
const stats = statSync(resolvedPath);
|
|
2200
|
+
if (stats.isDirectory()) {
|
|
2201
|
+
const bundle = loadRuntimeEventExportBundle(resolvedPath);
|
|
2202
|
+
return {
|
|
2203
|
+
sourcePath: resolvedPath,
|
|
2204
|
+
sourceKind: "bundle_root",
|
|
2205
|
+
normalizedEventExport: bundle.normalizedEventExport
|
|
2206
|
+
};
|
|
2207
|
+
}
|
|
2208
|
+
const normalizedEventExport = readJsonFile(resolvedPath);
|
|
2209
|
+
const validationErrors = validateNormalizedEventExport(normalizedEventExport);
|
|
2210
|
+
if (validationErrors.length > 0) {
|
|
2211
|
+
throw new Error(`normalized event export is invalid: ${validationErrors.join("; ")}`);
|
|
2212
|
+
}
|
|
2213
|
+
return {
|
|
2214
|
+
sourcePath: resolvedPath,
|
|
2215
|
+
sourceKind: "payload",
|
|
2216
|
+
normalizedEventExport
|
|
2217
|
+
};
|
|
2218
|
+
}
|
|
2219
|
+
function summarizeSupervision(input) {
|
|
2220
|
+
const loaded = loadOperatorEventExport(input);
|
|
2221
|
+
if (loaded === null) {
|
|
2222
|
+
return {
|
|
2223
|
+
available: false,
|
|
2224
|
+
sourcePath: null,
|
|
2225
|
+
sourceKind: "missing",
|
|
2226
|
+
exportDigest: null,
|
|
2227
|
+
flowing: null,
|
|
2228
|
+
sourceCount: 0,
|
|
2229
|
+
freshestSourceStream: null,
|
|
2230
|
+
freshestCreatedAt: null,
|
|
2231
|
+
freshestKind: null,
|
|
2232
|
+
humanLabelCount: null,
|
|
2233
|
+
sources: [],
|
|
2234
|
+
detail: "no event export path supplied"
|
|
2235
|
+
};
|
|
2236
|
+
}
|
|
2237
|
+
const observability = describeNormalizedEventExportObservability(loaded.normalizedEventExport);
|
|
2238
|
+
const freshestSource = observability.supervisionFreshnessBySource[0] ?? null;
|
|
2239
|
+
const flowing = observability.teacherFreshness.freshestCreatedAt !== null && observability.teacherFreshness.humanLabelCount > 0;
|
|
2240
|
+
return {
|
|
2241
|
+
available: true,
|
|
2242
|
+
sourcePath: loaded.sourcePath,
|
|
2243
|
+
sourceKind: loaded.sourceKind,
|
|
2244
|
+
exportDigest: observability.exportDigest,
|
|
2245
|
+
flowing,
|
|
2246
|
+
sourceCount: observability.supervisionFreshnessBySource.length,
|
|
2247
|
+
freshestSourceStream: observability.teacherFreshness.sourceStream ?? freshestSource?.sourceStream ?? null,
|
|
2248
|
+
freshestCreatedAt: observability.teacherFreshness.freshestCreatedAt ?? freshestSource?.freshestCreatedAt ?? null,
|
|
2249
|
+
freshestKind: observability.teacherFreshness.freshestKind ?? freshestSource?.freshestKind ?? null,
|
|
2250
|
+
humanLabelCount: observability.teacherFreshness.humanLabelCount,
|
|
2251
|
+
sources: [...observability.teacherFreshness.sources],
|
|
2252
|
+
detail: flowing
|
|
2253
|
+
? "human supervision is visible in the supplied export"
|
|
2254
|
+
: "the supplied export does not yet show human supervision"
|
|
2255
|
+
};
|
|
2256
|
+
}
|
|
2257
|
+
function loadTeacherSnapshot(input) {
|
|
2258
|
+
const teacherSnapshotPath = normalizeOptionalString(input.teacherSnapshotPath);
|
|
2259
|
+
if (teacherSnapshotPath === undefined) {
|
|
2260
|
+
return null;
|
|
2261
|
+
}
|
|
2262
|
+
const snapshot = readJsonFile(path.resolve(teacherSnapshotPath));
|
|
2263
|
+
if (snapshot.runtimeOwner !== "openclaw") {
|
|
2264
|
+
throw new Error("teacher snapshot runtimeOwner must be openclaw");
|
|
2265
|
+
}
|
|
2266
|
+
return snapshot;
|
|
2267
|
+
}
|
|
2268
|
+
function summarizeTeacherLoop(input) {
|
|
2269
|
+
const teacherSnapshotPath = normalizeOptionalString(input.teacherSnapshotPath);
|
|
2270
|
+
if (teacherSnapshotPath === undefined) {
|
|
2271
|
+
return {
|
|
2272
|
+
available: false,
|
|
2273
|
+
sourcePath: null,
|
|
2274
|
+
lastNoOpReason: "unavailable",
|
|
2275
|
+
latestFreshness: "unavailable",
|
|
2276
|
+
lastProcessedAt: null,
|
|
2277
|
+
queueDepth: null,
|
|
2278
|
+
queueCapacity: null,
|
|
2279
|
+
running: null,
|
|
2280
|
+
lastMaterializedPackId: null,
|
|
2281
|
+
notes: [],
|
|
2282
|
+
detail: "no teacher snapshot path supplied"
|
|
2283
|
+
};
|
|
2284
|
+
}
|
|
2285
|
+
const snapshot = loadTeacherSnapshot(input);
|
|
2286
|
+
if (snapshot === null) {
|
|
2287
|
+
return {
|
|
2288
|
+
available: false,
|
|
2289
|
+
sourcePath: path.resolve(teacherSnapshotPath),
|
|
2290
|
+
lastNoOpReason: "unavailable",
|
|
2291
|
+
latestFreshness: "unavailable",
|
|
2292
|
+
lastProcessedAt: null,
|
|
2293
|
+
queueDepth: null,
|
|
2294
|
+
queueCapacity: null,
|
|
2295
|
+
running: null,
|
|
2296
|
+
lastMaterializedPackId: null,
|
|
2297
|
+
notes: [],
|
|
2298
|
+
detail: "teacher snapshot could not be loaded"
|
|
2299
|
+
};
|
|
2300
|
+
}
|
|
2301
|
+
return {
|
|
2302
|
+
available: true,
|
|
2303
|
+
sourcePath: path.resolve(teacherSnapshotPath),
|
|
2304
|
+
lastNoOpReason: snapshot.diagnostics.lastNoOpReason,
|
|
2305
|
+
latestFreshness: snapshot.diagnostics.latestFreshness,
|
|
2306
|
+
lastProcessedAt: snapshot.diagnostics.lastProcessedAt,
|
|
2307
|
+
queueDepth: snapshot.queue.depth,
|
|
2308
|
+
queueCapacity: snapshot.queue.capacity,
|
|
2309
|
+
running: snapshot.queue.running,
|
|
2310
|
+
lastMaterializedPackId: snapshot.learner.lastMaterialization?.candidate.summary.packId ?? null,
|
|
2311
|
+
notes: [...snapshot.diagnostics.notes],
|
|
2312
|
+
detail: "async teacher diagnostics loaded"
|
|
2313
|
+
};
|
|
2314
|
+
}
|
|
2315
|
+
function summarizeAlwaysOnLearning(input) {
|
|
2316
|
+
const teacherSnapshotPath = normalizeOptionalString(input.teacherSnapshotPath);
|
|
2317
|
+
if (teacherSnapshotPath === undefined) {
|
|
2318
|
+
return {
|
|
2319
|
+
available: false,
|
|
2320
|
+
sourcePath: null,
|
|
2321
|
+
bootstrapped: null,
|
|
2322
|
+
mode: "unavailable",
|
|
2323
|
+
nextPriorityLane: "unavailable",
|
|
2324
|
+
pendingLive: null,
|
|
2325
|
+
pendingBackfill: null,
|
|
2326
|
+
pendingTotal: null,
|
|
2327
|
+
freshLivePriority: null,
|
|
2328
|
+
learnedRange: null,
|
|
2329
|
+
materializationCount: null,
|
|
2330
|
+
lastMaterializedAt: null,
|
|
2331
|
+
lastMaterializationReason: null,
|
|
2332
|
+
lastMaterializationLane: null,
|
|
2333
|
+
lastMaterializationPriority: null,
|
|
2334
|
+
lastMaterializedPackId: null,
|
|
2335
|
+
detail: "no teacher snapshot path supplied"
|
|
2336
|
+
};
|
|
2337
|
+
}
|
|
2338
|
+
const snapshot = loadTeacherSnapshot(input);
|
|
2339
|
+
if (snapshot === null) {
|
|
2340
|
+
return {
|
|
2341
|
+
available: false,
|
|
2342
|
+
sourcePath: path.resolve(teacherSnapshotPath),
|
|
2343
|
+
bootstrapped: null,
|
|
2344
|
+
mode: "unavailable",
|
|
2345
|
+
nextPriorityLane: "unavailable",
|
|
2346
|
+
pendingLive: null,
|
|
2347
|
+
pendingBackfill: null,
|
|
2348
|
+
pendingTotal: null,
|
|
2349
|
+
freshLivePriority: null,
|
|
2350
|
+
learnedRange: null,
|
|
2351
|
+
materializationCount: null,
|
|
2352
|
+
lastMaterializedAt: null,
|
|
2353
|
+
lastMaterializationReason: null,
|
|
2354
|
+
lastMaterializationLane: null,
|
|
2355
|
+
lastMaterializationPriority: null,
|
|
2356
|
+
lastMaterializedPackId: null,
|
|
2357
|
+
detail: "teacher snapshot could not be loaded"
|
|
2358
|
+
};
|
|
2359
|
+
}
|
|
2360
|
+
const plan = describeAlwaysOnLearningRuntimeState(snapshot.learner.state, snapshot.learner.lastMaterialization);
|
|
2361
|
+
return {
|
|
2362
|
+
available: true,
|
|
2363
|
+
sourcePath: path.resolve(teacherSnapshotPath),
|
|
2364
|
+
bootstrapped: plan.bootstrapped,
|
|
2365
|
+
mode: plan.mode,
|
|
2366
|
+
nextPriorityLane: plan.nextPriorityLane,
|
|
2367
|
+
pendingLive: plan.pending.live,
|
|
2368
|
+
pendingBackfill: plan.pending.backfill,
|
|
2369
|
+
pendingTotal: plan.pending.total,
|
|
2370
|
+
freshLivePriority: plan.pending.freshLivePriority,
|
|
2371
|
+
learnedRange: plan.learnedRange === null ? null : { ...plan.learnedRange },
|
|
2372
|
+
materializationCount: plan.materialization.count,
|
|
2373
|
+
lastMaterializedAt: plan.materialization.lastMaterializedAt,
|
|
2374
|
+
lastMaterializationReason: plan.materialization.lastReason,
|
|
2375
|
+
lastMaterializationLane: plan.materialization.lastLane,
|
|
2376
|
+
lastMaterializationPriority: plan.materialization.lastPriority,
|
|
2377
|
+
lastMaterializedPackId: snapshot.learner.lastMaterialization?.candidate.summary.packId ?? null,
|
|
2378
|
+
detail: plan.pending.freshLivePriority
|
|
2379
|
+
? "fresh live slices remain ahead of passive catch-up"
|
|
2380
|
+
: plan.pending.backfill > 0
|
|
2381
|
+
? "passive backfill remains queued behind the current live state"
|
|
2382
|
+
: plan.bootstrapped
|
|
2383
|
+
? "fast-init has handed off to the current learned export without queued backlog"
|
|
2384
|
+
: "learner is waiting for the first export"
|
|
2385
|
+
};
|
|
2386
|
+
}
|
|
2387
|
+
function buildOperatorFindings(report) {
|
|
2388
|
+
const findings = [];
|
|
2389
|
+
const push = (severity, code, summary, detail) => {
|
|
2390
|
+
findings.push({ severity, code, summary, detail });
|
|
2391
|
+
};
|
|
2392
|
+
if (report.active === null) {
|
|
2393
|
+
push("fail", "active_missing", "active slot is empty", "activate a healthy pack before serving or troubleshooting promotions");
|
|
2394
|
+
}
|
|
2395
|
+
else if (!report.active.activationReady) {
|
|
2396
|
+
push("fail", "active_unhealthy", `active slot is not activation-ready: ${report.active.packId}`, report.active.findings.join("; ") || "inspect the active pack payloads and activation pointers");
|
|
2397
|
+
}
|
|
2398
|
+
else {
|
|
2399
|
+
push("pass", "active_ready", `active slot is ready: ${report.active.packId}`, "serving can inspect the active pack without activation drift");
|
|
2400
|
+
}
|
|
2401
|
+
if (report.learnedRouting.required) {
|
|
2402
|
+
if (report.learnedRouting.available) {
|
|
2403
|
+
push("pass", "learned_route_ready", `learned routing is active: ${report.learnedRouting.routerIdentity ?? "unknown-router"}`, `updateCount=${report.learnedRouting.updateCount ?? 0}; labels=${report.learnedRouting.collectedLabelsTotal ?? 0}; handoff=${report.learnedRouting.handoffState}`);
|
|
2404
|
+
}
|
|
2405
|
+
else {
|
|
2406
|
+
push("fail", "learned_route_missing", "active pack requires learned routing but the learned route artifact is unavailable", "repair the router artifact or promote a healthy learned-routing candidate before serving");
|
|
2407
|
+
}
|
|
2408
|
+
}
|
|
2409
|
+
else {
|
|
2410
|
+
push("pass", "learned_route_optional", "active pack does not require learned routing", `handoff=${report.learnedRouting.handoffState}`);
|
|
2411
|
+
}
|
|
2412
|
+
if (report.servePath.state === "serving_active_pack") {
|
|
2413
|
+
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"}`);
|
|
2414
|
+
}
|
|
2415
|
+
else if (report.servePath.state === "fail_open_static_context") {
|
|
2416
|
+
push("warn", "serve_path_fail_open", "serve path would fail open to static context", report.servePath.error ?? "compile probe fell back to static context");
|
|
2417
|
+
}
|
|
2418
|
+
else if (report.servePath.state === "hard_fail") {
|
|
2419
|
+
push("fail", "serve_path_hard_fail", "serve path would hard fail on the learned-routing requirement", report.servePath.error ?? "compile probe violated a learned-routing hard requirement");
|
|
2420
|
+
}
|
|
2421
|
+
else {
|
|
2422
|
+
push("warn", "serve_path_unprobed", "serve path was not probed", "operator surface could not verify fail-open versus hard-fail behavior");
|
|
2423
|
+
}
|
|
2424
|
+
if (report.learnedRouting.required && report.servePath.state === "serving_active_pack" && report.servePath.usedLearnedRouteFn !== true) {
|
|
2425
|
+
push("fail", "serve_path_route_evidence_missing", "serve path compiled without learned-route evidence on a learned-routing pack", `router=${report.servePath.routerIdentity ?? report.learnedRouting.routerIdentity ?? "none"}; selection=${report.servePath.selectionMode ?? "unknown"}`);
|
|
2426
|
+
}
|
|
2427
|
+
if (report.servePath.state === "serving_active_pack") {
|
|
2428
|
+
if (report.servePath.contextAttribution.brainCompiledBlockCount > 0) {
|
|
2429
|
+
push("pass", "brain_context_visible", "serve probe selected brain-compiled context", `brainSources=${formatList(report.servePath.contextAttribution.brainCompiledSources)}; kernelSources=${formatList(report.servePath.contextAttribution.stableKernelSources)}; evidence=${report.servePath.contextAttribution.evidence}`);
|
|
2430
|
+
}
|
|
2431
|
+
else {
|
|
2432
|
+
push("warn", "brain_context_kernel_only", "serve probe stayed inside the stable kernel", `kernelSources=${formatList(report.servePath.contextAttribution.stableKernelSources)}; evidence=${report.servePath.contextAttribution.evidence}; ${report.servePath.contextAttribution.detail}`);
|
|
2433
|
+
}
|
|
2434
|
+
}
|
|
2435
|
+
if (report.candidate === null) {
|
|
2436
|
+
push("pass", "candidate_missing", "no candidate pack is currently staged", "steady state can legitimately run without a staged candidate until the next refresh lands");
|
|
2437
|
+
}
|
|
2438
|
+
else if (!report.candidate.activationReady) {
|
|
2439
|
+
push("warn", "candidate_unhealthy", `candidate slot is not activation-ready: ${report.candidate.packId}`, report.candidate.findings.join("; ") || "fix candidate pack payloads before promotion");
|
|
2440
|
+
}
|
|
2441
|
+
else if (report.promotion.allowed) {
|
|
2442
|
+
push("pass", "promotion_ready", `candidate is promotion-ready: ${report.candidate.packId}`, report.freshness.candidateAheadBy.length === 0
|
|
2443
|
+
? "candidate is staged and promotion is allowed"
|
|
2444
|
+
: `candidate is ahead on ${report.freshness.candidateAheadBy.join(", ")}`);
|
|
2445
|
+
}
|
|
2446
|
+
else {
|
|
2447
|
+
push("warn", "promotion_blocked", `candidate is staged but promotion is blocked: ${report.candidate.packId}`, report.promotion.findings.join("; ") || "inspect freshness and activation findings before promotion");
|
|
2448
|
+
}
|
|
2449
|
+
if (report.promotion.lastPromotion.known) {
|
|
2450
|
+
push("pass", "last_promotion_known", `last promotion is proven at ${report.promotion.lastPromotion.at}`, report.promotion.lastPromotion.note);
|
|
2451
|
+
}
|
|
2452
|
+
else {
|
|
2453
|
+
push("warn", "last_promotion_unknown", "last promotion is not provable from local activation pointers", report.promotion.lastPromotion.note);
|
|
2454
|
+
}
|
|
2455
|
+
if (report.rollback.allowed) {
|
|
2456
|
+
push("pass", "rollback_ready", `rollback is ready to restore ${report.rollback.previousPackId ?? "the previous pack"}`, report.previous === null ? "previous pack is pointer-visible even if slot inspection is unavailable" : `previous pack=${report.previous.packId}`);
|
|
2457
|
+
}
|
|
2458
|
+
else {
|
|
2459
|
+
push("warn", "rollback_blocked", "rollback is not ready", report.rollback.findings.join("; ") || "previous pointer is missing or no rollback target is retained");
|
|
2460
|
+
}
|
|
2461
|
+
if (!report.supervision.available) {
|
|
2462
|
+
push("warn", "supervision_unavailable", "supervision flow is not inspectable yet", "pass `--event-export <bundle-root-or-payload>` to inspect local supervision freshness");
|
|
2463
|
+
}
|
|
2464
|
+
else if (report.supervision.flowing) {
|
|
2465
|
+
push("pass", "supervision_visible", `supervision is flowing through ${report.supervision.freshestSourceStream ?? "unknown-source"}`, `freshest=${report.supervision.freshestCreatedAt ?? "unknown"}; humanLabels=${report.supervision.humanLabelCount ?? 0}`);
|
|
2466
|
+
}
|
|
2467
|
+
else {
|
|
2468
|
+
push("warn", "supervision_not_flowing", "the supplied export does not yet show human supervision", `sourcePath=${report.supervision.sourcePath ?? "unknown"}; exportDigest=${report.supervision.exportDigest ?? "unknown"}`);
|
|
2469
|
+
}
|
|
2470
|
+
if (!report.teacherLoop.available) {
|
|
2471
|
+
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");
|
|
2472
|
+
}
|
|
2473
|
+
else {
|
|
2474
|
+
push("pass", "teacher_snapshot_loaded", `last async no-op reason is ${report.teacherLoop.lastNoOpReason}`, `latestFreshness=${report.teacherLoop.latestFreshness}; queue=${report.teacherLoop.queueDepth}/${report.teacherLoop.queueCapacity}`);
|
|
2475
|
+
}
|
|
2476
|
+
return findings;
|
|
2477
|
+
}
|
|
2478
|
+
function summarizeOperatorStatus(findings) {
|
|
2479
|
+
if (findings.some((finding) => finding.severity === "fail")) {
|
|
2480
|
+
return "fail";
|
|
2481
|
+
}
|
|
2482
|
+
if (findings.some((finding) => finding.severity === "warn")) {
|
|
2483
|
+
return "warn";
|
|
2484
|
+
}
|
|
2485
|
+
return "ok";
|
|
2486
|
+
}
|
|
2487
|
+
function yesNo(value) {
|
|
2488
|
+
if (value === null) {
|
|
2489
|
+
return "unknown";
|
|
2490
|
+
}
|
|
2491
|
+
return value ? "yes" : "no";
|
|
2492
|
+
}
|
|
2493
|
+
function formatList(values, empty = "none") {
|
|
2494
|
+
return values.length === 0 ? empty : values.join(",");
|
|
2495
|
+
}
|
|
2496
|
+
function formatCompactList(values, empty = "none", maxItems = 2, maxLength = 20) {
|
|
2497
|
+
if (values.length === 0) {
|
|
2498
|
+
return empty;
|
|
2499
|
+
}
|
|
2500
|
+
const visible = values.slice(0, maxItems).map((value) => formatCompactValue(value, empty, maxLength));
|
|
2501
|
+
return values.length > maxItems ? `${visible.join("|")}+${values.length - maxItems}more` : visible.join("|");
|
|
2502
|
+
}
|
|
2503
|
+
function formatCompactValue(value, empty = "none", maxLength = 24) {
|
|
2504
|
+
if (value === null || value === undefined || value.length === 0) {
|
|
2505
|
+
return empty;
|
|
2506
|
+
}
|
|
2507
|
+
return value.length <= maxLength ? value : `${value.slice(0, maxLength)}…`;
|
|
2508
|
+
}
|
|
2509
|
+
export function buildOperatorSurfaceReport(input) {
|
|
2510
|
+
const activationRoot = path.resolve(normalizeNonEmptyString(input.activationRoot, "activationRoot"));
|
|
2511
|
+
const updatedAt = normalizeIsoTimestamp(input.updatedAt, "updatedAt", new Date().toISOString());
|
|
2512
|
+
const inspection = inspectActivationState(activationRoot, updatedAt);
|
|
2513
|
+
const observability = describeActivationObservability(activationRoot, "active", {
|
|
2514
|
+
updatedAt
|
|
2515
|
+
});
|
|
2516
|
+
const attachStatus = describeAttachStatus({ activationRoot });
|
|
2517
|
+
const active = summarizeOperatorSlot(inspection.active, inspection.pointers.active?.updatedAt ?? null);
|
|
2518
|
+
const reportBase = {
|
|
2519
|
+
generatedAt: updatedAt,
|
|
2520
|
+
activationRoot,
|
|
2521
|
+
active,
|
|
2522
|
+
candidate: summarizeOperatorSlot(inspection.candidate, inspection.pointers.candidate?.updatedAt ?? null),
|
|
2523
|
+
previous: summarizeOperatorSlot(inspection.previous, inspection.pointers.previous?.updatedAt ?? null),
|
|
2524
|
+
freshness: {
|
|
2525
|
+
activeBehindPromotionReadyCandidate: observability.promotionFreshness.activeBehindPromotionReadyCandidate,
|
|
2526
|
+
candidateAheadBy: summarizeCandidateAheadBy(observability.promotionFreshness.candidateAheadBy)
|
|
2527
|
+
},
|
|
2528
|
+
brain: summarizeBrainState(active, observability),
|
|
2529
|
+
learnedRouting: {
|
|
2530
|
+
required: observability.learnedRouteFn.required,
|
|
2531
|
+
available: observability.learnedRouteFn.available,
|
|
2532
|
+
routerIdentity: observability.learnedRouteFn.routerIdentity,
|
|
2533
|
+
routeFnVersion: observability.learnedRouteFn.routeFnVersion,
|
|
2534
|
+
trainingMethod: observability.learnedRouteFn.trainingMethod,
|
|
2535
|
+
routerTrainedAt: observability.learnedRouteFn.routerTrainedAt,
|
|
2536
|
+
objective: observability.learnedRouteFn.objective,
|
|
2537
|
+
pgProfile: observability.learnedRouteFn.pgProfile,
|
|
2538
|
+
objectiveChecksum: observability.learnedRouteFn.objectiveChecksum,
|
|
2539
|
+
updateMechanism: observability.learnedRouteFn.updateMechanism,
|
|
2540
|
+
updateVersion: observability.learnedRouteFn.updateVersion,
|
|
2541
|
+
updateCount: observability.learnedRouteFn.updateCount,
|
|
2542
|
+
supervisionCount: observability.learnedRouteFn.supervisionCount,
|
|
2543
|
+
collectedLabelsTotal: observability.learnedRouteFn.collectedLabels?.total ?? null,
|
|
2544
|
+
freshnessChecksum: observability.learnedRouteFn.freshnessChecksum,
|
|
2545
|
+
handoffState: observability.initHandoff.handoffState,
|
|
2546
|
+
initMode: observability.initHandoff.initMode,
|
|
2547
|
+
seedStateVisible: observability.initHandoff.seedStateVisible
|
|
2548
|
+
},
|
|
2549
|
+
servePath: summarizeServePath(attachStatus.compile),
|
|
2550
|
+
promotion: {
|
|
2551
|
+
allowed: inspection.promotion.allowed,
|
|
2552
|
+
findings: [...inspection.promotion.findings],
|
|
2553
|
+
lastPromotion: summarizeLastPromotion(inspection),
|
|
2554
|
+
activeUpdatedAt: inspection.pointers.active?.updatedAt ?? null,
|
|
2555
|
+
candidateUpdatedAt: inspection.pointers.candidate?.updatedAt ?? null,
|
|
2556
|
+
previousUpdatedAt: inspection.pointers.previous?.updatedAt ?? null
|
|
2557
|
+
},
|
|
2558
|
+
rollback: {
|
|
2559
|
+
allowed: inspection.rollback.allowed,
|
|
2560
|
+
findings: [...inspection.rollback.findings],
|
|
2561
|
+
previousPackId: inspection.previous?.packId ?? inspection.pointers.previous?.packId ?? null,
|
|
2562
|
+
state: inspection.rollback.allowed ? "ready" : inspection.active === null ? "unknown" : "blocked"
|
|
2563
|
+
},
|
|
2564
|
+
supervision: summarizeSupervision(input),
|
|
2565
|
+
learning: summarizeAlwaysOnLearning(input),
|
|
2566
|
+
teacherLoop: summarizeTeacherLoop(input)
|
|
2567
|
+
};
|
|
2568
|
+
const findings = buildOperatorFindings(reportBase);
|
|
2569
|
+
return {
|
|
2570
|
+
...reportBase,
|
|
2571
|
+
status: summarizeOperatorStatus(findings),
|
|
2572
|
+
findings
|
|
2573
|
+
};
|
|
2574
|
+
}
|
|
2575
|
+
function formatSlot(label, slot) {
|
|
2576
|
+
if (slot === null) {
|
|
2577
|
+
return `${label.padEnd(11)}pack=none ready=no`;
|
|
2578
|
+
}
|
|
2579
|
+
return `${label.padEnd(11)}pack=${slot.packId} ready=${yesNo(slot.activationReady)} route=${slot.routePolicy} snapshot=${slot.workspaceSnapshot} export=${formatCompactValue(slot.eventExportDigest)} range=${slot.eventRange.start}-${slot.eventRange.end}/${slot.eventRange.count} built=${slot.builtAt} updated=${slot.updatedAt ?? "unknown"}`;
|
|
2580
|
+
}
|
|
2581
|
+
export function formatOperatorRollbackReport(result) {
|
|
2582
|
+
const header = result.allowed ? (result.dryRun ? "ROLLBACK ready" : "ROLLBACK ok") : "ROLLBACK blocked";
|
|
2583
|
+
return [
|
|
2584
|
+
header,
|
|
2585
|
+
`preview ${yesNo(result.dryRun)} activation=${result.activationRoot} updatedAt=${result.updatedAt}`,
|
|
2586
|
+
`before active=${result.before.activePackId ?? "none"} candidate=${result.before.candidatePackId ?? "none"} previous=${result.before.previousPackId ?? "none"}`,
|
|
2587
|
+
`after active=${result.after?.activePackId ?? "none"} candidate=${result.after?.candidatePackId ?? "none"} previous=${result.after?.previousPackId ?? "none"}`,
|
|
2588
|
+
`result restored=${result.restoredPackId ?? "none"} parkedCandidate=${result.parkedCandidatePackId ?? "none"}`,
|
|
2589
|
+
`findings ${formatList(result.findings)}`
|
|
2590
|
+
].join("\n");
|
|
2591
|
+
}
|
|
2592
|
+
export function formatOperatorStatusReport(report) {
|
|
2593
|
+
return [
|
|
2594
|
+
`STATUS ${report.status}`,
|
|
2595
|
+
formatSlot("active", report.active),
|
|
2596
|
+
formatSlot("candidate", report.candidate),
|
|
2597
|
+
`freshness candidateAhead=${yesNo(report.freshness.activeBehindPromotionReadyCandidate)} delta=${formatList(report.freshness.candidateAheadBy)} lastPromotion=${report.promotion.lastPromotion.known ? report.promotion.lastPromotion.at : report.promotion.lastPromotion.confidence}`,
|
|
2598
|
+
`brain state=${report.brain.state} init=${report.brain.initMode ?? "unknown"} plasticity=${report.brain.runtimePlasticitySource ?? "unknown"} seedVisible=${yesNo(report.brain.seedStateVisible)} seedBlocks=${report.brain.seedBlockCount}`,
|
|
2599
|
+
`serve pack=${report.servePath.activePackId ?? report.active?.packId ?? "none"} state=${report.servePath.state} failOpen=${yesNo(report.servePath.fallbackToStaticContext)} hardFail=${yesNo(report.servePath.hardRequirementViolated)} usedRouteFn=${yesNo(report.servePath.usedLearnedRouteFn)} selection=${report.servePath.selectionMode ?? "unknown"} tiers=${report.servePath.contextAttribution.selectionTiers ?? "unknown"}`,
|
|
2600
|
+
`context kernel=${report.servePath.contextAttribution.stableKernelBlockCount} brain=${report.servePath.contextAttribution.brainCompiledBlockCount} evidence=${report.servePath.contextAttribution.evidence} kernelSrc=${formatCompactList(report.servePath.contextAttribution.stableKernelSources)} brainSrc=${formatCompactList(report.servePath.contextAttribution.brainCompiledSources)}`,
|
|
2601
|
+
`route router=${report.servePath.routerIdentity ?? report.learnedRouting.routerIdentity ?? "none"} refresh=${report.servePath.refreshStatus ?? "unknown"} freshness=${formatCompactValue(report.servePath.freshnessChecksum ?? report.learnedRouting.freshnessChecksum)} objective=${formatCompactValue(report.learnedRouting.objectiveChecksum)} labels=${report.learnedRouting.collectedLabelsTotal ?? 0} updates=${report.learnedRouting.updateCount ?? 0}`,
|
|
2602
|
+
`supervision flowing=${yesNo(report.supervision.flowing)} source=${report.supervision.freshestSourceStream ?? "none"} freshest=${report.supervision.freshestCreatedAt ?? "unknown"} humanLabels=${report.supervision.humanLabelCount ?? 0} export=${report.supervision.exportDigest ?? "none"}`,
|
|
2603
|
+
`learning mode=${report.learning.mode} next=${report.learning.nextPriorityLane} pendingLive=${report.learning.pendingLive ?? 0} pendingBackfill=${report.learning.pendingBackfill ?? 0} activeBuilt=${report.active?.builtAt ?? "unknown"} supervisionFreshest=${report.supervision.freshestCreatedAt ?? "unknown"} teacher=${report.teacherLoop.latestFreshness} lastNoOp=${report.teacherLoop.lastNoOpReason} lastProcessed=${report.teacherLoop.lastProcessedAt ?? "unknown"} materialized=${report.teacherLoop.lastMaterializedPackId ?? "none"}`,
|
|
2604
|
+
`rollback ready=${yesNo(report.rollback.allowed)} previous=${report.rollback.previousPackId ?? "none"} findings=${formatList(report.rollback.findings)}`
|
|
2605
|
+
].join("\n");
|
|
2606
|
+
}
|
|
2607
|
+
function orderedDoctorFindings(findings) {
|
|
2608
|
+
const severityOrder = {
|
|
2609
|
+
fail: 0,
|
|
2610
|
+
warn: 1,
|
|
2611
|
+
pass: 2
|
|
2612
|
+
};
|
|
2613
|
+
return [...findings].sort((left, right) => {
|
|
2614
|
+
if (severityOrder[left.severity] !== severityOrder[right.severity]) {
|
|
2615
|
+
return severityOrder[left.severity] - severityOrder[right.severity];
|
|
2616
|
+
}
|
|
2617
|
+
return left.code.localeCompare(right.code);
|
|
2618
|
+
});
|
|
2619
|
+
}
|
|
2620
|
+
function buildDoctorNextSteps(findings) {
|
|
2621
|
+
const steps = new Map();
|
|
2622
|
+
for (const finding of findings) {
|
|
2623
|
+
switch (finding.code) {
|
|
2624
|
+
case "active_missing":
|
|
2625
|
+
steps.set(finding.code, "activate a healthy pack before serving or promotion checks");
|
|
2626
|
+
break;
|
|
2627
|
+
case "active_unhealthy":
|
|
2628
|
+
case "learned_route_missing":
|
|
2629
|
+
steps.set(finding.code, "repair the active pack or promote a healthy learned-routing candidate");
|
|
2630
|
+
break;
|
|
2631
|
+
case "candidate_missing":
|
|
2632
|
+
case "promotion_blocked":
|
|
2633
|
+
case "candidate_unhealthy":
|
|
2634
|
+
steps.set(finding.code, "stage or repair a fresher candidate pack before attempting promotion");
|
|
2635
|
+
break;
|
|
2636
|
+
case "rollback_blocked":
|
|
2637
|
+
steps.set(finding.code, "capture a promotion before expecting rollback readiness; previous pointer is the key guardrail");
|
|
2638
|
+
break;
|
|
2639
|
+
case "last_promotion_unknown":
|
|
2640
|
+
steps.set(finding.code, "treat `active.updatedAt` as the last pointer move only; do not claim exact last-promotion time without previous-pointer lineage");
|
|
2641
|
+
break;
|
|
2642
|
+
case "supervision_unavailable":
|
|
2643
|
+
case "supervision_not_flowing":
|
|
2644
|
+
steps.set(finding.code, "pass `--event-export <bundle-root-or-payload>` to inspect local supervision freshness and teacher signals");
|
|
2645
|
+
break;
|
|
2646
|
+
case "serve_path_fail_open":
|
|
2647
|
+
steps.set(finding.code, "decide whether fail-open static context is acceptable for this runtime and repair activation if it is not");
|
|
2648
|
+
break;
|
|
2649
|
+
case "serve_path_hard_fail":
|
|
2650
|
+
case "serve_path_route_evidence_missing":
|
|
2651
|
+
steps.set(finding.code, "repair the active learned-routing pack or promote a healthy candidate before serving again");
|
|
2652
|
+
break;
|
|
2653
|
+
case "brain_context_kernel_only":
|
|
2654
|
+
steps.set(finding.code, "compare stable-kernel versus brain-compiled sources; if you expected live context, refresh exports or promote a fresher candidate pack");
|
|
2655
|
+
break;
|
|
2656
|
+
case "teacher_snapshot_unavailable":
|
|
2657
|
+
steps.set(finding.code, "serialize `teacherLoop.snapshot()` or `await teacherLoop.flush()` and pass `--teacher-snapshot <snapshot.json>` when you need the last no-op reason");
|
|
2658
|
+
break;
|
|
2659
|
+
default:
|
|
2660
|
+
break;
|
|
2661
|
+
}
|
|
2662
|
+
}
|
|
2663
|
+
return [...steps.values()];
|
|
2664
|
+
}
|
|
2665
|
+
export function formatOperatorDoctorReport(report) {
|
|
2666
|
+
const lines = [`DOCTOR ${report.status}`];
|
|
2667
|
+
for (const finding of orderedDoctorFindings(report.findings)) {
|
|
2668
|
+
lines.push(`${finding.severity.toUpperCase()} ${finding.summary}`);
|
|
2669
|
+
lines.push(` ${finding.detail}`);
|
|
2670
|
+
}
|
|
2671
|
+
const nextSteps = buildDoctorNextSteps(report.findings.filter((finding) => finding.severity !== "pass"));
|
|
2672
|
+
if (nextSteps.length > 0) {
|
|
2673
|
+
lines.push("NEXT");
|
|
2674
|
+
for (const step of nextSteps) {
|
|
2675
|
+
lines.push(`- ${step}`);
|
|
2676
|
+
}
|
|
2677
|
+
}
|
|
2678
|
+
return lines.join("\n");
|
|
2679
|
+
}
|
|
2680
|
+
/**
|
|
2681
|
+
* Describes the kernel/brain boundary for a single compile response.
|
|
2682
|
+
*
|
|
2683
|
+
* Combines:
|
|
2684
|
+
* - Brain context summary (from the compile response diagnostics)
|
|
2685
|
+
* - Kernel surface validation (if a surface descriptor is supplied)
|
|
2686
|
+
* - A coverage advisory based on routing signals
|
|
2687
|
+
*
|
|
2688
|
+
* See `docs/kernel-brain-boundary.md` for the full decision framework.
|
|
2689
|
+
*/
|
|
2690
|
+
export function describeKernelBrainBoundary(compileResponse, surface) {
|
|
2691
|
+
const diag = compileResponse.diagnostics;
|
|
2692
|
+
// Collect the roles of selected blocks.
|
|
2693
|
+
const selectedRoles = [
|
|
2694
|
+
...new Set(compileResponse.selectedContext
|
|
2695
|
+
.map((b) => b.source)
|
|
2696
|
+
.filter((s) => typeof s === "string" && s.length > 0))
|
|
2697
|
+
];
|
|
2698
|
+
// Detect whether any block was compacted (compactedFrom set on the block).
|
|
2699
|
+
const compactionApplied = compileResponse.selectedContext.some((b) => Array.isArray(b.compactedFrom) && (b.compactedFrom?.length ?? 0) > 0);
|
|
2700
|
+
// Coverage advisory.
|
|
2701
|
+
// Token match evidence lives in diagnostics.notes as "selection_mode=token_match(...)"
|
|
2702
|
+
// or "selection_tiers=token_match_only" / "selection_tiers=token_match+priority_fallback".
|
|
2703
|
+
const notesStr = diag.notes.join(" ");
|
|
2704
|
+
const hasTokenMatches = notesStr.includes("selection_mode=token_match") ||
|
|
2705
|
+
notesStr.includes("selection_tiers=token_match");
|
|
2706
|
+
let brainCoverageAdvisory;
|
|
2707
|
+
if (diag.usedLearnedRouteFn && hasTokenMatches) {
|
|
2708
|
+
brainCoverageAdvisory = "likely_covered";
|
|
2709
|
+
}
|
|
2710
|
+
else if (hasTokenMatches || diag.usedLearnedRouteFn) {
|
|
2711
|
+
brainCoverageAdvisory = "partial";
|
|
2712
|
+
}
|
|
2713
|
+
else {
|
|
2714
|
+
brainCoverageAdvisory = "likely_gap";
|
|
2715
|
+
}
|
|
2716
|
+
const kernelValidation = surface !== undefined ? validateKernelSurface(surface) : null;
|
|
2717
|
+
return {
|
|
2718
|
+
brain: {
|
|
2719
|
+
packId: compileResponse.packId,
|
|
2720
|
+
mode: diag.modeEffective,
|
|
2721
|
+
selectedBlockCount: compileResponse.selectedContext.length,
|
|
2722
|
+
selectedRoles,
|
|
2723
|
+
usedLearnedRouteFn: diag.usedLearnedRouteFn,
|
|
2724
|
+
compactionApplied
|
|
2725
|
+
},
|
|
2726
|
+
kernelValidation,
|
|
2727
|
+
brainCoverageAdvisory
|
|
2728
|
+
};
|
|
2729
|
+
}
|
|
495
2730
|
//# sourceMappingURL=index.js.map
|