@openclawbrain/cli 0.4.24 → 0.4.26

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/src/cli.js CHANGED
@@ -17,7 +17,7 @@ import { inspectOpenClawBrainHookStatus, inspectOpenClawBrainPluginAllowlist } f
17
17
  import { describeOpenClawBrainInstallIdentity, describeOpenClawBrainInstallLayout, findInstalledOpenClawBrainPlugin, getOpenClawBrainKnownPluginIds, normalizeOpenClawBrainPluginsConfig, pinInstalledOpenClawBrainPluginActivationRoot, resolveOpenClawBrainInstallTarget } from "./openclaw-plugin-install.js";
18
18
  import { buildOpenClawBrainConvergeRestartPlan, classifyOpenClawBrainConvergeVerification, describeOpenClawBrainConvergeChangeReasons, diffOpenClawBrainConvergeRuntimeFingerprint, finalizeOpenClawBrainConvergeResult, planOpenClawBrainConvergePluginAction, shouldReplaceOpenClawBrainInstallBeforeConverge } from "./install-converge.js";
19
19
  import { loadAttachmentPolicyDeclaration, resolveEffectiveAttachmentPolicyTruth, writeAttachmentPolicyDeclaration } from "./attachment-policy-truth.js";
20
- import { DEFAULT_WATCH_POLL_INTERVAL_SECONDS, buildNormalizedEventExportFromScannedEvents, bootstrapRuntimeAttach, buildOperatorSurfaceReport, clearOpenClawProfileRuntimeLoadProof, compileRuntimeContext, createAsyncTeacherLiveLoop, createOpenClawLocalSessionTail, createRuntimeEventExportScanner, describeCurrentProfileBrainStatus, formatOperatorRollbackReport, listOpenClawProfileRuntimeLoadProofs, loadRuntimeEventExportBundle, loadWatchTeacherSnapshotState, persistWatchTeacherSnapshot, rollbackRuntimeAttach, resolveAttachmentRuntimeLoadProofsPath, resolveOperatorTeacherSnapshotPath, resolveAsyncTeacherLiveLoopSnapshotPath, resolveWatchSessionTailCursorPath, resolveWatchStateRoot, resolveWatchTeacherSnapshotPath, scanLiveEventExport, scanRecordedSession, summarizeLearningPathFromMaterialization, summarizeNormalizedEventExportLabelFlow, summarizeTeacherNoArtifactCycle, writeScannedEventExportBundle } from "./index.js";
20
+ import { DEFAULT_WATCH_POLL_INTERVAL_SECONDS, buildNormalizedEventExportFromScannedEvents, bootstrapRuntimeAttach, clearOpenClawProfileRuntimeLoadProof, compileRuntimeContext, createAsyncTeacherLiveLoop, createOpenClawLocalSessionTail, createRuntimeEventExportScanner, describeCurrentProfileBrainStatusWithReport, formatOperatorRollbackReport, listOpenClawProfileRuntimeLoadProofs, loadRuntimeEventExportBundle, loadWatchTeacherSnapshotState, persistWatchTeacherSnapshot, rollbackRuntimeAttach, resolveAttachmentRuntimeLoadProofsPath, resolveOperatorTeacherSnapshotPath, resolveAsyncTeacherLiveLoopSnapshotPath, resolveWatchSessionTailCursorPath, resolveWatchStateRoot, resolveWatchTeacherSnapshotPath, scanLiveEventExport, scanRecordedSession, summarizeLearningPathFromMaterialization, summarizeNormalizedEventExportLabelFlow, summarizeTeacherNoArtifactCycle, writeScannedEventExportBundle } from "./index.js";
21
21
  import { appendLearningUpdateLogs } from "./learning-spine.js";
22
22
  import { buildPassiveLearningSessionExportFromOpenClawSessionStore } from "./local-session-passive-learning.js";
23
23
  import { reindexMaterializationCandidateWithEmbedder } from "./materialization-embedder.js";
@@ -25,7 +25,7 @@ import { summarizePackVectorEmbeddingState } from "./embedding-status.js";
25
25
  import { buildTracedLearningBridgePayloadFromRuntime, buildTracedLearningStatusSurface, persistTracedLearningBridgeState } from "./traced-learning-bridge.js";
26
26
  import { discoverOpenClawSessionStores, loadOpenClawSessionIndex, readOpenClawSessionFile } from "./session-store.js";
27
27
  import { readOpenClawBrainProviderDefaults, readOpenClawBrainProviderConfig, readOpenClawBrainProviderConfigFromSources, resolveOpenClawBrainProviderDefaultsPath } from "./provider-config.js";
28
- import { formatOperatorLearningAttributionSummary, formatOperatorLearningPathSummary } from "./status-learning-path.js";
28
+ import { formatOperatorAttributionCoverageSummary, formatOperatorFeedbackSummary, formatOperatorLearningAttributionSummary, formatOperatorLearningPathSummary } from "./status-learning-path.js";
29
29
  import { buildProofCommandForOpenClawHome, buildProofCommandHelpSection, captureOperatorProofBundle, formatOperatorProofResult, parseProofCliArgs } from "./proof-command.js";
30
30
  const OPENCLAWBRAIN_EMBEDDER_BASE_URL_ENV = "OPENCLAWBRAIN_EMBEDDER_BASE_URL";
31
31
  const OPENCLAWBRAIN_EMBEDDER_PROVIDER_ENV = "OPENCLAWBRAIN_EMBEDDER_PROVIDER";
