@openclawbrain/cli 0.4.1 → 0.4.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -8
- package/dist/src/attachment-policy-truth.js +175 -0
- package/dist/src/cli.js +120 -29
- package/dist/src/embedding-status.js +78 -0
- package/dist/src/traced-learning-bridge.js +554 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
# @openclawbrain/cli
|
|
2
2
|
|
|
3
|
-
`@openclawbrain/cli@0.4.
|
|
3
|
+
`@openclawbrain/cli@0.4.3` is the published operator CLI package for OpenClawBrain.
|
|
4
4
|
|
|
5
5
|
Primary public flow:
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
8
|
openclaw plugins install @openclawbrain/openclaw@0.4.0
|
|
9
|
-
npx @openclawbrain/cli@0.4.
|
|
9
|
+
npx @openclawbrain/cli@0.4.3 install --openclaw-home ~/.openclaw
|
|
10
10
|
openclaw gateway restart
|
|
11
|
-
npx @openclawbrain/cli@0.4.
|
|
11
|
+
npx @openclawbrain/cli@0.4.3 status --openclaw-home ~/.openclaw --detailed
|
|
12
12
|
```
|
|
13
13
|
|
|
14
|
-
Patch note for `0.4.
|
|
14
|
+
Patch note for `0.4.3`: the CLI now reports active-pack numeric embeddings more truthfully across alternate vector shapes, and the repo-side serve-time decision matcher is more tolerant of event-id and timestamp drift when harvesting supervision candidates.
|
|
15
15
|
|
|
16
16
|
Current caveat: some hosts still warn about a plugin id mismatch because the plugin manifest uses `openclawbrain` while the package/entry hint uses `openclaw`. The install still works; treat that warning as currently cosmetic.
|
|
17
17
|
|
|
@@ -20,10 +20,10 @@ This package carries the `openclawbrain` CLI, daemon controls, import/export hel
|
|
|
20
20
|
## Commands
|
|
21
21
|
|
|
22
22
|
```bash
|
|
23
|
-
npx @openclawbrain/cli@0.4.
|
|
24
|
-
npx @openclawbrain/cli@0.4.
|
|
25
|
-
npx @openclawbrain/cli@0.4.
|
|
26
|
-
npx @openclawbrain/cli@0.4.
|
|
23
|
+
npx @openclawbrain/cli@0.4.3 install --openclaw-home ~/.openclaw
|
|
24
|
+
npx @openclawbrain/cli@0.4.3 status --openclaw-home ~/.openclaw --detailed
|
|
25
|
+
npx @openclawbrain/cli@0.4.3 rollback --activation-root /var/openclawbrain/activation --dry-run
|
|
26
|
+
npx @openclawbrain/cli@0.4.3 daemon status --activation-root /var/openclawbrain/activation
|
|
27
27
|
```
|
|
28
28
|
|
|
29
29
|
If the CLI is already on your `PATH`, `openclawbrain ...` is the same command surface. The docs lead with `npx` because that is the clean-host public-registry lane that already passed on `redogfood`.
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
|
|
4
|
+
const ATTACHMENT_POLICY_DECLARATION_CONTRACT = "openclawbrain.attachment-policy-declaration.v1";
|
|
5
|
+
const ATTACHMENT_POLICY_DECLARATION_DIRNAME = "attachment-truth";
|
|
6
|
+
const ATTACHMENT_POLICY_DECLARATION_BASENAME = "policy-declaration.json";
|
|
7
|
+
|
|
8
|
+
function toErrorMessage(error) {
|
|
9
|
+
return error instanceof Error ? error.message : String(error);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function readRecord(value) {
|
|
13
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
return value;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function normalizeOptionalString(value) {
|
|
21
|
+
return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function normalizeIsoTimestamp(value, fieldName) {
|
|
25
|
+
const normalized = normalizeOptionalString(value);
|
|
26
|
+
|
|
27
|
+
if (normalized === null) {
|
|
28
|
+
throw new Error(`${fieldName} is required`);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (Number.isNaN(Date.parse(normalized))) {
|
|
32
|
+
throw new Error(`${fieldName} must be an ISO timestamp`);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return new Date(normalized).toISOString();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function normalizeAttachmentPolicy(value, fieldName = "policy") {
|
|
39
|
+
if (value === "undeclared" || value === "dedicated" || value === "shared") {
|
|
40
|
+
return value;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
throw new Error(`${fieldName} must be one of undeclared, dedicated, or shared`);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function normalizeOptionalAttachmentPolicy(value, fieldName) {
|
|
47
|
+
if (value === null || value === undefined) {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return normalizeAttachmentPolicy(value, fieldName);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function validateAttachmentPolicyDeclaration(activationRoot, value) {
|
|
55
|
+
const record = readRecord(value);
|
|
56
|
+
|
|
57
|
+
if (record === null) {
|
|
58
|
+
throw new Error("attachment policy declaration must contain an object");
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (record.contract !== ATTACHMENT_POLICY_DECLARATION_CONTRACT) {
|
|
62
|
+
throw new Error(`attachment policy declaration contract must be ${ATTACHMENT_POLICY_DECLARATION_CONTRACT}`);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (typeof record.activationRoot !== "string" || record.activationRoot.trim().length === 0) {
|
|
66
|
+
throw new Error("attachment policy declaration activationRoot must be a non-empty string");
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const resolvedActivationRoot = path.resolve(record.activationRoot);
|
|
70
|
+
if (resolvedActivationRoot !== activationRoot) {
|
|
71
|
+
throw new Error(`attachment policy declaration activationRoot mismatch: expected ${activationRoot}, received ${resolvedActivationRoot}`);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return {
|
|
75
|
+
contract: ATTACHMENT_POLICY_DECLARATION_CONTRACT,
|
|
76
|
+
activationRoot,
|
|
77
|
+
updatedAt: normalizeIsoTimestamp(record.updatedAt, "updatedAt"),
|
|
78
|
+
policy: normalizeAttachmentPolicy(record.policy),
|
|
79
|
+
source: normalizeOptionalString(record.source) ?? "unknown",
|
|
80
|
+
openclawHome: normalizeOptionalString(record.openclawHome),
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function resolveAttachmentPolicyDeclarationPath(activationRoot) {
|
|
85
|
+
return path.resolve(activationRoot, ATTACHMENT_POLICY_DECLARATION_DIRNAME, ATTACHMENT_POLICY_DECLARATION_BASENAME);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function loadAttachmentPolicyDeclaration(activationRoot) {
|
|
89
|
+
const resolvedActivationRoot = path.resolve(activationRoot);
|
|
90
|
+
const declarationPath = resolveAttachmentPolicyDeclarationPath(resolvedActivationRoot);
|
|
91
|
+
|
|
92
|
+
if (!existsSync(declarationPath)) {
|
|
93
|
+
return {
|
|
94
|
+
path: declarationPath,
|
|
95
|
+
declaration: null,
|
|
96
|
+
error: null,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
try {
|
|
101
|
+
return {
|
|
102
|
+
path: declarationPath,
|
|
103
|
+
declaration: validateAttachmentPolicyDeclaration(
|
|
104
|
+
resolvedActivationRoot,
|
|
105
|
+
JSON.parse(readFileSync(declarationPath, "utf8")),
|
|
106
|
+
),
|
|
107
|
+
error: null,
|
|
108
|
+
};
|
|
109
|
+
} catch (error) {
|
|
110
|
+
return {
|
|
111
|
+
path: declarationPath,
|
|
112
|
+
declaration: null,
|
|
113
|
+
error: toErrorMessage(error),
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export function writeAttachmentPolicyDeclaration(input) {
|
|
119
|
+
const activationRoot = path.resolve(input.activationRoot);
|
|
120
|
+
const declarationPath = resolveAttachmentPolicyDeclarationPath(activationRoot);
|
|
121
|
+
const declaration = {
|
|
122
|
+
contract: ATTACHMENT_POLICY_DECLARATION_CONTRACT,
|
|
123
|
+
activationRoot,
|
|
124
|
+
updatedAt: normalizeIsoTimestamp(input.updatedAt ?? new Date().toISOString(), "updatedAt"),
|
|
125
|
+
policy: normalizeAttachmentPolicy(input.policy),
|
|
126
|
+
source: normalizeOptionalString(input.source) ?? "cli",
|
|
127
|
+
openclawHome: normalizeOptionalString(input.openclawHome),
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
mkdirSync(path.dirname(declarationPath), { recursive: true });
|
|
131
|
+
writeFileSync(declarationPath, `${JSON.stringify(declaration, null, 2)}\n`, "utf8");
|
|
132
|
+
|
|
133
|
+
return {
|
|
134
|
+
path: declarationPath,
|
|
135
|
+
declaration,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export function resolveEffectiveAttachmentPolicyTruth(input) {
|
|
140
|
+
const referenceCount =
|
|
141
|
+
typeof input.referenceCount === "number" && Number.isInteger(input.referenceCount) && input.referenceCount >= 0
|
|
142
|
+
? input.referenceCount
|
|
143
|
+
: 0;
|
|
144
|
+
const discoverablePolicy = referenceCount > 1 ? "shared" : null;
|
|
145
|
+
const statusPolicy = normalizeOptionalAttachmentPolicy(input.statusPolicy ?? null, "statusPolicy");
|
|
146
|
+
const reportPolicy = normalizeOptionalAttachmentPolicy(input.reportPolicy ?? null, "reportPolicy");
|
|
147
|
+
const declaredPolicy = normalizeOptionalAttachmentPolicy(input.declaredPolicy ?? null, "declaredPolicy");
|
|
148
|
+
const effectivePolicy =
|
|
149
|
+
discoverablePolicy ??
|
|
150
|
+
(statusPolicy !== null && statusPolicy !== "undeclared"
|
|
151
|
+
? statusPolicy
|
|
152
|
+
: reportPolicy !== null && reportPolicy !== "undeclared"
|
|
153
|
+
? reportPolicy
|
|
154
|
+
: declaredPolicy);
|
|
155
|
+
|
|
156
|
+
return {
|
|
157
|
+
effectivePolicy,
|
|
158
|
+
statusPolicy:
|
|
159
|
+
effectivePolicy === null
|
|
160
|
+
? statusPolicy
|
|
161
|
+
: discoverablePolicy !== null
|
|
162
|
+
? discoverablePolicy
|
|
163
|
+
: statusPolicy === null || statusPolicy === "undeclared"
|
|
164
|
+
? effectivePolicy
|
|
165
|
+
: statusPolicy,
|
|
166
|
+
reportPolicy:
|
|
167
|
+
effectivePolicy === null
|
|
168
|
+
? reportPolicy
|
|
169
|
+
: discoverablePolicy !== null
|
|
170
|
+
? discoverablePolicy
|
|
171
|
+
: reportPolicy === null || reportPolicy === "undeclared"
|
|
172
|
+
? effectivePolicy
|
|
173
|
+
: reportPolicy,
|
|
174
|
+
};
|
|
175
|
+
}
|
package/dist/src/cli.js
CHANGED
|
@@ -15,9 +15,12 @@ import { resolveActivationRoot } from "./resolve-activation-root.js";
|
|
|
15
15
|
import { describeOpenClawHomeInspection, discoverOpenClawHomes, formatOpenClawHomeLayout, formatOpenClawHomeProfileSource, inspectOpenClawHome } from "./openclaw-home-layout.js";
|
|
16
16
|
import { inspectOpenClawBrainHookStatus, inspectOpenClawBrainPluginAllowlist } from "./openclaw-hook-truth.js";
|
|
17
17
|
import { describeOpenClawBrainInstallIdentity, describeOpenClawBrainInstallLayout, findInstalledOpenClawBrainPlugin, getOpenClawBrainKnownPluginIds, pinInstalledOpenClawBrainPluginActivationRoot, resolveOpenClawBrainInstallTarget } from "./openclaw-plugin-install.js";
|
|
18
|
+
import { loadAttachmentPolicyDeclaration, resolveEffectiveAttachmentPolicyTruth, writeAttachmentPolicyDeclaration } from "./attachment-policy-truth.js";
|
|
18
19
|
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, writeScannedEventExportBundle } from "./index.js";
|
|
19
20
|
import { appendLearningUpdateLogs } from "./learning-spine.js";
|
|
20
21
|
import { buildPassiveLearningSessionExportFromOpenClawSessionStore } from "./local-session-passive-learning.js";
|
|
22
|
+
import { summarizePackVectorEmbeddingState } from "./embedding-status.js";
|
|
23
|
+
import { buildTracedLearningStatusSurface, loadBrainStoreTracedLearningBridge, mergeTracedLearningBridgePayload, persistBrainStoreTracedLearningBridge, writeTracedLearningBridge } from "./traced-learning-bridge.js";
|
|
21
24
|
import { discoverOpenClawSessionStores, loadOpenClawSessionIndex, readOpenClawSessionFile } from "./session-store.js";
|
|
22
25
|
import { readOpenClawBrainProviderDefaults, readOpenClawBrainProviderConfig, readOpenClawBrainProviderConfigFromSources, resolveOpenClawBrainProviderDefaultsPath } from "./provider-config.js";
|
|
23
26
|
const OPENCLAWBRAIN_EMBEDDER_BASE_URL_ENV = "OPENCLAWBRAIN_EMBEDDER_BASE_URL";
|
|
@@ -861,6 +864,56 @@ function summarizeStatusAttachmentTruth(input) {
|
|
|
861
864
|
})
|
|
862
865
|
};
|
|
863
866
|
}
|
|
867
|
+
function normalizeAttachmentPolicyMode(value) {
|
|
868
|
+
return value === "undeclared" || value === "dedicated" || value === "shared"
|
|
869
|
+
? value
|
|
870
|
+
: null;
|
|
871
|
+
}
|
|
872
|
+
function applyAttachmentPolicyTruth(status, report) {
|
|
873
|
+
const referenceCount = findInstalledHookReferencesForActivationRoot({
|
|
874
|
+
activationRoot: status.host.activationRoot
|
|
875
|
+
}).length;
|
|
876
|
+
const declaration = loadAttachmentPolicyDeclaration(status.host.activationRoot);
|
|
877
|
+
const resolvedPolicy = resolveEffectiveAttachmentPolicyTruth({
|
|
878
|
+
statusPolicy: normalizeAttachmentPolicyMode(status.attachment.policyMode),
|
|
879
|
+
reportPolicy: report === null
|
|
880
|
+
? null
|
|
881
|
+
: normalizeAttachmentPolicyMode(report.manyProfile.declaredAttachmentPolicy),
|
|
882
|
+
declaredPolicy: declaration.declaration?.policy ?? null,
|
|
883
|
+
referenceCount
|
|
884
|
+
});
|
|
885
|
+
const effectivePolicy = resolvedPolicy.effectivePolicy;
|
|
886
|
+
if (effectivePolicy === null) {
|
|
887
|
+
return {
|
|
888
|
+
status,
|
|
889
|
+
report
|
|
890
|
+
};
|
|
891
|
+
}
|
|
892
|
+
const nextStatusPolicy = resolvedPolicy.statusPolicy;
|
|
893
|
+
const nextReportPolicy = report === null
|
|
894
|
+
? null
|
|
895
|
+
: resolvedPolicy.reportPolicy;
|
|
896
|
+
return {
|
|
897
|
+
status: nextStatusPolicy === status.attachment.policyMode
|
|
898
|
+
? status
|
|
899
|
+
: {
|
|
900
|
+
...status,
|
|
901
|
+
attachment: {
|
|
902
|
+
...status.attachment,
|
|
903
|
+
policyMode: nextStatusPolicy
|
|
904
|
+
}
|
|
905
|
+
},
|
|
906
|
+
report: report === null || nextReportPolicy === report.manyProfile.declaredAttachmentPolicy
|
|
907
|
+
? report
|
|
908
|
+
: {
|
|
909
|
+
...report,
|
|
910
|
+
manyProfile: {
|
|
911
|
+
...report.manyProfile,
|
|
912
|
+
declaredAttachmentPolicy: nextReportPolicy
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
};
|
|
916
|
+
}
|
|
864
917
|
function runOllamaProbe(args, baseUrl) {
|
|
865
918
|
try {
|
|
866
919
|
execFileSync("ollama", [...args], {
|
|
@@ -901,11 +954,14 @@ function summarizeStatusEmbeddings(report, providerConfig) {
|
|
|
901
954
|
requireActivationReady: true
|
|
902
955
|
});
|
|
903
956
|
if (activePack !== null) {
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
957
|
+
const summary = summarizePackVectorEmbeddingState(activePack.vectors);
|
|
958
|
+
totalEntryCount = summary.vectorEntryCount;
|
|
959
|
+
embeddedEntryCount = summary.numericEmbeddingEntryCount;
|
|
960
|
+
models = summary.embeddingModels;
|
|
961
|
+
liveState = embeddedEntryCount === null ? "unknown" : embeddedEntryCount > 0 ? "yes" : "no";
|
|
962
|
+
liveDetail = embeddedEntryCount === null || totalEntryCount === null
|
|
963
|
+
? "active pack vector entries were unreadable during embedding inspection"
|
|
964
|
+
: `active pack stores ${embeddedEntryCount}/${totalEntryCount} numeric embeddings`;
|
|
909
965
|
}
|
|
910
966
|
}
|
|
911
967
|
catch (error) {
|
|
@@ -1257,6 +1313,10 @@ function summarizeDisplayedStatus(status, installHook) {
|
|
|
1257
1313
|
? "fail"
|
|
1258
1314
|
: status.brainStatus.status;
|
|
1259
1315
|
}
|
|
1316
|
+
function formatTracedLearningSurface(surface) {
|
|
1317
|
+
const detail = surface.error === null ? surface.detail : `${surface.detail}: ${surface.error}`;
|
|
1318
|
+
return `present=${yesNo(surface.present)} updated=${surface.updatedAt ?? "none"} routes=${surface.routeTraceCount} supervision=${surface.supervisionCount} updates=${surface.routerUpdateCount} teacher=${surface.teacherArtifactCount} pg=${surface.pgVersionUsed ?? "none"} pack=${surface.materializedPackId ?? "none"} detail=${detail}`;
|
|
1319
|
+
}
|
|
1260
1320
|
function buildCompactStatusHeader(status, report, options) {
|
|
1261
1321
|
const installHook = summarizeStatusInstallHook(options.openclawHome);
|
|
1262
1322
|
const hookLoad = summarizeStatusHookLoad(installHook, status);
|
|
@@ -1272,6 +1332,7 @@ function buildCompactStatusHeader(status, report, options) {
|
|
|
1272
1332
|
openclawHome: options.openclawHome,
|
|
1273
1333
|
status
|
|
1274
1334
|
});
|
|
1335
|
+
const tracedLearning = options.tracedLearning ?? buildTracedLearningStatusSurface(status.host.activationRoot);
|
|
1275
1336
|
return [
|
|
1276
1337
|
`lifecycle attach=${status.attachment.state} learner=${yesNo(status.passiveLearning.learnerRunning)} watch=${summarizeStatusWatchState(status)} export=${status.passiveLearning.exportState} promote=${summarizeStatusPromotionState(status)} serve=${summarizeStatusServeReality(status)}`,
|
|
1277
1338
|
`hook install=${hookLoad.installState} loadability=${hookLoad.loadability} loadProof=${hookLoad.loadProof} layout=${status.hook.installLayout ?? "unverified"} additional=${status.hook.additionalInstallCount ?? 0} detail=${hookLoad.detail}`,
|
|
@@ -1286,6 +1347,7 @@ function buildCompactStatusHeader(status, report, options) {
|
|
|
1286
1347
|
`teacher model=${teacher.model} enabled=${yesNo(teacher.enabled)} healthy=${yesNo(teacher.healthy)} stale=${yesNo(teacher.stale)} idle=${yesNo(teacher.idle)} cycle=${teacher.latestCycle} why=${teacher.detail}`,
|
|
1287
1348
|
`embedder model=${embedder.model} provisioned=${yesNo(embedder.provisioned)} live=${yesNo(embedder.live)} why=${embedder.detail}`,
|
|
1288
1349
|
`routeFn available=${yesNo(routeFn.available)} freshness=${routeFn.freshness} trained=${routeFn.trainedAt ?? "none"} updated=${routeFn.updatedAt ?? "none"} used=${routeFn.usedAt ?? "none"} why=${routeFn.detail}`,
|
|
1350
|
+
`traced ${formatTracedLearningSurface(tracedLearning)}`,
|
|
1289
1351
|
`embeddings provider=${embeddings.provider} provisioned=${embeddings.provisionedState} live=${embeddings.liveState} stored=${embeddings.embeddedEntryCount ?? "none"}/${embeddings.totalEntryCount ?? "none"} models=${liveModels}`,
|
|
1290
1352
|
`localLLM detected=${yesNo(localLlm.detected)} enabled=${yesNo(localLlm.enabled)} provider=${localLlm.provider} model=${localLlm.model}`,
|
|
1291
1353
|
`alerts service_risk=${formatStatusAlertLine(alerts.serviceRisk)} degraded_brain=${formatStatusAlertLine(alerts.degradedBrain)} cosmetic_noise=${formatStatusAlertLine(alerts.cosmeticNoise)}`
|
|
@@ -1302,6 +1364,7 @@ function formatCurrentProfileStatusSummary(status, report, targetInspection, opt
|
|
|
1302
1364
|
openclawHome: options.openclawHome,
|
|
1303
1365
|
status
|
|
1304
1366
|
});
|
|
1367
|
+
const tracedLearning = options.tracedLearning ?? buildTracedLearningStatusSurface(status.host.activationRoot);
|
|
1305
1368
|
const profileIdSuffix = status.profile.profileId === null ? "" : ` id=${status.profile.profileId}`;
|
|
1306
1369
|
const targetLine = targetInspection === null
|
|
1307
1370
|
? `target activation=${status.host.activationRoot} source=activation_root_only`
|
|
@@ -1335,6 +1398,7 @@ function formatCurrentProfileStatusSummary(status, report, targetInspection, opt
|
|
|
1335
1398
|
`graph source=${report.graph.runtimePlasticitySource ?? "none"} blocks=${report.graph.blockCount ?? "none"} strongest=${report.graph.strongestBlockId ?? "none"} ops=${formatStructuralOps(report)} latest=${report.graph.latestMaterialization.packId ?? "none"} latestChanged=${yesNo(report.graph.latestMaterialization.changed)} connect=${formatGraphConnectDiagnostics(report.graph.latestMaterialization.connectDiagnostics ?? report.graph.connectDiagnostics)} summary=${formatGraphSummary(report)}`,
|
|
1336
1399
|
`path ${formatLearningPathSummary(report.learningPath)}`,
|
|
1337
1400
|
`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}`,
|
|
1401
|
+
`traced ${formatTracedLearningSurface(tracedLearning)}`,
|
|
1338
1402
|
`teacherProof ${formatTeacherLoopSummary(report)}`,
|
|
1339
1403
|
`watch cadence=${report.teacherLoop.learningCadence} scan=${report.teacherLoop.scanPolicy} heartbeat=${report.teacherLoop.lastHeartbeatAt ?? "none"} interval=${report.teacherLoop.pollIntervalSeconds ?? "none"} replayed=${report.teacherLoop.replayedBundleCount ?? "none"}/${report.teacherLoop.replayedEventCount ?? "none"} exported=${report.teacherLoop.exportedBundleCount ?? "none"}/${report.teacherLoop.exportedEventCount ?? "none"} tail=${report.teacherLoop.sessionTailSessionsTracked ?? "none"}/${report.teacherLoop.sessionTailBridgedEventCount ?? "none"} tailState=${report.teacherLoop.localSessionTailNoopReason ?? "none"} lastJob=${report.teacherLoop.lastAppliedMaterializationJobId ?? "none"} lastPack=${report.teacherLoop.lastMaterializedPackId ?? "none"}`,
|
|
1340
1404
|
`embeddings provider=${embeddings.provider} provisioned=${embeddings.provisionedState} live=${embeddings.liveState} stored=${embeddings.embeddedEntryCount ?? "none"}/${embeddings.totalEntryCount ?? "none"} models=${liveModels}`,
|
|
@@ -3294,6 +3358,13 @@ function runProfileHookAttachCommand(parsed) {
|
|
|
3294
3358
|
steps.push(pluginConfigRepair.detail);
|
|
3295
3359
|
const learnerService = ensureLifecycleLearnerService(parsed.activationRoot);
|
|
3296
3360
|
steps.push(learnerService.detail);
|
|
3361
|
+
const attachmentPolicyDeclaration = writeAttachmentPolicyDeclaration({
|
|
3362
|
+
activationRoot: parsed.activationRoot,
|
|
3363
|
+
policy: parsed.shared ? "shared" : "dedicated",
|
|
3364
|
+
source: parsed.command,
|
|
3365
|
+
openclawHome: parsed.openclawHome
|
|
3366
|
+
});
|
|
3367
|
+
steps.push(`Recorded attachment policy declaration: ${attachmentPolicyDeclaration.declaration.policy} at ${shortenPath(attachmentPolicyDeclaration.path)}`);
|
|
3297
3368
|
const brainFeedback = buildInstallBrainFeedbackSummary({
|
|
3298
3369
|
parsed,
|
|
3299
3370
|
targetInspection,
|
|
@@ -4244,6 +4315,31 @@ function runLearnCommand(parsed) {
|
|
|
4244
4315
|
lastAppliedMaterializationJobId: lastMaterialization?.jobId ?? null
|
|
4245
4316
|
}
|
|
4246
4317
|
});
|
|
4318
|
+
const tracedLearningBridge = mergeTracedLearningBridgePayload({
|
|
4319
|
+
updatedAt: now,
|
|
4320
|
+
routeTraceCount: lastMaterialization?.candidate.summary.learnedRouter.routeTraceCount ?? serveTimeLearning.decisionLogCount,
|
|
4321
|
+
supervisionCount,
|
|
4322
|
+
routerUpdateCount,
|
|
4323
|
+
teacherArtifactCount: teacherArtifacts.length,
|
|
4324
|
+
pgVersionRequested: learnPathReport.pgVersionRequested,
|
|
4325
|
+
pgVersionUsed: learnPathReport.pgVersionUsed,
|
|
4326
|
+
decisionLogCount: learnPathReport.decisionLogCount,
|
|
4327
|
+
fallbackReason: learnPathReport.fallbackReason,
|
|
4328
|
+
routerNoOpReason,
|
|
4329
|
+
materializedPackId,
|
|
4330
|
+
promoted,
|
|
4331
|
+
baselinePersisted,
|
|
4332
|
+
source: {
|
|
4333
|
+
command: "learn",
|
|
4334
|
+
exportDigest: learningExport.provenance.exportDigest,
|
|
4335
|
+
teacherSnapshotPath
|
|
4336
|
+
}
|
|
4337
|
+
}, loadBrainStoreTracedLearningBridge());
|
|
4338
|
+
const surfacedSupervisionCount = tracedLearningBridge.supervisionCount;
|
|
4339
|
+
const surfacedRouterUpdateCount = tracedLearningBridge.routerUpdateCount;
|
|
4340
|
+
const surfacedRouterNoOpReason = tracedLearningBridge.routerNoOpReason;
|
|
4341
|
+
persistBrainStoreTracedLearningBridge(tracedLearningBridge);
|
|
4342
|
+
writeTracedLearningBridge(activationRoot, tracedLearningBridge);
|
|
4247
4343
|
const summaryMessage = materializedPackId === null
|
|
4248
4344
|
? `Scanned ${totalSessions} sessions, ${totalEvents} new events, no candidate materialized, no promotion.`
|
|
4249
4345
|
: `Scanned ${totalSessions} sessions, ${totalEvents} new events, materialized ${materializedPackId}, promoted.${connectSummary}`;
|
|
@@ -4266,9 +4362,9 @@ function runLearnCommand(parsed) {
|
|
|
4266
4362
|
teacherBudget: learnerResult.state.sparseFeedback.teacherBudget,
|
|
4267
4363
|
eligibleFeedbackCount: learnerResult.state.sparseFeedback.eligibleFeedbackCount,
|
|
4268
4364
|
budgetedOutFeedbackCount: learnerResult.state.sparseFeedback.budgetedOutFeedbackCount,
|
|
4269
|
-
supervisionCount,
|
|
4270
|
-
routerUpdateCount,
|
|
4271
|
-
routerNoOpReason,
|
|
4365
|
+
supervisionCount: surfacedSupervisionCount,
|
|
4366
|
+
routerUpdateCount: surfacedRouterUpdateCount,
|
|
4367
|
+
routerNoOpReason: surfacedRouterNoOpReason,
|
|
4272
4368
|
pending: plan.pending,
|
|
4273
4369
|
learnedRange: plan.learnedRange
|
|
4274
4370
|
},
|
|
@@ -4288,8 +4384,8 @@ function runLearnCommand(parsed) {
|
|
|
4288
4384
|
}
|
|
4289
4385
|
else {
|
|
4290
4386
|
const text = materializedPackId === null
|
|
4291
|
-
? `Scanned ${totalSessions} sessions, ${totalEvents} new events, no promotion. cycles=${learnerResult.cycles.length} stop=${learnerResult.stopReason} supervision=${
|
|
4292
|
-
: `Scanned ${totalSessions} sessions, ${totalEvents} new events, materialized ${materializedPackId}, promoted.${connectSummary} cycles=${learnerResult.cycles.length} supervision=${
|
|
4387
|
+
? `Scanned ${totalSessions} sessions, ${totalEvents} new events, no promotion. cycles=${learnerResult.cycles.length} stop=${learnerResult.stopReason} supervision=${surfacedSupervisionCount}.`
|
|
4388
|
+
: `Scanned ${totalSessions} sessions, ${totalEvents} new events, materialized ${materializedPackId}, promoted.${connectSummary} cycles=${learnerResult.cycles.length} supervision=${surfacedSupervisionCount}.`;
|
|
4293
4389
|
console.log(text);
|
|
4294
4390
|
console.log(`labels: source=${labelFlow.source} human=${labelFlow.humanLabelCount ?? "none"} self=${labelFlow.selfLabelCount ?? "none"} implicitPositive=${labelFlow.implicitPositiveCount ?? "none"} teacherArtifacts=${labelFlow.asyncTeacherArtifactCount ?? "none"}`);
|
|
4295
4391
|
console.log(`path: source=${learningPath.source} pg=${learningPath.policyGradientVersion} method=${learningPath.policyGradientMethod ?? "none"} target=${learningPath.targetConstruction ?? "none"} connect=${learningPath.connectOpsFired ?? "none"} trajectories=${learningPath.reconstructedTrajectoryCount ?? "none"}`);
|
|
@@ -4452,19 +4548,7 @@ function exportLocalSessionTailChangesToScanRoot(input) {
|
|
|
4452
4548
|
};
|
|
4453
4549
|
}
|
|
4454
4550
|
function summarizeVectorEmbeddingState(vectors) {
|
|
4455
|
-
|
|
4456
|
-
return {
|
|
4457
|
-
vectorEntryCount: null,
|
|
4458
|
-
numericEmbeddingEntryCount: null,
|
|
4459
|
-
embeddingModels: []
|
|
4460
|
-
};
|
|
4461
|
-
}
|
|
4462
|
-
const embeddingModels = [...new Set(vectors.entries.flatMap((entry) => (entry.embedding === undefined ? [] : [entry.embedding.model])))].sort((left, right) => left.localeCompare(right));
|
|
4463
|
-
return {
|
|
4464
|
-
vectorEntryCount: vectors.entries.length,
|
|
4465
|
-
numericEmbeddingEntryCount: vectors.entries.filter((entry) => entry.embedding !== undefined).length,
|
|
4466
|
-
embeddingModels
|
|
4467
|
-
};
|
|
4551
|
+
return summarizePackVectorEmbeddingState(vectors);
|
|
4468
4552
|
}
|
|
4469
4553
|
function buildWatchEmbedTracePoint(input) {
|
|
4470
4554
|
const summary = summarizeVectorEmbeddingState(input.vectors);
|
|
@@ -5546,25 +5630,32 @@ export function runOperatorCli(argv = process.argv.slice(2)) {
|
|
|
5546
5630
|
teacherSnapshotPath: resolveOperatorTeacherSnapshotPath(activationRoot, statusOrRollback.input.teacherSnapshotPath)
|
|
5547
5631
|
};
|
|
5548
5632
|
const status = describeCurrentProfileBrainStatus(operatorInput);
|
|
5633
|
+
const tracedLearning = buildTracedLearningStatusSurface(activationRoot);
|
|
5634
|
+
const normalizedStatusAndReport = applyAttachmentPolicyTruth(status, statusOrRollback.json ? null : buildOperatorSurfaceReport(operatorInput));
|
|
5549
5635
|
if (statusOrRollback.json) {
|
|
5550
|
-
console.log(JSON.stringify(
|
|
5636
|
+
console.log(JSON.stringify({
|
|
5637
|
+
...normalizedStatusAndReport.status,
|
|
5638
|
+
tracedLearning
|
|
5639
|
+
}, null, 2));
|
|
5551
5640
|
}
|
|
5552
5641
|
else {
|
|
5553
|
-
const report =
|
|
5642
|
+
const report = normalizedStatusAndReport.report;
|
|
5554
5643
|
const providerConfig = readOpenClawBrainProviderConfigFromSources({
|
|
5555
5644
|
env: process.env,
|
|
5556
5645
|
activationRoot
|
|
5557
5646
|
});
|
|
5558
5647
|
if (statusOrRollback.detailed) {
|
|
5559
|
-
console.log(formatCurrentProfileStatusSummary(status, report, targetInspection, {
|
|
5648
|
+
console.log(formatCurrentProfileStatusSummary(normalizedStatusAndReport.status, report, targetInspection, {
|
|
5560
5649
|
openclawHome: statusOrRollback.openclawHome,
|
|
5561
|
-
providerConfig
|
|
5650
|
+
providerConfig,
|
|
5651
|
+
tracedLearning
|
|
5562
5652
|
}));
|
|
5563
5653
|
}
|
|
5564
5654
|
else {
|
|
5565
|
-
console.log(formatHumanFriendlyStatus(status, report, targetInspection, {
|
|
5655
|
+
console.log(formatHumanFriendlyStatus(normalizedStatusAndReport.status, report, targetInspection, {
|
|
5566
5656
|
openclawHome: statusOrRollback.openclawHome,
|
|
5567
|
-
providerConfig
|
|
5657
|
+
providerConfig,
|
|
5658
|
+
tracedLearning
|
|
5568
5659
|
}));
|
|
5569
5660
|
}
|
|
5570
5661
|
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
function isNumericArray(value) {
|
|
2
|
+
return Array.isArray(value) &&
|
|
3
|
+
value.length > 0 &&
|
|
4
|
+
value.every((entry) => typeof entry === "number" && Number.isFinite(entry));
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
function isNumericTypedArray(value) {
|
|
8
|
+
return ArrayBuffer.isView(value) &&
|
|
9
|
+
!(value instanceof DataView) &&
|
|
10
|
+
typeof value.length === "number" &&
|
|
11
|
+
value.length > 0 &&
|
|
12
|
+
Array.from(value).every((entry) => typeof entry === "number" && Number.isFinite(entry));
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function hasNumericValues(value) {
|
|
16
|
+
return isNumericArray(value) || isNumericTypedArray(value);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function normalizeOptionalString(value) {
|
|
20
|
+
if (typeof value !== "string") {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
const trimmed = value.trim();
|
|
24
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function extractNumericEmbeddingShape(entry) {
|
|
28
|
+
const embedding = entry?.embedding;
|
|
29
|
+
const candidates = [
|
|
30
|
+
embedding,
|
|
31
|
+
embedding?.values,
|
|
32
|
+
embedding?.vector,
|
|
33
|
+
embedding?.embedding,
|
|
34
|
+
entry?.values,
|
|
35
|
+
entry?.vector,
|
|
36
|
+
entry?.numericEmbedding,
|
|
37
|
+
entry?.numericEmbeddingValues,
|
|
38
|
+
];
|
|
39
|
+
return candidates.find((candidate) => hasNumericValues(candidate)) ?? null;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function extractEmbeddingModel(entry) {
|
|
43
|
+
const candidates = [
|
|
44
|
+
entry?.embedding?.model,
|
|
45
|
+
entry?.embeddingModel,
|
|
46
|
+
entry?.model,
|
|
47
|
+
];
|
|
48
|
+
for (const candidate of candidates) {
|
|
49
|
+
const normalized = normalizeOptionalString(candidate);
|
|
50
|
+
if (normalized !== null) {
|
|
51
|
+
return normalized;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function summarizePackVectorEmbeddingState(vectors) {
|
|
58
|
+
if (!vectors || !Array.isArray(vectors.entries)) {
|
|
59
|
+
return {
|
|
60
|
+
vectorEntryCount: null,
|
|
61
|
+
numericEmbeddingEntryCount: null,
|
|
62
|
+
embeddingModels: []
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
const embeddingModels = [...new Set(vectors.entries
|
|
66
|
+
.flatMap((entry) => {
|
|
67
|
+
if (extractNumericEmbeddingShape(entry) === null) {
|
|
68
|
+
return [];
|
|
69
|
+
}
|
|
70
|
+
const model = extractEmbeddingModel(entry);
|
|
71
|
+
return model === null ? [] : [model];
|
|
72
|
+
}))].sort((left, right) => left.localeCompare(right));
|
|
73
|
+
return {
|
|
74
|
+
vectorEntryCount: vectors.entries.length,
|
|
75
|
+
numericEmbeddingEntryCount: vectors.entries.filter((entry) => extractNumericEmbeddingShape(entry) !== null).length,
|
|
76
|
+
embeddingModels
|
|
77
|
+
};
|
|
78
|
+
}
|
|
@@ -0,0 +1,554 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
|
|
5
|
+
const TRACED_LEARNING_BRIDGE_CONTRACT = "openclawbrain.traced-learning-bridge.v1";
|
|
6
|
+
const TRACED_LEARNING_BRIDGE_FILENAME = "traced-learning-state.json";
|
|
7
|
+
// Canonical split-package learn/status summary persisted under brain_training_state.
|
|
8
|
+
const TRACED_LEARNING_STATUS_SURFACE_STATE_KEY = "traced_learning_status_surface_json";
|
|
9
|
+
const TRACED_LEARNING_STATUS_SURFACE_CONTRACT = "openclawbrain.traced-learning-status-surface.v1";
|
|
10
|
+
const TRACED_LEARNING_STATUS_SURFACE_BRIDGE = "brain_store_traced_learning_status_surface";
|
|
11
|
+
|
|
12
|
+
function normalizeCount(value) {
|
|
13
|
+
return Number.isFinite(value) && value >= 0 ? Math.trunc(value) : 0;
|
|
14
|
+
}
|
|
15
|
+
function normalizeOptionalString(value) {
|
|
16
|
+
return typeof value === "string" && value.trim().length > 0 ? value : null;
|
|
17
|
+
}
|
|
18
|
+
function normalizeSource(value) {
|
|
19
|
+
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
20
|
+
}
|
|
21
|
+
function normalizeBridgePayload(payload) {
|
|
22
|
+
if (payload === null || typeof payload !== "object" || Array.isArray(payload)) {
|
|
23
|
+
throw new Error("expected traced-learning bridge payload object");
|
|
24
|
+
}
|
|
25
|
+
return {
|
|
26
|
+
contract: TRACED_LEARNING_BRIDGE_CONTRACT,
|
|
27
|
+
updatedAt: normalizeOptionalString(payload.updatedAt) ?? new Date().toISOString(),
|
|
28
|
+
routeTraceCount: normalizeCount(payload.routeTraceCount),
|
|
29
|
+
supervisionCount: normalizeCount(payload.supervisionCount),
|
|
30
|
+
routerUpdateCount: normalizeCount(payload.routerUpdateCount),
|
|
31
|
+
teacherArtifactCount: normalizeCount(payload.teacherArtifactCount),
|
|
32
|
+
pgVersionRequested: normalizeOptionalString(payload.pgVersionRequested),
|
|
33
|
+
pgVersionUsed: normalizeOptionalString(payload.pgVersionUsed),
|
|
34
|
+
decisionLogCount: normalizeCount(payload.decisionLogCount),
|
|
35
|
+
fallbackReason: normalizeOptionalString(payload.fallbackReason),
|
|
36
|
+
routerNoOpReason: normalizeOptionalString(payload.routerNoOpReason),
|
|
37
|
+
materializedPackId: normalizeOptionalString(payload.materializedPackId),
|
|
38
|
+
promoted: payload.promoted === true,
|
|
39
|
+
baselinePersisted: payload.baselinePersisted === true,
|
|
40
|
+
source: normalizeSource(payload.source)
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
function normalizePersistedStatusSurface(payload) {
|
|
44
|
+
if (payload === null || typeof payload !== "object" || Array.isArray(payload)) {
|
|
45
|
+
throw new Error("expected traced-learning status surface payload object");
|
|
46
|
+
}
|
|
47
|
+
const source = normalizeSource(payload.source);
|
|
48
|
+
if (source === null) {
|
|
49
|
+
throw new Error("expected traced-learning status surface source");
|
|
50
|
+
}
|
|
51
|
+
return {
|
|
52
|
+
contract: TRACED_LEARNING_STATUS_SURFACE_CONTRACT,
|
|
53
|
+
updatedAt: normalizeOptionalString(payload.updatedAt) ?? new Date().toISOString(),
|
|
54
|
+
routeTraceCount: normalizeCount(payload.routeTraceCount),
|
|
55
|
+
supervisionCount: normalizeCount(payload.supervisionCount),
|
|
56
|
+
routerUpdateCount: normalizeCount(payload.routerUpdateCount),
|
|
57
|
+
teacherArtifactCount: normalizeCount(payload.teacherArtifactCount),
|
|
58
|
+
pgVersionRequested: normalizeOptionalString(payload.pgVersionRequested),
|
|
59
|
+
pgVersionUsed: normalizeOptionalString(payload.pgVersionUsed),
|
|
60
|
+
decisionLogCount: normalizeCount(payload.decisionLogCount),
|
|
61
|
+
fallbackReason: normalizeOptionalString(payload.fallbackReason),
|
|
62
|
+
routerNoOpReason: normalizeOptionalString(payload.routerNoOpReason),
|
|
63
|
+
materializedPackId: normalizeOptionalString(payload.materializedPackId),
|
|
64
|
+
promoted: payload.promoted === true,
|
|
65
|
+
baselinePersisted: payload.baselinePersisted === true,
|
|
66
|
+
source
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
function defaultSurface(pathname, detail, error = null) {
|
|
70
|
+
return {
|
|
71
|
+
path: pathname,
|
|
72
|
+
present: false,
|
|
73
|
+
updatedAt: null,
|
|
74
|
+
routeTraceCount: 0,
|
|
75
|
+
supervisionCount: 0,
|
|
76
|
+
routerUpdateCount: 0,
|
|
77
|
+
teacherArtifactCount: 0,
|
|
78
|
+
pgVersionRequested: null,
|
|
79
|
+
pgVersionUsed: null,
|
|
80
|
+
decisionLogCount: 0,
|
|
81
|
+
materializedPackId: null,
|
|
82
|
+
promoted: false,
|
|
83
|
+
baselinePersisted: false,
|
|
84
|
+
source: null,
|
|
85
|
+
detail,
|
|
86
|
+
error
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
function resolveBrainRoot(env = process.env) {
|
|
90
|
+
const explicit = normalizeOptionalString(env.OPENCLAWBRAIN_ROOT);
|
|
91
|
+
if (explicit !== null) {
|
|
92
|
+
return path.resolve(explicit);
|
|
93
|
+
}
|
|
94
|
+
const lcmDatabasePath = normalizeOptionalString(env.LCM_DATABASE_PATH);
|
|
95
|
+
if (lcmDatabasePath !== null) {
|
|
96
|
+
return path.join(path.dirname(path.resolve(lcmDatabasePath)), "openclawbrain");
|
|
97
|
+
}
|
|
98
|
+
return path.join(homedir(), ".openclaw", "openclawbrain");
|
|
99
|
+
}
|
|
100
|
+
function loadTrainingStateValue(db, key) {
|
|
101
|
+
const row = db.prepare(`SELECT value FROM brain_training_state WHERE key = ?`).get(key);
|
|
102
|
+
return row !== undefined && typeof row.value === "string" ? row.value : null;
|
|
103
|
+
}
|
|
104
|
+
function loadTrainingStateJson(db, key) {
|
|
105
|
+
const raw = loadTrainingStateValue(db, key);
|
|
106
|
+
if (typeof raw !== "string") {
|
|
107
|
+
return {
|
|
108
|
+
value: null,
|
|
109
|
+
error: null
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
const trimmed = raw.trim();
|
|
113
|
+
if (trimmed.length === 0) {
|
|
114
|
+
return {
|
|
115
|
+
value: null,
|
|
116
|
+
error: null
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
try {
|
|
120
|
+
return {
|
|
121
|
+
value: JSON.parse(trimmed),
|
|
122
|
+
error: null
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
catch (error) {
|
|
126
|
+
return {
|
|
127
|
+
value: null,
|
|
128
|
+
error: error instanceof Error ? error.message : String(error)
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
function writeTrainingStateJson(db, key, value) {
|
|
133
|
+
db.prepare(`INSERT OR REPLACE INTO brain_training_state (key, value) VALUES (?, ?)`).run(key, JSON.stringify(value));
|
|
134
|
+
}
|
|
135
|
+
function countRows(db, tableName) {
|
|
136
|
+
const row = db.prepare(`SELECT COUNT(*) as count FROM ${tableName}`).get();
|
|
137
|
+
return normalizeCount(row?.count);
|
|
138
|
+
}
|
|
139
|
+
function toIsoTimestamp(value) {
|
|
140
|
+
return Number.isFinite(value) && value > 0 ? new Date(value).toISOString() : null;
|
|
141
|
+
}
|
|
142
|
+
function buildPersistedStatusSurfaceBridge(summary, context) {
|
|
143
|
+
return normalizeBridgePayload({
|
|
144
|
+
updatedAt: summary.updatedAt,
|
|
145
|
+
routeTraceCount: summary.routeTraceCount,
|
|
146
|
+
supervisionCount: summary.supervisionCount,
|
|
147
|
+
routerUpdateCount: summary.routerUpdateCount,
|
|
148
|
+
teacherArtifactCount: summary.teacherArtifactCount,
|
|
149
|
+
pgVersionRequested: summary.pgVersionRequested,
|
|
150
|
+
pgVersionUsed: summary.pgVersionUsed,
|
|
151
|
+
decisionLogCount: summary.decisionLogCount,
|
|
152
|
+
fallbackReason: summary.fallbackReason,
|
|
153
|
+
routerNoOpReason: summary.routerNoOpReason,
|
|
154
|
+
materializedPackId: summary.materializedPackId,
|
|
155
|
+
promoted: summary.promoted,
|
|
156
|
+
baselinePersisted: summary.baselinePersisted,
|
|
157
|
+
source: {
|
|
158
|
+
command: "brain-store",
|
|
159
|
+
bridge: TRACED_LEARNING_STATUS_SURFACE_BRIDGE,
|
|
160
|
+
brainRoot: context.brainRoot,
|
|
161
|
+
stateDbPath: context.dbPath,
|
|
162
|
+
persistedKey: TRACED_LEARNING_STATUS_SURFACE_STATE_KEY,
|
|
163
|
+
surfacedFrom: summary.source
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
function loadPersistedStatusSurface(db, context) {
|
|
168
|
+
const loaded = loadTrainingStateJson(db, TRACED_LEARNING_STATUS_SURFACE_STATE_KEY);
|
|
169
|
+
if (loaded.value === null) {
|
|
170
|
+
return {
|
|
171
|
+
bridge: null,
|
|
172
|
+
error: loaded.error
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
try {
|
|
176
|
+
if (normalizeOptionalString(loaded.value.contract) !== TRACED_LEARNING_STATUS_SURFACE_CONTRACT) {
|
|
177
|
+
throw new Error("unexpected traced-learning status surface contract");
|
|
178
|
+
}
|
|
179
|
+
return {
|
|
180
|
+
bridge: buildPersistedStatusSurfaceBridge(normalizePersistedStatusSurface(loaded.value), context),
|
|
181
|
+
error: null
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
catch (error) {
|
|
185
|
+
return {
|
|
186
|
+
bridge: null,
|
|
187
|
+
error: error instanceof Error ? error.message : String(error)
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
function buildDerivedBrainStoreBridge(db, context) {
|
|
192
|
+
const routeTraceCount = countRows(db, "brain_traces");
|
|
193
|
+
const supervisionCount = countRows(db, "brain_trace_supervision");
|
|
194
|
+
const candidateUpdateRaw = loadTrainingStateValue(db, "last_pg_candidate_update_json");
|
|
195
|
+
const candidatePackVersionRaw = loadTrainingStateValue(db, "last_pg_candidate_pack_version");
|
|
196
|
+
const candidateUpdate = candidateUpdateRaw === null || candidateUpdateRaw.trim().length === 0
|
|
197
|
+
? null
|
|
198
|
+
: JSON.parse(candidateUpdateRaw);
|
|
199
|
+
const candidatePackVersion = Number.parseInt(candidatePackVersionRaw ?? "", 10);
|
|
200
|
+
return normalizeBridgePayload({
|
|
201
|
+
updatedAt: toIsoTimestamp(candidateUpdate?.generatedAt),
|
|
202
|
+
routeTraceCount,
|
|
203
|
+
supervisionCount,
|
|
204
|
+
routerUpdateCount: candidateUpdate?.routeUpdateCount,
|
|
205
|
+
teacherArtifactCount: candidateUpdate?.teacherLabelCount,
|
|
206
|
+
pgVersionRequested: null,
|
|
207
|
+
pgVersionUsed: null,
|
|
208
|
+
decisionLogCount: 0,
|
|
209
|
+
fallbackReason: null,
|
|
210
|
+
routerNoOpReason: null,
|
|
211
|
+
materializedPackId: null,
|
|
212
|
+
promoted: false,
|
|
213
|
+
baselinePersisted: false,
|
|
214
|
+
source: {
|
|
215
|
+
command: "brain-store",
|
|
216
|
+
bridge: "brain_store_state",
|
|
217
|
+
brainRoot: context.brainRoot,
|
|
218
|
+
stateDbPath: context.dbPath,
|
|
219
|
+
candidatePackVersion: Number.isFinite(candidatePackVersion) ? candidatePackVersion : null,
|
|
220
|
+
candidateUpdateCount: normalizeCount(candidateUpdate?.updateCount)
|
|
221
|
+
}
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
function hasMeaningfulTracedLearningSignal(bridge) {
|
|
225
|
+
return bridge.routeTraceCount > 0 ||
|
|
226
|
+
bridge.supervisionCount > 0 ||
|
|
227
|
+
bridge.routerUpdateCount > 0 ||
|
|
228
|
+
bridge.teacherArtifactCount > 0 ||
|
|
229
|
+
bridge.decisionLogCount > 0 ||
|
|
230
|
+
bridge.materializedPackId !== null ||
|
|
231
|
+
bridge.promoted ||
|
|
232
|
+
bridge.baselinePersisted ||
|
|
233
|
+
bridge.pgVersionRequested !== null ||
|
|
234
|
+
bridge.pgVersionUsed !== null ||
|
|
235
|
+
bridge.fallbackReason !== null ||
|
|
236
|
+
bridge.routerNoOpReason !== null ||
|
|
237
|
+
Number.isFinite(bridge.source?.candidatePackVersion) ||
|
|
238
|
+
normalizeCount(bridge.source?.candidateUpdateCount) > 0;
|
|
239
|
+
}
|
|
240
|
+
export function resolveTracedLearningBridgePath(activationRoot) {
|
|
241
|
+
return path.join(path.resolve(activationRoot), "watch", TRACED_LEARNING_BRIDGE_FILENAME);
|
|
242
|
+
}
|
|
243
|
+
export function writeTracedLearningBridge(activationRoot, payload) {
|
|
244
|
+
const bridgePath = resolveTracedLearningBridgePath(activationRoot);
|
|
245
|
+
const bridge = normalizeBridgePayload(payload);
|
|
246
|
+
mkdirSync(path.dirname(bridgePath), { recursive: true });
|
|
247
|
+
writeFileSync(bridgePath, `${JSON.stringify(bridge, null, 2)}\n`, "utf8");
|
|
248
|
+
return bridgePath;
|
|
249
|
+
}
|
|
250
|
+
export function loadTracedLearningBridge(activationRoot) {
|
|
251
|
+
const bridgePath = resolveTracedLearningBridgePath(activationRoot);
|
|
252
|
+
if (!existsSync(bridgePath)) {
|
|
253
|
+
return {
|
|
254
|
+
path: bridgePath,
|
|
255
|
+
bridge: null,
|
|
256
|
+
error: null
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
try {
|
|
260
|
+
const parsed = JSON.parse(readFileSync(bridgePath, "utf8"));
|
|
261
|
+
return {
|
|
262
|
+
path: bridgePath,
|
|
263
|
+
bridge: normalizeBridgePayload(parsed),
|
|
264
|
+
error: null
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
catch (error) {
|
|
268
|
+
return {
|
|
269
|
+
path: bridgePath,
|
|
270
|
+
bridge: null,
|
|
271
|
+
error: error instanceof Error ? error.message : String(error)
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
export function persistBrainStoreTracedLearningBridge(payload, options = {}) {
|
|
276
|
+
const brainRoot = resolveBrainRoot(options.env ?? process.env);
|
|
277
|
+
const dbPath = path.join(brainRoot, "state.db");
|
|
278
|
+
if (!existsSync(dbPath)) {
|
|
279
|
+
return {
|
|
280
|
+
path: dbPath,
|
|
281
|
+
bridge: null,
|
|
282
|
+
persisted: false,
|
|
283
|
+
error: null
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
const sqlite = typeof process.getBuiltinModule === "function"
|
|
287
|
+
? process.getBuiltinModule("node:sqlite")
|
|
288
|
+
: null;
|
|
289
|
+
if (sqlite === null || typeof sqlite.DatabaseSync !== "function") {
|
|
290
|
+
return {
|
|
291
|
+
path: dbPath,
|
|
292
|
+
bridge: null,
|
|
293
|
+
persisted: false,
|
|
294
|
+
error: null
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
let db;
|
|
298
|
+
try {
|
|
299
|
+
db = new sqlite.DatabaseSync(dbPath);
|
|
300
|
+
const summary = normalizePersistedStatusSurface(payload);
|
|
301
|
+
writeTrainingStateJson(db, TRACED_LEARNING_STATUS_SURFACE_STATE_KEY, summary);
|
|
302
|
+
return {
|
|
303
|
+
path: dbPath,
|
|
304
|
+
bridge: buildPersistedStatusSurfaceBridge(summary, {
|
|
305
|
+
brainRoot,
|
|
306
|
+
dbPath
|
|
307
|
+
}),
|
|
308
|
+
persisted: true,
|
|
309
|
+
error: null
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
catch (error) {
|
|
313
|
+
return {
|
|
314
|
+
path: dbPath,
|
|
315
|
+
bridge: null,
|
|
316
|
+
persisted: false,
|
|
317
|
+
error: error instanceof Error ? error.message : String(error)
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
finally {
|
|
321
|
+
if (db && typeof db.close === "function") {
|
|
322
|
+
db.close();
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
export function loadBrainStoreTracedLearningBridge(options = {}) {
|
|
327
|
+
const brainRoot = resolveBrainRoot(options.env ?? process.env);
|
|
328
|
+
const dbPath = path.join(brainRoot, "state.db");
|
|
329
|
+
if (!existsSync(dbPath)) {
|
|
330
|
+
return {
|
|
331
|
+
path: dbPath,
|
|
332
|
+
bridge: null,
|
|
333
|
+
error: null
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
const sqlite = typeof process.getBuiltinModule === "function"
|
|
337
|
+
? process.getBuiltinModule("node:sqlite")
|
|
338
|
+
: null;
|
|
339
|
+
if (sqlite === null || typeof sqlite.DatabaseSync !== "function") {
|
|
340
|
+
return {
|
|
341
|
+
path: dbPath,
|
|
342
|
+
bridge: null,
|
|
343
|
+
error: null
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
let db;
|
|
347
|
+
try {
|
|
348
|
+
db = new sqlite.DatabaseSync(dbPath, { readOnly: true });
|
|
349
|
+
const persisted = loadPersistedStatusSurface(db, {
|
|
350
|
+
brainRoot,
|
|
351
|
+
dbPath
|
|
352
|
+
});
|
|
353
|
+
if (persisted.bridge !== null) {
|
|
354
|
+
return {
|
|
355
|
+
path: dbPath,
|
|
356
|
+
bridge: persisted.bridge,
|
|
357
|
+
error: null
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
const bridge = buildDerivedBrainStoreBridge(db, {
|
|
361
|
+
brainRoot,
|
|
362
|
+
dbPath
|
|
363
|
+
});
|
|
364
|
+
if (!hasMeaningfulTracedLearningSignal(bridge)) {
|
|
365
|
+
return {
|
|
366
|
+
path: dbPath,
|
|
367
|
+
bridge: null,
|
|
368
|
+
error: persisted.error
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
return {
|
|
372
|
+
path: dbPath,
|
|
373
|
+
bridge,
|
|
374
|
+
error: null
|
|
375
|
+
};
|
|
376
|
+
}
|
|
377
|
+
catch (error) {
|
|
378
|
+
return {
|
|
379
|
+
path: dbPath,
|
|
380
|
+
bridge: null,
|
|
381
|
+
error: error instanceof Error ? error.message : String(error)
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
finally {
|
|
385
|
+
if (db && typeof db.close === "function") {
|
|
386
|
+
db.close();
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
function describeBridgeRuntimeState(loaded) {
|
|
391
|
+
return loaded.bridge === null ? (loaded.error === null ? "missing" : "unreadable") : "present";
|
|
392
|
+
}
|
|
393
|
+
function buildStatusSurface(pathname, bridge, options = {}) {
|
|
394
|
+
const detailParts = [
|
|
395
|
+
`source=${bridge.source?.command === undefined ? "learn" : String(bridge.source.command)}`,
|
|
396
|
+
`promoted=${bridge.promoted ? "yes" : "no"}`
|
|
397
|
+
];
|
|
398
|
+
if (typeof bridge.source?.bridge === "string") {
|
|
399
|
+
detailParts.push(`bridge=${bridge.source.bridge}`);
|
|
400
|
+
}
|
|
401
|
+
if (options.runtimeState !== undefined) {
|
|
402
|
+
detailParts.push(`runtime=${options.runtimeState}`);
|
|
403
|
+
}
|
|
404
|
+
if (bridge.fallbackReason !== null) {
|
|
405
|
+
detailParts.push(`fallback=${bridge.fallbackReason}`);
|
|
406
|
+
}
|
|
407
|
+
if (bridge.routerNoOpReason !== null) {
|
|
408
|
+
detailParts.push(`noOp=${bridge.routerNoOpReason}`);
|
|
409
|
+
}
|
|
410
|
+
return {
|
|
411
|
+
path: pathname,
|
|
412
|
+
present: true,
|
|
413
|
+
updatedAt: bridge.updatedAt,
|
|
414
|
+
routeTraceCount: bridge.routeTraceCount,
|
|
415
|
+
supervisionCount: bridge.supervisionCount,
|
|
416
|
+
routerUpdateCount: bridge.routerUpdateCount,
|
|
417
|
+
teacherArtifactCount: bridge.teacherArtifactCount,
|
|
418
|
+
pgVersionRequested: bridge.pgVersionRequested,
|
|
419
|
+
pgVersionUsed: bridge.pgVersionUsed,
|
|
420
|
+
decisionLogCount: bridge.decisionLogCount,
|
|
421
|
+
materializedPackId: bridge.materializedPackId,
|
|
422
|
+
promoted: bridge.promoted,
|
|
423
|
+
baselinePersisted: bridge.baselinePersisted,
|
|
424
|
+
source: bridge.source,
|
|
425
|
+
detail: detailParts.join(" "),
|
|
426
|
+
error: options.error ?? null
|
|
427
|
+
};
|
|
428
|
+
}
|
|
429
|
+
function buildRuntimeMaterializationMetadata(loaded) {
|
|
430
|
+
if (loaded.bridge === null) {
|
|
431
|
+
return null;
|
|
432
|
+
}
|
|
433
|
+
return {
|
|
434
|
+
path: loaded.path,
|
|
435
|
+
updatedAt: loaded.bridge.updatedAt,
|
|
436
|
+
routeTraceCount: loaded.bridge.routeTraceCount,
|
|
437
|
+
supervisionCount: loaded.bridge.supervisionCount,
|
|
438
|
+
routerUpdateCount: loaded.bridge.routerUpdateCount,
|
|
439
|
+
teacherArtifactCount: loaded.bridge.teacherArtifactCount,
|
|
440
|
+
pgVersionRequested: loaded.bridge.pgVersionRequested,
|
|
441
|
+
pgVersionUsed: loaded.bridge.pgVersionUsed,
|
|
442
|
+
decisionLogCount: loaded.bridge.decisionLogCount,
|
|
443
|
+
materializedPackId: loaded.bridge.materializedPackId,
|
|
444
|
+
promoted: loaded.bridge.promoted,
|
|
445
|
+
baselinePersisted: loaded.bridge.baselinePersisted,
|
|
446
|
+
fallbackReason: loaded.bridge.fallbackReason,
|
|
447
|
+
routerNoOpReason: loaded.bridge.routerNoOpReason,
|
|
448
|
+
source: loaded.bridge.source
|
|
449
|
+
};
|
|
450
|
+
}
|
|
451
|
+
function mergeCanonicalStatusBridge(canonicalBridge, runtimeLoaded) {
|
|
452
|
+
const runtimeBridge = runtimeLoaded.bridge;
|
|
453
|
+
const runtimeMaterialized = buildRuntimeMaterializationMetadata(runtimeLoaded);
|
|
454
|
+
const hasPersistedSurface = canonicalBridge.source?.bridge === TRACED_LEARNING_STATUS_SURFACE_BRIDGE;
|
|
455
|
+
if (hasPersistedSurface) {
|
|
456
|
+
return {
|
|
457
|
+
updatedAt: canonicalBridge.updatedAt,
|
|
458
|
+
routeTraceCount: canonicalBridge.routeTraceCount,
|
|
459
|
+
supervisionCount: canonicalBridge.supervisionCount,
|
|
460
|
+
routerUpdateCount: canonicalBridge.routerUpdateCount,
|
|
461
|
+
teacherArtifactCount: canonicalBridge.teacherArtifactCount,
|
|
462
|
+
pgVersionRequested: canonicalBridge.pgVersionRequested,
|
|
463
|
+
pgVersionUsed: canonicalBridge.pgVersionUsed,
|
|
464
|
+
decisionLogCount: canonicalBridge.decisionLogCount,
|
|
465
|
+
materializedPackId: canonicalBridge.materializedPackId,
|
|
466
|
+
promoted: canonicalBridge.promoted,
|
|
467
|
+
baselinePersisted: canonicalBridge.baselinePersisted,
|
|
468
|
+
fallbackReason: canonicalBridge.fallbackReason,
|
|
469
|
+
routerNoOpReason: canonicalBridge.routerNoOpReason,
|
|
470
|
+
source: runtimeMaterialized === null
|
|
471
|
+
? canonicalBridge.source
|
|
472
|
+
: {
|
|
473
|
+
...(canonicalBridge.source ?? {}),
|
|
474
|
+
runtimeMaterialized
|
|
475
|
+
}
|
|
476
|
+
};
|
|
477
|
+
}
|
|
478
|
+
return {
|
|
479
|
+
updatedAt: canonicalBridge.updatedAt ?? runtimeBridge?.updatedAt ?? null,
|
|
480
|
+
routeTraceCount: canonicalBridge.routeTraceCount,
|
|
481
|
+
supervisionCount: canonicalBridge.supervisionCount,
|
|
482
|
+
routerUpdateCount: canonicalBridge.routerUpdateCount,
|
|
483
|
+
teacherArtifactCount: canonicalBridge.teacherArtifactCount,
|
|
484
|
+
pgVersionRequested: runtimeBridge?.pgVersionRequested ?? canonicalBridge.pgVersionRequested ?? null,
|
|
485
|
+
pgVersionUsed: runtimeBridge?.pgVersionUsed ?? canonicalBridge.pgVersionUsed ?? null,
|
|
486
|
+
decisionLogCount: runtimeBridge?.decisionLogCount ?? canonicalBridge.decisionLogCount ?? 0,
|
|
487
|
+
materializedPackId: runtimeBridge?.materializedPackId ?? canonicalBridge.materializedPackId ?? null,
|
|
488
|
+
promoted: runtimeBridge?.promoted ?? canonicalBridge.promoted,
|
|
489
|
+
baselinePersisted: runtimeBridge?.baselinePersisted ?? canonicalBridge.baselinePersisted,
|
|
490
|
+
fallbackReason: runtimeBridge?.fallbackReason ?? canonicalBridge.fallbackReason ?? null,
|
|
491
|
+
routerNoOpReason: runtimeBridge?.routerNoOpReason ?? canonicalBridge.routerNoOpReason ?? null,
|
|
492
|
+
source: runtimeMaterialized === null
|
|
493
|
+
? canonicalBridge.source
|
|
494
|
+
: {
|
|
495
|
+
...(canonicalBridge.source ?? {}),
|
|
496
|
+
runtimeMaterialized
|
|
497
|
+
}
|
|
498
|
+
};
|
|
499
|
+
}
|
|
500
|
+
export function mergeTracedLearningBridgePayload(payload, persisted) {
|
|
501
|
+
const current = normalizeBridgePayload(payload);
|
|
502
|
+
const persistedBridge = persisted?.bridge ?? null;
|
|
503
|
+
if (persistedBridge === null) {
|
|
504
|
+
return current;
|
|
505
|
+
}
|
|
506
|
+
const routeTraceCount = Math.max(current.routeTraceCount, persistedBridge.routeTraceCount);
|
|
507
|
+
const supervisionCount = Math.max(current.supervisionCount, persistedBridge.supervisionCount);
|
|
508
|
+
const routerUpdateCount = Math.max(current.routerUpdateCount, persistedBridge.routerUpdateCount);
|
|
509
|
+
const teacherArtifactCount = Math.max(current.teacherArtifactCount, persistedBridge.teacherArtifactCount);
|
|
510
|
+
const usedBridge = routeTraceCount !== current.routeTraceCount ||
|
|
511
|
+
supervisionCount !== current.supervisionCount ||
|
|
512
|
+
routerUpdateCount !== current.routerUpdateCount ||
|
|
513
|
+
teacherArtifactCount !== current.teacherArtifactCount;
|
|
514
|
+
if (!usedBridge) {
|
|
515
|
+
return current;
|
|
516
|
+
}
|
|
517
|
+
return normalizeBridgePayload({
|
|
518
|
+
...current,
|
|
519
|
+
routeTraceCount,
|
|
520
|
+
supervisionCount,
|
|
521
|
+
routerUpdateCount,
|
|
522
|
+
teacherArtifactCount,
|
|
523
|
+
routerNoOpReason: supervisionCount > 0 || routerUpdateCount > 0 ? null : current.routerNoOpReason,
|
|
524
|
+
source: {
|
|
525
|
+
...(current.source ?? {}),
|
|
526
|
+
bridge: normalizeOptionalString(persistedBridge.source?.bridge) ?? "brain_store_state",
|
|
527
|
+
bridgedRuntime: {
|
|
528
|
+
path: persisted?.path ?? null,
|
|
529
|
+
updatedAt: persistedBridge.updatedAt,
|
|
530
|
+
routeTraceCount: persistedBridge.routeTraceCount,
|
|
531
|
+
supervisionCount: persistedBridge.supervisionCount,
|
|
532
|
+
routerUpdateCount: persistedBridge.routerUpdateCount,
|
|
533
|
+
teacherArtifactCount: persistedBridge.teacherArtifactCount,
|
|
534
|
+
source: persistedBridge.source
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
});
|
|
538
|
+
}
|
|
539
|
+
export function buildTracedLearningStatusSurface(activationRoot, options = {}) {
|
|
540
|
+
const persisted = loadBrainStoreTracedLearningBridge(options);
|
|
541
|
+
const runtime = loadTracedLearningBridge(activationRoot);
|
|
542
|
+
if (persisted.bridge !== null) {
|
|
543
|
+
return buildStatusSurface(persisted.path, mergeCanonicalStatusBridge(persisted.bridge, runtime), {
|
|
544
|
+
runtimeState: describeBridgeRuntimeState(runtime)
|
|
545
|
+
});
|
|
546
|
+
}
|
|
547
|
+
if (runtime.bridge !== null) {
|
|
548
|
+
return buildStatusSurface(runtime.path, runtime.bridge);
|
|
549
|
+
}
|
|
550
|
+
if (persisted.error !== null) {
|
|
551
|
+
return defaultSurface(persisted.path, "brain_store_unreadable", persisted.error);
|
|
552
|
+
}
|
|
553
|
+
return defaultSurface(runtime.path, runtime.error === null ? "bridge_missing" : "bridge_unreadable", runtime.error);
|
|
554
|
+
}
|
package/package.json
CHANGED