@@ -1500,7 +1500,9 @@ function formatCurrentProfileStatusSummary(status, report, targetInspection, opt
1500
1500
  learningPath: report.learningPath,
1501
1501
  tracedLearning
1502
1502
  })}`,
1503
+ `feedback ${formatOperatorFeedbackSummary({ tracedLearning })}`,
1503
1504
  `attribution ${formatOperatorLearningAttributionSummary({ status })}`,
1505
+ `attrCover ${formatOperatorAttributionCoverageSummary({ tracedLearning })}`,
1504
1506
  `learning state=${report.learning.backlogState} bootstrapped=${yesNo(report.learning.bootstrapped)} mode=${report.learning.mode} next=${report.learning.nextPriorityLane} priority=${report.learning.nextPriorityBucket} pending=${report.learning.pendingLive ?? "none"}/${report.learning.pendingBackfill ?? "none"} buckets=${formatLearningBuckets(report)} warn=${formatLearningWarnings(report)} lastPack=${report.learning.lastMaterializedPackId ?? "none"} detail=${report.learning.detail}`,
1505
1507
  `traced ${formatTracedLearningSurface(tracedLearning)}`,
1506
1508
  `teacherProof ${formatTeacherLoopSummary(report)}`,
@@ -1690,8 +1692,7 @@ function inspectInstallConvergeVerification(parsed) {
1690
1692
  openclawHome: parsed.openclawHome,
1691
1693
  ...(targetInspection.profileId === null ? {} : { profileId: targetInspection.profileId })
1692
1694
  };
1693
- const status = describeCurrentProfileBrainStatus(operatorInput);
1694
- const report = buildOperatorSurfaceReport(operatorInput);
1695
+ const { status, report } = describeCurrentProfileBrainStatusWithReport(operatorInput);
1695
1696
  const normalizedStatusAndReport = applyAttachmentPolicyTruth(status, report);
1696
1697
  const installHook = summarizeStatusInstallHook(parsed.openclawHome);
1697
1698
  const attachmentTruth = summarizeStatusAttachmentTruth({
@@ -6362,9 +6363,10 @@ export function runOperatorCli(argv = process.argv.slice(2)) {
6362
6363
  : { profileId: targetInspection.profileId }),
6363
6364
  teacherSnapshotPath: resolveOperatorTeacherSnapshotPath(activationRoot, statusOrRollback.input.teacherSnapshotPath)
6364
6365
  };
6365
- const status = describeCurrentProfileBrainStatus(operatorInput);
6366
+ const statusWithReport = describeCurrentProfileBrainStatusWithReport(operatorInput);
6367
+ const status = statusWithReport.status;
6366
6368
  const tracedLearning = buildTracedLearningStatusSurface(activationRoot);
6367
- const normalizedStatusAndReport = applyAttachmentPolicyTruth(status, statusOrRollback.json ? null : buildOperatorSurfaceReport(operatorInput));
6369
+ const normalizedStatusAndReport = applyAttachmentPolicyTruth(status, statusOrRollback.json ? null : statusWithReport.report);
6368
6370
  if (statusOrRollback.json) {
6369
6371
  console.log(JSON.stringify({
6370
6372
  ...normalizedStatusAndReport.status,
package/dist/src/index.js CHANGED
@@ -8031,9 +8031,15 @@ export function buildOperatorSurfaceReport(input) {
8031
8031
  findings
8032
8032
  };
8033
8033
  }
8034
- export function describeCurrentProfileBrainStatus(input) {
8034
+ export function describeCurrentProfileBrainStatusWithReport(input) {
8035
8035
  const report = buildOperatorSurfaceReport(input);
8036
- return buildCurrentProfileBrainStatusFromReport(report, report.manyProfile.declaredAttachmentPolicy, normalizeOptionalString(input.profileId) ?? null);
8036
+ return {
8037
+ report,
8038
+ status: buildCurrentProfileBrainStatusFromReport(report, report.manyProfile.declaredAttachmentPolicy, normalizeOptionalString(input.profileId) ?? null)
8039
+ };
8040
+ }
8041
+ export function describeCurrentProfileBrainStatus(input) {
8042
+ return describeCurrentProfileBrainStatusWithReport(input).status;
8037
8043
  }
8038
8044
  export function formatOperatorRollbackReport(result) {
8039
8045
  const header = result.allowed ? (result.dryRun ? "ROLLBACK ready" : "ROLLBACK ok") : "ROLLBACK blocked";
@@ -76,6 +76,41 @@ function writeJson(filePath, value) {
76
76
  writeText(filePath, `${JSON.stringify(value, null, 2)}\n`);
77
77
  }
78
78
 
79
+ function readJsonObject(value) {
80
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
81
+ return null;
82
+ }
83
+ return value;
84
+ }
85
+
86
+ function parseJsonObjectText(text) {
87
+ if (typeof text !== "string" || text.trim().length === 0) {
88
+ return null;
89
+ }
90
+ try {
91
+ return readJsonObject(JSON.parse(text));
92
+ }
93
+ catch {
94
+ return null;
95
+ }
96
+ }
97
+
98
+ function normalizeOptionalBoolean(value) {
99
+ return value === true ? true : value === false ? false : null;
100
+ }
101
+
102
+ function extractInstallRestartState(text) {
103
+ const parsed = parseJsonObjectText(text);
104
+ const restart = readJsonObject(parsed?.restart ?? null);
105
+ if (restart === null) {
106
+ return null;
107
+ }
108
+ return {
109
+ required: normalizeOptionalBoolean(restart.required),
110
+ performed: normalizeOptionalBoolean(restart.performed),
111
+ };
112
+ }
113
+
79
114
  function buildCurrentCliInvocation(cliEntryPath = process.argv[1]) {
80
115
  const normalizedEntryPath = normalizeOptionalCliString(cliEntryPath);
81
116
  if (normalizedEntryPath === null) {
@@ -511,7 +546,7 @@ function buildVerdict({ steps, gatewayStatus, pluginInspect, statusSignals, brea
511
546
  };
512
547
  }
513
548
 
514
- function buildSummary({ options, steps, verdict, gatewayStatusText, pluginInspectText, statusSignals, breadcrumbs, runtimeLoadProofSnapshot, guardLine, attributionLine, learningPathLine, coverageSnapshot, hardeningSnapshot }) {
549
+ function buildSummary({ options, steps, verdict, gatewayStatusText, pluginInspectText, statusSignals, breadcrumbs, runtimeLoadProofSnapshot, guardLine, feedbackLine, attributionLine, attributionCoverageLine, learningPathLine, coverageSnapshot, hardeningSnapshot }) {
515
550
  const passed = [];
516
551
  const missing = [];
517
552
  const warnings = Array.isArray(verdict.warnings) ? verdict.warnings : [];
@@ -573,9 +608,15 @@ function buildSummary({ options, steps, verdict, gatewayStatusText, pluginInspec
573
608
  : [`- ${guardLine}`]),
574
609
  "",
575
610
  "## Learning Attribution",
611
+ ...(feedbackLine === null
612
+ ? ["- feedback line not reported by detailed status"]
613
+ : [`- ${feedbackLine}`]),
576
614
  ...(attributionLine === null
577
615
  ? ["- attribution line not reported by detailed status"]
578
616
  : [`- ${attributionLine}`]),
617
+ ...(attributionCoverageLine === null
618
+ ? ["- attribution coverage line not reported by detailed status"]
619
+ : [`- ${attributionCoverageLine}`]),
579
620
  ...(learningPathLine === null
580
621
  ? []
581
622
  : [`- ${learningPathLine}`]),
@@ -802,7 +843,7 @@ export function captureOperatorProofBundle(options) {
802
843
  mkdirSync(bundleDir, { recursive: true });
803
844
  const steps = [];
804
845
  const gatewayProfile = readOpenClawProfileName(options.openclawHome);
805
- function addStep(stepId, label, command, args, { skipped = false } = {}) {
846
+ function addStep(stepId, label, command, args, { skipped = false, skipSummary = "step intentionally skipped" } = {}) {
806
847
  if (skipped) {
807
848
  steps.push({
808
849
  stepId,
@@ -811,7 +852,7 @@ export function captureOperatorProofBundle(options) {
811
852
  skipped: true,
812
853
  captureState: "complete",
813
854
  resultClass: "success",
814
- summary: "step intentionally skipped",
855
+ summary: skipSummary,
815
856
  stdoutPath: null,
816
857
  stderrPath: null,
817
858
  });
@@ -844,8 +885,27 @@ export function captureOperatorProofBundle(options) {
844
885
  });
845
886
  return capture;
846
887
  }
847
- addStep("01-install", "install", cliInvocation.command, [...cliInvocation.args, "install", "--openclaw-home", options.openclawHome], { skipped: options.skipInstall === true });
848
- addStep("02-restart", "gateway restart", "openclaw", buildGatewayArgs("restart", gatewayProfile), { skipped: options.skipRestart === true });
888
+ const installCapture = addStep("01-install", "install", cliInvocation.command, [...cliInvocation.args, "install", "--openclaw-home", options.openclawHome, "--json"], { skipped: options.skipInstall === true });
889
+ const installRestartState = options.skipInstall === true || installCapture.exitCode !== 0 || installCapture.error
890
+ ? null
891
+ : extractInstallRestartState(installCapture.stdout);
892
+ let restartSkipSummary = null;
893
+ if (options.skipRestart === true) {
894
+ restartSkipSummary = "step intentionally skipped";
895
+ }
896
+ else if (installRestartState?.performed === true) {
897
+ restartSkipSummary = "step intentionally skipped because install already performed the gateway restart";
898
+ }
899
+ else if (installRestartState?.required === false) {
900
+ restartSkipSummary = "step intentionally skipped because install reported no gateway restart was required";
901
+ }
902
+ else if (gatewayProfile === null) {
903
+ restartSkipSummary = "skipped because exact OpenClaw profile token could not be inferred; avoiding shared-gateway self-interrupt during proof capture";
904
+ }
905
+ addStep("02-restart", "gateway restart", "openclaw", buildGatewayArgs("restart", gatewayProfile), {
906
+ skipped: restartSkipSummary !== null,
907
+ skipSummary: restartSkipSummary ?? undefined
908
+ });
849
909
  const gatewayStatusCapture = addStep("03-gateway-status", "gateway status", "openclaw", buildGatewayStatusArgs(
850
910
  gatewayProfile,
851
911
  normalizeOptionalCliString(options.gatewayUrl ?? null),
@@ -861,7 +921,9 @@ export function captureOperatorProofBundle(options) {
861
921
  const serveLine = extractDetailedStatusLine(statusCapture.stdout, "serve");
862
922
  const routeFnLine = extractDetailedStatusLine(statusCapture.stdout, "routeFn");
863
923
  const guardLine = extractDetailedStatusLine(statusCapture.stdout, "guard");
924
+ const feedbackLine = extractDetailedStatusLine(statusCapture.stdout, "feedback");
864
925
  const attributionLine = extractDetailedStatusLine(statusCapture.stdout, "attribution");
926
+ const attributionCoverageLine = extractDetailedStatusLine(statusCapture.stdout, "attrCover");
865
927
  const learningPathLine = extractDetailedStatusLine(statusCapture.stdout, "path");
866
928
  const runtimeLoadProofPath = normalizeReportedProofPath(statusSignals.proofPath)
867
929
  ?? path.join(activationRoot, "attachment-truth", "runtime-load-proofs.json");
@@ -917,7 +979,9 @@ export function captureOperatorProofBundle(options) {
917
979
  runtimeLoadProofPath,
918
980
  runtimeLoadProofError: runtimeLoadProofSnapshot.error,
919
981
  guardLine,
982
+ feedbackLine,
920
983
  attributionLine,
984
+ attributionCoverageLine,
921
985
  learningPathLine,
922
986
  });
923
987
  writeJson(path.join(bundleDir, "hardening-snapshot.json"), hardeningSnapshot);
@@ -931,7 +995,9 @@ export function captureOperatorProofBundle(options) {
931
995
  breadcrumbs,
932
996
  runtimeLoadProofSnapshot,
933
997
  guardLine,
998
+ feedbackLine,
934
999
  attributionLine,
1000
+ attributionCoverageLine,
935
1001
  learningPathLine,
936
1002
  coverageSnapshot,
937
1003
  hardeningSnapshot,
@@ -950,7 +1016,9 @@ export function captureOperatorProofBundle(options) {
950
1016
  verdict,
951
1017
  statusSignals,
952
1018
  guardLine,
1019
+ feedbackLine,
953
1020
  attributionLine,
1021
+ attributionCoverageLine,
954
1022
  learningPathLine,
955
1023
  steps,
956
1024
  summaryPath: path.join(bundleDir, "summary.md"),
@@ -7,6 +7,29 @@ function isSeedAwaitingFirstPromotion(status) {
7
7
  function normalizeOptionalString(value) {
8
8
  return typeof value === "string" && value.trim().length > 0 ? value : null;
9
9
  }
10
+ function formatOptionalFeedbackLatest(tracedLearning) {
11
+ const latestLabel = normalizeOptionalString(tracedLearning?.feedbackSummary?.latestLabel);
12
+ return latestLabel === null ? "" : ` latest=${latestLabel}`;
13
+ }
14
+ function formatOperatorFeedbackSummary({ tracedLearning }) {
15
+ const routeTraceCount = tracedLearning?.feedbackSummary?.routeTraceCount ?? tracedLearning?.routeTraceCount ?? 0;
16
+ const supervisedTraceCount = tracedLearning?.feedbackSummary?.supervisedTraceCount ?? tracedLearning?.supervisionCount ?? 0;
17
+ return [
18
+ `helpful=${tracedLearning?.feedbackSummary?.helpfulCount ?? 0}`,
19
+ `irrelevant=${tracedLearning?.feedbackSummary?.irrelevantCount ?? 0}`,
20
+ `harmful=${tracedLearning?.feedbackSummary?.harmfulCount ?? 0}`,
21
+ `supervisedTraceCount=${supervisedTraceCount}`,
22
+ `routeTraceCount=${routeTraceCount}`
23
+ ].join(" ") + formatOptionalFeedbackLatest(tracedLearning);
24
+ }
25
+ function formatOperatorAttributionCoverageSummary({ tracedLearning }) {
26
+ return [
27
+ `completedWithoutEvaluation=${tracedLearning?.attributionCoverage?.completedWithoutEvaluationCount ?? 0}`,
28
+ `ready=${tracedLearning?.attributionCoverage?.readyCount ?? 0}`,
29
+ `delayed=${tracedLearning?.attributionCoverage?.delayedCount ?? 0}`,
30
+ `budgetDeferred=${tracedLearning?.attributionCoverage?.budgetDeferredCount ?? 0}`
31
+ ].join(" ");
32
+ }
10
33
  function formatOperatorLearningAttributionSummary({ status }) {
11
34
  const attribution = status?.learningAttribution ?? null;
12
35
  if (!attribution) {
@@ -55,4 +78,4 @@ export function formatOperatorLearningPathSummary({ status, learningPath, traced
55
78
  ...detailParts
56
79
  ].join(" ");
57
80
  }
58
- export { formatOperatorLearningAttributionSummary };
81
+ export { formatOperatorAttributionCoverageSummary, formatOperatorFeedbackSummary, formatOperatorLearningAttributionSummary };
@@ -15,12 +15,412 @@ function normalizeCount(value) {
15
15
  function normalizeOptionalString(value) {
16
16
  return typeof value === "string" && value.trim().length > 0 ? value : null;
17
17
  }
18
+ function parseJsonValue(value, fallback) {
19
+ if (typeof value !== "string" || value.trim().length === 0) {
20
+ return fallback;
21
+ }
22
+ try {
23
+ return JSON.parse(value);
24
+ }
25
+ catch {
26
+ return fallback;
27
+ }
28
+ }
18
29
  function normalizeUnitInterval(value) {
19
30
  return Number.isFinite(value) ? Math.max(0, Math.min(1, Number(value))) : 0;
20
31
  }
21
32
  function normalizeSource(value) {
22
33
  return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
23
34
  }
35
+ function toRecord(value) {
36
+ return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
37
+ }
38
+ function normalizeAgentIdentity(value) {
39
+ const record = toRecord(value);
40
+ const agentId = normalizeOptionalString(record?.agentId);
41
+ const lane = normalizeOptionalString(record?.lane);
42
+ return agentId === null || lane === null ? null : { agentId, lane };
43
+ }
44
+ function formatAgentIdentity(identity) {
45
+ if (identity === null) {
46
+ return null;
47
+ }
48
+ return identity.lane === "main" ? identity.agentId : `${identity.agentId}:${identity.lane}`;
49
+ }
50
+ function defaultFeedbackSummary(routeTraceCount = 0, supervisedTraceCount = 0, detail = "feedback truth is not visible in the current status surface") {
51
+ return {
52
+ visible: false,
53
+ helpfulCount: 0,
54
+ irrelevantCount: 0,
55
+ harmfulCount: 0,
56
+ supervisedTraceCount,
57
+ routeTraceCount,
58
+ latestAgentIdentity: null,
59
+ latestLabel: null,
60
+ detail
61
+ };
62
+ }
63
+ function normalizeFeedbackSummary(value, counts = {}) {
64
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
65
+ return defaultFeedbackSummary(normalizeCount(counts.routeTraceCount), normalizeCount(counts.supervisedTraceCount));
66
+ }
67
+ const routeTraceCount = normalizeCount(value.routeTraceCount ?? counts.routeTraceCount);
68
+ const supervisedTraceCount = normalizeCount(value.supervisedTraceCount ?? counts.supervisedTraceCount);
69
+ const latestAgentIdentity = normalizeAgentIdentity(value.latestAgentIdentity);
70
+ return {
71
+ visible: value.visible === true,
72
+ helpfulCount: normalizeCount(value.helpfulCount),
73
+ irrelevantCount: normalizeCount(value.irrelevantCount),
74
+ harmfulCount: normalizeCount(value.harmfulCount),
75
+ supervisedTraceCount,
76
+ routeTraceCount,
77
+ latestAgentIdentity,
78
+ latestLabel: normalizeOptionalString(value.latestLabel) ?? formatAgentIdentity(latestAgentIdentity),
79
+ detail: normalizeOptionalString(value.detail)
80
+ ?? (routeTraceCount === 0
81
+ ? "no traced routes recorded yet"
82
+ : `${supervisedTraceCount}/${routeTraceCount} traced routes are covered by live verdicts`)
83
+ };
84
+ }
85
+ function defaultAttributionCoverage(detail = "teacher gating truth is not visible in the current status surface") {
86
+ return {
87
+ visible: false,
88
+ gatingVisible: false,
89
+ completedWithoutEvaluationCount: 0,
90
+ readyCount: 0,
91
+ delayedCount: 0,
92
+ budgetDeferredCount: 0,
93
+ detail
94
+ };
95
+ }
96
+ function normalizeAttributionCoverage(value) {
97
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
98
+ return defaultAttributionCoverage();
99
+ }
100
+ return {
101
+ visible: value.visible === true,
102
+ gatingVisible: value.gatingVisible === true,
103
+ completedWithoutEvaluationCount: normalizeCount(value.completedWithoutEvaluationCount),
104
+ readyCount: normalizeCount(value.readyCount),
105
+ delayedCount: normalizeCount(value.delayedCount),
106
+ budgetDeferredCount: normalizeCount(value.budgetDeferredCount),
107
+ detail: normalizeOptionalString(value.detail)
108
+ ?? "teacher gating truth is not visible in the current status surface"
109
+ };
110
+ }
111
+ function hasNonEmptyToolResults(value) {
112
+ if (Array.isArray(value)) {
113
+ return value.length > 0;
114
+ }
115
+ return typeof value === "string" && value.trim().length > 0 && value.trim() !== "[]";
116
+ }
117
+ function hasTeacherBindingMode(rawEvaluation) {
118
+ const evaluation = parseJsonValue(rawEvaluation, null);
119
+ switch (evaluation?.bindingMode) {
120
+ case "exact_decision_id":
121
+ case "exact_selection_digest":
122
+ case "turn_compile_event_id":
123
+ case "trace_id":
124
+ case "legacy_heuristic":
125
+ case "unbound":
126
+ return true;
127
+ default:
128
+ return false;
129
+ }
130
+ }
131
+ function classifyContextFeedbackVerdict(score) {
132
+ if (Number(score) >= 0.25) {
133
+ return "helpful";
134
+ }
135
+ if (Number(score) <= -0.25) {
136
+ return "harmful";
137
+ }
138
+ return "irrelevant";
139
+ }
140
+ function isObservationReadyForTeacher(row, readyBefore) {
141
+ return row.status === "pending_teacher"
142
+ || normalizeOptionalString(row.follow_up_text) !== null
143
+ || hasNonEmptyToolResults(row.tool_results_json)
144
+ || Number(row.created_at ?? 0) <= readyBefore;
145
+ }
146
+ function buildDerivedFeedbackSummary(db, routeTraceCount, defaultSupervisionCount) {
147
+ const traceRows = db.prepare(`
148
+ SELECT id, route_trace_json
149
+ FROM brain_traces
150
+ `).all();
151
+ const traceAgentIdentityById = new Map();
152
+ for (const row of traceRows) {
153
+ const routeTrace = parseJsonValue(row?.route_trace_json, null);
154
+ const agentIdentity = normalizeAgentIdentity(routeTrace?.agentIdentity);
155
+ if (agentIdentity !== null && typeof row?.id === "string") {
156
+ traceAgentIdentityById.set(row.id, agentIdentity);
157
+ }
158
+ }
159
+ const verdictCounts = {
160
+ helpfulCount: 0,
161
+ irrelevantCount: 0,
162
+ harmfulCount: 0
163
+ };
164
+ const latestTraceIds = new Set();
165
+ let latestAgentIdentity = null;
166
+ const supervisionRows = db.prepare(`
167
+ SELECT trace_id, metadata, value
168
+ FROM brain_trace_supervision
169
+ WHERE resolution = 'promoted_to_label'
170
+ ORDER BY created_at DESC
171
+ `).all();
172
+ for (const row of supervisionRows) {
173
+ const traceId = normalizeOptionalString(row?.trace_id);
174
+ if (traceId === null || latestTraceIds.has(traceId)) {
175
+ continue;
176
+ }
177
+ latestTraceIds.add(traceId);
178
+ const metadata = parseJsonValue(row?.metadata, {});
179
+ const agentIdentity = normalizeAgentIdentity(metadata?.agentIdentity)
180
+ ?? traceAgentIdentityById.get(traceId)
181
+ ?? null;
182
+ if (latestAgentIdentity === null) {
183
+ latestAgentIdentity = agentIdentity;
184
+ }
185
+ const verdict = classifyContextFeedbackVerdict(Number(row?.value ?? 0));
186
+ if (verdict === "helpful") {
187
+ verdictCounts.helpfulCount += 1;
188
+ }
189
+ else if (verdict === "harmful") {
190
+ verdictCounts.harmfulCount += 1;
191
+ }
192
+ else {
193
+ verdictCounts.irrelevantCount += 1;
194
+ }
195
+ }
196
+ const supervisedTraceCount = latestTraceIds.size || normalizeCount(defaultSupervisionCount);
197
+ return {
198
+ visible: true,
199
+ ...verdictCounts,
200
+ supervisedTraceCount,
201
+ routeTraceCount,
202
+ latestAgentIdentity,
203
+ latestLabel: formatAgentIdentity(latestAgentIdentity),
204
+ detail: routeTraceCount === 0
205
+ ? "no traced routes recorded yet"
206
+ : `${verdictCounts.helpfulCount} helpful, ${verdictCounts.irrelevantCount} irrelevant, ${verdictCounts.harmfulCount} harmful; ${supervisedTraceCount}/${routeTraceCount} traced routes are supervised`
207
+ };
208
+ }
209
+ function buildDerivedAttributionCoverage(db) {
210
+ const completedRows = db.prepare(`
211
+ SELECT teacher_evaluation_json
212
+ FROM brain_observations
213
+ WHERE status = 'completed'
214
+ `).all();
215
+ const completedWithoutEvaluationCount = completedRows.reduce((sum, row) => sum + (hasTeacherBindingMode(row?.teacher_evaluation_json) ? 0 : 1), 0);
216
+ const evaluationCycle = loadTrainingStateJson(db, "last_teacher_evaluation_cycle_json");
217
+ const budgetPerTick = Number.isFinite(evaluationCycle.value?.budgetPerTick)
218
+ ? Math.max(0, Math.trunc(evaluationCycle.value.budgetPerTick))
219
+ : null;
220
+ const delayMs = Number.isFinite(evaluationCycle.value?.delayMs)
221
+ ? Math.max(0, Math.trunc(evaluationCycle.value.delayMs))
222
+ : null;
223
+ if (budgetPerTick === null || delayMs === null) {
224
+ return {
225
+ visible: true,
226
+ gatingVisible: false,
227
+ completedWithoutEvaluationCount,
228
+ readyCount: 0,
229
+ delayedCount: 0,
230
+ budgetDeferredCount: 0,
231
+ detail: "teacher gating truth is not visible in the current status surface"
232
+ };
233
+ }
234
+ const pendingRows = db.prepare(`
235
+ SELECT status, follow_up_text, tool_results_json, created_at
236
+ FROM brain_observations
237
+ WHERE status IN ('pending_followup', 'pending_teacher')
238
+ ORDER BY created_at ASC
239
+ `).all();
240
+ const readyBefore = Date.now() - delayMs;
241
+ let readyCount = 0;
242
+ for (const row of pendingRows) {
243
+ if (isObservationReadyForTeacher(row, readyBefore)) {
244
+ readyCount += 1;
245
+ }
246
+ }
247
+ const delayedCount = Math.max(0, pendingRows.length - readyCount);
248
+ const budgetDeferredCount = Math.max(0, readyCount - budgetPerTick);
249
+ return {
250
+ visible: true,
251
+ gatingVisible: true,
252
+ completedWithoutEvaluationCount,
253
+ readyCount,
254
+ delayedCount,
255
+ budgetDeferredCount,
256
+ detail: pendingRows.length === 0
257
+ ? "no teacher observations are pending"
258
+ : `completed_without_evaluation=${completedWithoutEvaluationCount}; ready=${readyCount}, delayed=${delayedCount}, budget_deferred=${budgetDeferredCount}`
259
+ };
260
+ }
261
+ function loadJsonFile(pathname) {
262
+ if (!existsSync(pathname)) {
263
+ return null;
264
+ }
265
+ try {
266
+ return JSON.parse(readFileSync(pathname, "utf8"));
267
+ }
268
+ catch {
269
+ return null;
270
+ }
271
+ }
272
+ function resolveActivePackPaths(activationRoot) {
273
+ const pointers = toRecord(loadJsonFile(path.join(path.resolve(activationRoot), "activation-pointers.json")));
274
+ const active = toRecord(pointers?.active);
275
+ const packRootDir = normalizeOptionalString(active?.packRootDir)
276
+ ?? (normalizeOptionalString(active?.packId) === null
277
+ ? null
278
+ : path.join(path.resolve(activationRoot), "packs", String(active.packId)));
279
+ const manifestPath = normalizeOptionalString(active?.manifestPath)
280
+ ?? (packRootDir === null ? null : path.join(packRootDir, "manifest.json"));
281
+ return {
282
+ packRootDir,
283
+ manifestPath
284
+ };
285
+ }
286
+ function buildActivePackFeedbackSummary(activationRoot) {
287
+ const active = resolveActivePackPaths(activationRoot);
288
+ if (active.packRootDir === null || active.manifestPath === null) {
289
+ return null;
290
+ }
291
+ const manifest = toRecord(loadJsonFile(active.manifestPath));
292
+ const runtimeAssets = toRecord(manifest?.runtimeAssets);
293
+ const router = toRecord(runtimeAssets?.router);
294
+ const routerArtifactPath = normalizeOptionalString(router?.artifactPath);
295
+ if (routerArtifactPath === null) {
296
+ return null;
297
+ }
298
+ const routerPath = path.isAbsolute(routerArtifactPath)
299
+ ? routerArtifactPath
300
+ : path.join(active.packRootDir, routerArtifactPath);
301
+ const routerArtifact = toRecord(loadJsonFile(routerPath));
302
+ const traces = Array.isArray(routerArtifact?.traces) ? routerArtifact.traces : [];
303
+ const verdictCounts = {
304
+ helpfulCount: 0,
305
+ irrelevantCount: 0,
306
+ harmfulCount: 0
307
+ };
308
+ for (const trace of traces) {
309
+ const traceRecord = toRecord(trace);
310
+ if (normalizeOptionalString(traceRecord?.supervisionKind) === "route_trace") {
311
+ continue;
312
+ }
313
+ const verdict = classifyContextFeedbackVerdict(Number(traceRecord?.reward ?? 0));
314
+ if (verdict === "helpful") {
315
+ verdictCounts.helpfulCount += 1;
316
+ }
317
+ else if (verdict === "harmful") {
318
+ verdictCounts.harmfulCount += 1;
319
+ }
320
+ else {
321
+ verdictCounts.irrelevantCount += 1;
322
+ }
323
+ }
324
+ const routeTraceCount = normalizeCount(routerArtifact?.training?.routeTraceCount) || traces.length;
325
+ const supervisedTraceCount = verdictCounts.helpfulCount + verdictCounts.irrelevantCount + verdictCounts.harmfulCount;
326
+ if (routeTraceCount === 0 && supervisedTraceCount === 0) {
327
+ return null;
328
+ }
329
+ return {
330
+ visible: true,
331
+ ...verdictCounts,
332
+ supervisedTraceCount,
333
+ routeTraceCount,
334
+ latestAgentIdentity: null,
335
+ latestLabel: null,
336
+ detail: routeTraceCount === 0
337
+ ? "no active-pack traced routes are visible"
338
+ : `${verdictCounts.helpfulCount} helpful, ${verdictCounts.irrelevantCount} irrelevant, ${verdictCounts.harmfulCount} harmful; ${supervisedTraceCount}/${routeTraceCount} active-pack traced routes are supervised`
339
+ };
340
+ }
341
+ function readIntegerNote(notes, prefix) {
342
+ if (!Array.isArray(notes)) {
343
+ return null;
344
+ }
345
+ const entry = notes.find((candidate) => typeof candidate === "string" && candidate.startsWith(prefix));
346
+ if (typeof entry !== "string") {
347
+ return null;
348
+ }
349
+ const parsed = Number.parseInt(entry.slice(prefix.length), 10);
350
+ return Number.isFinite(parsed) ? parsed : null;
351
+ }
352
+ function buildWatchSnapshotAttributionCoverage(activationRoot) {
353
+ const snapshot = toRecord(loadJsonFile(path.join(path.resolve(activationRoot), "watch", "teacher-snapshot.json")));
354
+ const notes = Array.isArray(snapshot?.notes)
355
+ ? snapshot.notes
356
+ : Array.isArray(snapshot?.snapshot?.diagnostics?.notes)
357
+ ? snapshot.snapshot.diagnostics.notes
358
+ : Array.isArray(snapshot?.diagnostics?.notes)
359
+ ? snapshot.diagnostics.notes
360
+ : [];
361
+ const readyCount = readIntegerNote(notes, "teacher_feedback_eligible=");
362
+ const delayedCount = readIntegerNote(notes, "teacher_feedback_delayed=");
363
+ const budgetDeferredCount = readIntegerNote(notes, "teacher_feedback_budgeted_out=");
364
+ const budgetPerTick = readIntegerNote(notes, "teacher_budget=");
365
+ const delayMs = readIntegerNote(notes, "teacher_delay_ms=");
366
+ if (readyCount === null && delayedCount === null && budgetDeferredCount === null && budgetPerTick === null && delayMs === null) {
367
+ return null;
368
+ }
369
+ return {
370
+ visible: true,
371
+ gatingVisible: budgetPerTick !== null || delayMs !== null,
372
+ completedWithoutEvaluationCount: 0,
373
+ readyCount: normalizeCount(readyCount),
374
+ delayedCount: normalizeCount(delayedCount),
375
+ budgetDeferredCount: normalizeCount(budgetDeferredCount),
376
+ detail: `watch sparse-feedback queue: completed_without_evaluation=0, ready=${normalizeCount(readyCount)}, delayed=${normalizeCount(delayedCount)}, budget_deferred=${normalizeCount(budgetDeferredCount)}`
377
+ };
378
+ }
379
+ function shouldPreferActivationFeedbackSummary(current, fallback) {
380
+ if (fallback === null) {
381
+ return false;
382
+ }
383
+ if (current.visible !== true) {
384
+ return true;
385
+ }
386
+ return normalizeCount(current.supervisedTraceCount) === 0 && normalizeCount(fallback.supervisedTraceCount) > 0;
387
+ }
388
+ function shouldPreferWatchAttributionCoverage(current, fallback) {
389
+ if (fallback === null) {
390
+ return false;
391
+ }
392
+ if (current.visible !== true || current.gatingVisible !== true) {
393
+ return normalizeCount(fallback.readyCount) > 0
394
+ || normalizeCount(fallback.delayedCount) > 0
395
+ || normalizeCount(fallback.budgetDeferredCount) > 0;
396
+ }
397
+ const currentKnown = normalizeCount(current.completedWithoutEvaluationCount)
398
+ + normalizeCount(current.readyCount)
399
+ + normalizeCount(current.delayedCount)
400
+ + normalizeCount(current.budgetDeferredCount);
401
+ const fallbackKnown = normalizeCount(fallback.completedWithoutEvaluationCount)
402
+ + normalizeCount(fallback.readyCount)
403
+ + normalizeCount(fallback.delayedCount)
404
+ + normalizeCount(fallback.budgetDeferredCount);
405
+ return currentKnown === 0 && fallbackKnown > 0;
406
+ }
407
+ function enrichBridgeWithActivationTruth(activationRoot, bridge) {
408
+ const feedbackSummary = buildActivePackFeedbackSummary(activationRoot);
409
+ const attributionCoverage = buildWatchSnapshotAttributionCoverage(activationRoot);
410
+ if (!shouldPreferActivationFeedbackSummary(bridge.feedbackSummary, feedbackSummary)
411
+ && !shouldPreferWatchAttributionCoverage(bridge.attributionCoverage, attributionCoverage)) {
412
+ return bridge;
413
+ }
414
+ return normalizeBridgePayload({
415
+ ...bridge,
416
+ feedbackSummary: shouldPreferActivationFeedbackSummary(bridge.feedbackSummary, feedbackSummary)
417
+ ? feedbackSummary
418
+ : bridge.feedbackSummary,
419
+ attributionCoverage: shouldPreferWatchAttributionCoverage(bridge.attributionCoverage, attributionCoverage)
420
+ ? attributionCoverage
421
+ : bridge.attributionCoverage
422
+ });
423
+ }
24
424
  function normalizeLastInterruptionSummary(value) {
25
425
  if (value === null || typeof value !== "object" || Array.isArray(value)) {
26
426
  return null;
@@ -95,11 +495,13 @@ function normalizeBridgePayload(payload) {
95
495
  if (payload === null || typeof payload !== "object" || Array.isArray(payload)) {
96
496
  throw new Error("expected traced-learning bridge payload object");
97
497
  }
498
+ const routeTraceCount = normalizeCount(payload.routeTraceCount);
499
+ const supervisionCount = normalizeCount(payload.supervisionCount);
98
500
  return {
99
501
  contract: TRACED_LEARNING_BRIDGE_CONTRACT,
100
502
  updatedAt: normalizeOptionalString(payload.updatedAt) ?? new Date().toISOString(),
101
- routeTraceCount: normalizeCount(payload.routeTraceCount),
102
- supervisionCount: normalizeCount(payload.supervisionCount),
503
+ routeTraceCount,
504
+ supervisionCount,
103
505
  routerUpdateCount: normalizeCount(payload.routerUpdateCount),
104
506
  teacherArtifactCount: normalizeCount(payload.teacherArtifactCount),
105
507
  pgVersionRequested: normalizeOptionalString(payload.pgVersionRequested),
@@ -111,6 +513,11 @@ function normalizeBridgePayload(payload) {
111
513
  promoted: payload.promoted === true,
112
514
  baselinePersisted: payload.baselinePersisted === true,
113
515
  lastInterruptionSummary: normalizeLastInterruptionSummary(payload.lastInterruptionSummary),
516
+ feedbackSummary: normalizeFeedbackSummary(payload.feedbackSummary, {
517
+ routeTraceCount,
518
+ supervisedTraceCount: supervisionCount
519
+ }),
520
+ attributionCoverage: normalizeAttributionCoverage(payload.attributionCoverage),
114
521
  source: normalizeSource(payload.source)
115
522
  };
116
523
  }
@@ -122,11 +529,13 @@ function normalizePersistedStatusSurface(payload) {
122
529
  if (source === null) {
123
530
  throw new Error("expected traced-learning status surface source");
124
531
  }
532
+ const routeTraceCount = normalizeCount(payload.routeTraceCount);
533
+ const supervisionCount = normalizeCount(payload.supervisionCount);
125
534
  return {
126
535
  contract: TRACED_LEARNING_STATUS_SURFACE_CONTRACT,
127
536
  updatedAt: normalizeOptionalString(payload.updatedAt) ?? new Date().toISOString(),
128
- routeTraceCount: normalizeCount(payload.routeTraceCount),
129
- supervisionCount: normalizeCount(payload.supervisionCount),
537
+ routeTraceCount,
538
+ supervisionCount,
130
539
  routerUpdateCount: normalizeCount(payload.routerUpdateCount),
131
540
  teacherArtifactCount: normalizeCount(payload.teacherArtifactCount),
132
541
  pgVersionRequested: normalizeOptionalString(payload.pgVersionRequested),
@@ -138,6 +547,11 @@ function normalizePersistedStatusSurface(payload) {
138
547
  promoted: payload.promoted === true,
139
548
  baselinePersisted: payload.baselinePersisted === true,
140
549
  lastInterruptionSummary: normalizeLastInterruptionSummary(payload.lastInterruptionSummary),
550
+ feedbackSummary: normalizeFeedbackSummary(payload.feedbackSummary, {
551
+ routeTraceCount,
552
+ supervisedTraceCount: supervisionCount
553
+ }),
554
+ attributionCoverage: normalizeAttributionCoverage(payload.attributionCoverage),
141
555
  source
142
556
  };
143
557
  }
@@ -157,6 +571,8 @@ function defaultSurface(pathname, detail, error = null) {
157
571
  promoted: false,
158
572
  baselinePersisted: false,
159
573
  lastInterruptionSummary: null,
574
+ feedbackSummary: defaultFeedbackSummary(),
575
+ attributionCoverage: defaultAttributionCoverage(),
160
576
  source: null,
161
577
  detail,
162
578
  error
@@ -278,6 +694,8 @@ function buildPersistedStatusSurfaceBridge(summary, context) {
278
694
  promoted: summary.promoted,
279
695
  baselinePersisted: summary.baselinePersisted,
280
696
  lastInterruptionSummary: summary.lastInterruptionSummary,
697
+ feedbackSummary: summary.feedbackSummary,
698
+ attributionCoverage: summary.attributionCoverage,
281
699
  source: {
282
700
  command: "brain-store",
283
701
  bridge: TRACED_LEARNING_STATUS_SURFACE_BRIDGE,
@@ -321,6 +739,20 @@ function buildDerivedBrainStoreBridge(db, context, lastInterruptionSummary = nul
321
739
  ? null
322
740
  : JSON.parse(candidateUpdateRaw);
323
741
  const candidatePackVersion = Number.parseInt(candidatePackVersionRaw ?? "", 10);
742
+ let feedbackSummary = defaultFeedbackSummary(routeTraceCount, supervisionCount);
743
+ try {
744
+ feedbackSummary = buildDerivedFeedbackSummary(db, routeTraceCount, supervisionCount);
745
+ }
746
+ catch {
747
+ feedbackSummary = defaultFeedbackSummary(routeTraceCount, supervisionCount);
748
+ }
749
+ let attributionCoverage = defaultAttributionCoverage();
750
+ try {
751
+ attributionCoverage = buildDerivedAttributionCoverage(db);
752
+ }
753
+ catch {
754
+ attributionCoverage = defaultAttributionCoverage();
755
+ }
324
756
  return normalizeBridgePayload({
325
757
  updatedAt: toIsoTimestamp(candidateUpdate?.generatedAt),
326
758
  routeTraceCount,
@@ -336,6 +768,8 @@ function buildDerivedBrainStoreBridge(db, context, lastInterruptionSummary = nul
336
768
  promoted: false,
337
769
  baselinePersisted: false,
338
770
  lastInterruptionSummary,
771
+ feedbackSummary,
772
+ attributionCoverage,
339
773
  source: {
340
774
  command: "brain-store",
341
775
  bridge: "brain_store_state",
@@ -360,6 +794,13 @@ function hasMeaningfulTracedLearningSignal(bridge) {
360
794
  bridge.pgVersionUsed !== null ||
361
795
  bridge.fallbackReason !== null ||
362
796
  bridge.routerNoOpReason !== null ||
797
+ bridge.feedbackSummary.helpfulCount > 0 ||
798
+ bridge.feedbackSummary.irrelevantCount > 0 ||
799
+ bridge.feedbackSummary.harmfulCount > 0 ||
800
+ bridge.attributionCoverage.completedWithoutEvaluationCount > 0 ||
801
+ bridge.attributionCoverage.readyCount > 0 ||
802
+ bridge.attributionCoverage.delayedCount > 0 ||
803
+ bridge.attributionCoverage.budgetDeferredCount > 0 ||
363
804
  Number.isFinite(bridge.source?.candidatePackVersion) ||
364
805
  normalizeCount(bridge.source?.candidateUpdateCount) > 0;
365
806
  }
@@ -479,27 +920,41 @@ export function loadBrainStoreTracedLearningBridge(options = {}) {
479
920
  try {
480
921
  db = new sqlite.DatabaseSync(dbPath, { readOnly: true });
481
922
  const lastInterruptionSummary = loadLastAssemblyInterruptionSummary(db);
923
+ let derived = null;
924
+ try {
925
+ derived = buildDerivedBrainStoreBridge(db, {
926
+ brainRoot,
927
+ dbPath
928
+ }, lastInterruptionSummary);
929
+ }
930
+ catch {
931
+ derived = null;
932
+ }
482
933
  const persisted = loadPersistedStatusSurface(db, {
483
934
  brainRoot,
484
935
  dbPath
485
936
  });
486
937
  if (persisted.bridge !== null) {
487
- const bridge = lastInterruptionSummary === null
488
- ? persisted.bridge
489
- : normalizeBridgePayload({
490
- ...persisted.bridge,
491
- lastInterruptionSummary
492
- });
938
+ const bridge = normalizeBridgePayload({
939
+ ...persisted.bridge,
940
+ lastInterruptionSummary: lastInterruptionSummary ?? persisted.bridge.lastInterruptionSummary,
941
+ feedbackSummary: derived?.feedbackSummary ?? persisted.bridge.feedbackSummary,
942
+ attributionCoverage: derived?.attributionCoverage ?? persisted.bridge.attributionCoverage
943
+ });
493
944
  return {
494
945
  path: dbPath,
495
946
  bridge,
496
947
  error: null
497
948
  };
498
949
  }
499
- const bridge = buildDerivedBrainStoreBridge(db, {
500
- brainRoot,
501
- dbPath
502
- }, lastInterruptionSummary);
950
+ const bridge = derived;
951
+ if (bridge === null) {
952
+ return {
953
+ path: dbPath,
954
+ bridge: null,
955
+ error: persisted.error
956
+ };
957
+ }
503
958
  if (!hasMeaningfulTracedLearningSignal(bridge)) {
504
959
  return {
505
960
  path: dbPath,
@@ -565,6 +1020,8 @@ function buildStatusSurface(pathname, bridge, options = {}) {
565
1020
  promoted: bridge.promoted,
566
1021
  baselinePersisted: bridge.baselinePersisted,
567
1022
  lastInterruptionSummary: bridge.lastInterruptionSummary,
1023
+ feedbackSummary: bridge.feedbackSummary,
1024
+ attributionCoverage: bridge.attributionCoverage,
568
1025
  source: bridge.source,
569
1026
  detail: detailParts.join(" "),
570
1027
  error: options.error ?? null
@@ -611,6 +1068,8 @@ function mergeCanonicalStatusBridge(canonicalBridge, runtimeLoaded) {
611
1068
  promoted: canonicalBridge.promoted,
612
1069
  baselinePersisted: canonicalBridge.baselinePersisted,
613
1070
  lastInterruptionSummary: canonicalBridge.lastInterruptionSummary ?? runtimeBridge?.lastInterruptionSummary ?? null,
1071
+ feedbackSummary: canonicalBridge.feedbackSummary,
1072
+ attributionCoverage: canonicalBridge.attributionCoverage,
614
1073
  fallbackReason: canonicalBridge.fallbackReason,
615
1074
  routerNoOpReason: canonicalBridge.routerNoOpReason,
616
1075
  source: runtimeMaterialized === null
@@ -634,6 +1093,8 @@ function mergeCanonicalStatusBridge(canonicalBridge, runtimeLoaded) {
634
1093
  promoted: runtimeBridge?.promoted ?? canonicalBridge.promoted,
635
1094
  baselinePersisted: runtimeBridge?.baselinePersisted ?? canonicalBridge.baselinePersisted,
636
1095
  lastInterruptionSummary: canonicalBridge.lastInterruptionSummary ?? runtimeBridge?.lastInterruptionSummary ?? null,
1096
+ feedbackSummary: canonicalBridge.feedbackSummary,
1097
+ attributionCoverage: canonicalBridge.attributionCoverage,
637
1098
  fallbackReason: runtimeBridge?.fallbackReason ?? canonicalBridge.fallbackReason ?? null,
638
1099
  routerNoOpReason: runtimeBridge?.routerNoOpReason ?? canonicalBridge.routerNoOpReason ?? null,
639
1100
  source: runtimeMaterialized === null
@@ -655,11 +1116,16 @@ export function mergeTracedLearningBridgePayload(payload, persisted) {
655
1116
  const routerUpdateCount = Math.max(current.routerUpdateCount, persistedBridge.routerUpdateCount);
656
1117
  const teacherArtifactCount = Math.max(current.teacherArtifactCount, persistedBridge.teacherArtifactCount);
657
1118
  const lastInterruptionSummary = current.lastInterruptionSummary ?? persistedBridge.lastInterruptionSummary ?? null;
1119
+ const feedbackSummary = current.feedbackSummary.visible ? current.feedbackSummary : persistedBridge.feedbackSummary;
1120
+ const attributionCoverage = current.attributionCoverage.visible ? current.attributionCoverage : persistedBridge.attributionCoverage;
658
1121
  const usedBridge = routeTraceCount !== current.routeTraceCount ||
659
1122
  supervisionCount !== current.supervisionCount ||
660
1123
  routerUpdateCount !== current.routerUpdateCount ||
661
1124
  teacherArtifactCount !== current.teacherArtifactCount ||
662
- lastInterruptionSummary !== current.lastInterruptionSummary;
1125
+ lastInterruptionSummary !== current.lastInterruptionSummary ||
1126
+ feedbackSummary.visible !== current.feedbackSummary.visible ||
1127
+ attributionCoverage.visible !== current.attributionCoverage.visible ||
1128
+ attributionCoverage.gatingVisible !== current.attributionCoverage.gatingVisible;
663
1129
  if (!usedBridge) {
664
1130
  return current;
665
1131
  }
@@ -670,6 +1136,8 @@ export function mergeTracedLearningBridgePayload(payload, persisted) {
670
1136
  routerUpdateCount,
671
1137
  teacherArtifactCount,
672
1138
  lastInterruptionSummary,
1139
+ feedbackSummary,
1140
+ attributionCoverage,
673
1141
  routerNoOpReason: supervisionCount > 0 || routerUpdateCount > 0 ? null : current.routerNoOpReason,
674
1142
  source: {
675
1143
  ...(current.source ?? {}),
@@ -690,12 +1158,12 @@ export function buildTracedLearningStatusSurface(activationRoot, options = {}) {
690
1158
  const persisted = loadBrainStoreTracedLearningBridge(options);
691
1159
  const runtime = loadTracedLearningBridge(activationRoot);
692
1160
  if (persisted.bridge !== null) {
693
- return buildStatusSurface(persisted.path, mergeCanonicalStatusBridge(persisted.bridge, runtime), {
1161
+ return buildStatusSurface(persisted.path, enrichBridgeWithActivationTruth(activationRoot, mergeCanonicalStatusBridge(persisted.bridge, runtime)), {
694
1162
  runtimeState: describeBridgeRuntimeState(runtime)
695
1163
  });
696
1164
  }
697
1165
  if (runtime.bridge !== null) {
698
- return buildStatusSurface(runtime.path, runtime.bridge);
1166
+ return buildStatusSurface(runtime.path, enrichBridgeWithActivationTruth(activationRoot, runtime.bridge));
699
1167
  }
700
1168
  if (persisted.error !== null) {
701
1169
  return defaultSurface(persisted.path, "brain_store_unreadable", persisted.error);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openclawbrain/cli",
3
- "version": "0.4.24",
3
+ "version": "0.4.26",
4
4
  "description": "OpenClawBrain operator CLI package with install/status helpers, daemon controls, and import/export tooling.",
5
5
  "type": "module",
6
6
  "main": "./dist/src/index.js",