@openclawbrain/cli 0.4.1 → 0.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -8
- package/dist/src/attachment-policy-truth.js +175 -0
- package/dist/src/cli.js +110 -11
- 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.2` is the repo's next operator CLI package surface for OpenClawBrain. The latest published CLI remains `0.4.1` until this repo state is shipped.
|
|
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.2 install --openclaw-home ~/.openclaw
|
|
10
10
|
openclaw gateway restart
|
|
11
|
-
npx @openclawbrain/cli@0.4.
|
|
11
|
+
npx @openclawbrain/cli@0.4.2 status --openclaw-home ~/.openclaw --detailed
|
|
12
12
|
```
|
|
13
13
|
|
|
14
|
-
Patch note for `0.4.
|
|
14
|
+
Patch note for `0.4.2`: the CLI now persists declared attachment policy during install/attach so later `status` reads stop underreporting shared installs as `policy=null` / `undeclared`, and the package tarball now carries the full operator module surface plus traced-learning bridge needed for the canonical brain-store status path.
|
|
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.2 install --openclaw-home ~/.openclaw
|
|
24
|
+
npx @openclawbrain/cli@0.4.2 status --openclaw-home ~/.openclaw --detailed
|
|
25
|
+
npx @openclawbrain/cli@0.4.2 rollback --activation-root /var/openclawbrain/activation --dry-run
|
|
26
|
+
npx @openclawbrain/cli@0.4.2 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,11 @@ 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 { buildTracedLearningStatusSurface, loadBrainStoreTracedLearningBridge, mergeTracedLearningBridgePayload, persistBrainStoreTracedLearningBridge, writeTracedLearningBridge } from "./traced-learning-bridge.js";
|
|
21
23
|
import { discoverOpenClawSessionStores, loadOpenClawSessionIndex, readOpenClawSessionFile } from "./session-store.js";
|
|
22
24
|
import { readOpenClawBrainProviderDefaults, readOpenClawBrainProviderConfig, readOpenClawBrainProviderConfigFromSources, resolveOpenClawBrainProviderDefaultsPath } from "./provider-config.js";
|
|
23
25
|
const OPENCLAWBRAIN_EMBEDDER_BASE_URL_ENV = "OPENCLAWBRAIN_EMBEDDER_BASE_URL";
|
|
@@ -861,6 +863,56 @@ function summarizeStatusAttachmentTruth(input) {
|
|
|
861
863
|
})
|
|
862
864
|
};
|
|
863
865
|
}
|
|
866
|
+
function normalizeAttachmentPolicyMode(value) {
|
|
867
|
+
return value === "undeclared" || value === "dedicated" || value === "shared"
|
|
868
|
+
? value
|
|
869
|
+
: null;
|
|
870
|
+
}
|
|
871
|
+
function applyAttachmentPolicyTruth(status, report) {
|
|
872
|
+
const referenceCount = findInstalledHookReferencesForActivationRoot({
|
|
873
|
+
activationRoot: status.host.activationRoot
|
|
874
|
+
}).length;
|
|
875
|
+
const declaration = loadAttachmentPolicyDeclaration(status.host.activationRoot);
|
|
876
|
+
const resolvedPolicy = resolveEffectiveAttachmentPolicyTruth({
|
|
877
|
+
statusPolicy: normalizeAttachmentPolicyMode(status.attachment.policyMode),
|
|
878
|
+
reportPolicy: report === null
|
|
879
|
+
? null
|
|
880
|
+
: normalizeAttachmentPolicyMode(report.manyProfile.declaredAttachmentPolicy),
|
|
881
|
+
declaredPolicy: declaration.declaration?.policy ?? null,
|
|
882
|
+
referenceCount
|
|
883
|
+
});
|
|
884
|
+
const effectivePolicy = resolvedPolicy.effectivePolicy;
|
|
885
|
+
if (effectivePolicy === null) {
|
|
886
|
+
return {
|
|
887
|
+
status,
|
|
888
|
+
report
|
|
889
|
+
};
|
|
890
|
+
}
|
|
891
|
+
const nextStatusPolicy = resolvedPolicy.statusPolicy;
|
|
892
|
+
const nextReportPolicy = report === null
|
|
893
|
+
? null
|
|
894
|
+
: resolvedPolicy.reportPolicy;
|
|
895
|
+
return {
|
|
896
|
+
status: nextStatusPolicy === status.attachment.policyMode
|
|
897
|
+
? status
|
|
898
|
+
: {
|
|
899
|
+
...status,
|
|
900
|
+
attachment: {
|
|
901
|
+
...status.attachment,
|
|
902
|
+
policyMode: nextStatusPolicy
|
|
903
|
+
}
|
|
904
|
+
},
|
|
905
|
+
report: report === null || nextReportPolicy === report.manyProfile.declaredAttachmentPolicy
|
|
906
|
+
? report
|
|
907
|
+
: {
|
|
908
|
+
...report,
|
|
909
|
+
manyProfile: {
|
|
910
|
+
...report.manyProfile,
|
|
911
|
+
declaredAttachmentPolicy: nextReportPolicy
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
};
|
|
915
|
+
}
|
|
864
916
|
function runOllamaProbe(args, baseUrl) {
|
|
865
917
|
try {
|
|
866
918
|
execFileSync("ollama", [...args], {
|
|
@@ -1257,6 +1309,10 @@ function summarizeDisplayedStatus(status, installHook) {
|
|
|
1257
1309
|
? "fail"
|
|
1258
1310
|
: status.brainStatus.status;
|
|
1259
1311
|
}
|
|
1312
|
+
function formatTracedLearningSurface(surface) {
|
|
1313
|
+
const detail = surface.error === null ? surface.detail : `${surface.detail}: ${surface.error}`;
|
|
1314
|
+
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}`;
|
|
1315
|
+
}
|
|
1260
1316
|
function buildCompactStatusHeader(status, report, options) {
|
|
1261
1317
|
const installHook = summarizeStatusInstallHook(options.openclawHome);
|
|
1262
1318
|
const hookLoad = summarizeStatusHookLoad(installHook, status);
|
|
@@ -1272,6 +1328,7 @@ function buildCompactStatusHeader(status, report, options) {
|
|
|
1272
1328
|
openclawHome: options.openclawHome,
|
|
1273
1329
|
status
|
|
1274
1330
|
});
|
|
1331
|
+
const tracedLearning = options.tracedLearning ?? buildTracedLearningStatusSurface(status.host.activationRoot);
|
|
1275
1332
|
return [
|
|
1276
1333
|
`lifecycle attach=${status.attachment.state} learner=${yesNo(status.passiveLearning.learnerRunning)} watch=${summarizeStatusWatchState(status)} export=${status.passiveLearning.exportState} promote=${summarizeStatusPromotionState(status)} serve=${summarizeStatusServeReality(status)}`,
|
|
1277
1334
|
`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 +1343,7 @@ function buildCompactStatusHeader(status, report, options) {
|
|
|
1286
1343
|
`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
1344
|
`embedder model=${embedder.model} provisioned=${yesNo(embedder.provisioned)} live=${yesNo(embedder.live)} why=${embedder.detail}`,
|
|
1288
1345
|
`routeFn available=${yesNo(routeFn.available)} freshness=${routeFn.freshness} trained=${routeFn.trainedAt ?? "none"} updated=${routeFn.updatedAt ?? "none"} used=${routeFn.usedAt ?? "none"} why=${routeFn.detail}`,
|
|
1346
|
+
`traced ${formatTracedLearningSurface(tracedLearning)}`,
|
|
1289
1347
|
`embeddings provider=${embeddings.provider} provisioned=${embeddings.provisionedState} live=${embeddings.liveState} stored=${embeddings.embeddedEntryCount ?? "none"}/${embeddings.totalEntryCount ?? "none"} models=${liveModels}`,
|
|
1290
1348
|
`localLLM detected=${yesNo(localLlm.detected)} enabled=${yesNo(localLlm.enabled)} provider=${localLlm.provider} model=${localLlm.model}`,
|
|
1291
1349
|
`alerts service_risk=${formatStatusAlertLine(alerts.serviceRisk)} degraded_brain=${formatStatusAlertLine(alerts.degradedBrain)} cosmetic_noise=${formatStatusAlertLine(alerts.cosmeticNoise)}`
|
|
@@ -1302,6 +1360,7 @@ function formatCurrentProfileStatusSummary(status, report, targetInspection, opt
|
|
|
1302
1360
|
openclawHome: options.openclawHome,
|
|
1303
1361
|
status
|
|
1304
1362
|
});
|
|
1363
|
+
const tracedLearning = options.tracedLearning ?? buildTracedLearningStatusSurface(status.host.activationRoot);
|
|
1305
1364
|
const profileIdSuffix = status.profile.profileId === null ? "" : ` id=${status.profile.profileId}`;
|
|
1306
1365
|
const targetLine = targetInspection === null
|
|
1307
1366
|
? `target activation=${status.host.activationRoot} source=activation_root_only`
|
|
@@ -1335,6 +1394,7 @@ function formatCurrentProfileStatusSummary(status, report, targetInspection, opt
|
|
|
1335
1394
|
`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
1395
|
`path ${formatLearningPathSummary(report.learningPath)}`,
|
|
1337
1396
|
`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}`,
|
|
1397
|
+
`traced ${formatTracedLearningSurface(tracedLearning)}`,
|
|
1338
1398
|
`teacherProof ${formatTeacherLoopSummary(report)}`,
|
|
1339
1399
|
`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
1400
|
`embeddings provider=${embeddings.provider} provisioned=${embeddings.provisionedState} live=${embeddings.liveState} stored=${embeddings.embeddedEntryCount ?? "none"}/${embeddings.totalEntryCount ?? "none"} models=${liveModels}`,
|
|
@@ -3294,6 +3354,13 @@ function runProfileHookAttachCommand(parsed) {
|
|
|
3294
3354
|
steps.push(pluginConfigRepair.detail);
|
|
3295
3355
|
const learnerService = ensureLifecycleLearnerService(parsed.activationRoot);
|
|
3296
3356
|
steps.push(learnerService.detail);
|
|
3357
|
+
const attachmentPolicyDeclaration = writeAttachmentPolicyDeclaration({
|
|
3358
|
+
activationRoot: parsed.activationRoot,
|
|
3359
|
+
policy: parsed.shared ? "shared" : "dedicated",
|
|
3360
|
+
source: parsed.command,
|
|
3361
|
+
openclawHome: parsed.openclawHome
|
|
3362
|
+
});
|
|
3363
|
+
steps.push(`Recorded attachment policy declaration: ${attachmentPolicyDeclaration.declaration.policy} at ${shortenPath(attachmentPolicyDeclaration.path)}`);
|
|
3297
3364
|
const brainFeedback = buildInstallBrainFeedbackSummary({
|
|
3298
3365
|
parsed,
|
|
3299
3366
|
targetInspection,
|
|
@@ -4244,6 +4311,31 @@ function runLearnCommand(parsed) {
|
|
|
4244
4311
|
lastAppliedMaterializationJobId: lastMaterialization?.jobId ?? null
|
|
4245
4312
|
}
|
|
4246
4313
|
});
|
|
4314
|
+
const tracedLearningBridge = mergeTracedLearningBridgePayload({
|
|
4315
|
+
updatedAt: now,
|
|
4316
|
+
routeTraceCount: lastMaterialization?.candidate.summary.learnedRouter.routeTraceCount ?? serveTimeLearning.decisionLogCount,
|
|
4317
|
+
supervisionCount,
|
|
4318
|
+
routerUpdateCount,
|
|
4319
|
+
teacherArtifactCount: teacherArtifacts.length,
|
|
4320
|
+
pgVersionRequested: learnPathReport.pgVersionRequested,
|
|
4321
|
+
pgVersionUsed: learnPathReport.pgVersionUsed,
|
|
4322
|
+
decisionLogCount: learnPathReport.decisionLogCount,
|
|
4323
|
+
fallbackReason: learnPathReport.fallbackReason,
|
|
4324
|
+
routerNoOpReason,
|
|
4325
|
+
materializedPackId,
|
|
4326
|
+
promoted,
|
|
4327
|
+
baselinePersisted,
|
|
4328
|
+
source: {
|
|
4329
|
+
command: "learn",
|
|
4330
|
+
exportDigest: learningExport.provenance.exportDigest,
|
|
4331
|
+
teacherSnapshotPath
|
|
4332
|
+
}
|
|
4333
|
+
}, loadBrainStoreTracedLearningBridge());
|
|
4334
|
+
const surfacedSupervisionCount = tracedLearningBridge.supervisionCount;
|
|
4335
|
+
const surfacedRouterUpdateCount = tracedLearningBridge.routerUpdateCount;
|
|
4336
|
+
const surfacedRouterNoOpReason = tracedLearningBridge.routerNoOpReason;
|
|
4337
|
+
persistBrainStoreTracedLearningBridge(tracedLearningBridge);
|
|
4338
|
+
writeTracedLearningBridge(activationRoot, tracedLearningBridge);
|
|
4247
4339
|
const summaryMessage = materializedPackId === null
|
|
4248
4340
|
? `Scanned ${totalSessions} sessions, ${totalEvents} new events, no candidate materialized, no promotion.`
|
|
4249
4341
|
: `Scanned ${totalSessions} sessions, ${totalEvents} new events, materialized ${materializedPackId}, promoted.${connectSummary}`;
|
|
@@ -4266,9 +4358,9 @@ function runLearnCommand(parsed) {
|
|
|
4266
4358
|
teacherBudget: learnerResult.state.sparseFeedback.teacherBudget,
|
|
4267
4359
|
eligibleFeedbackCount: learnerResult.state.sparseFeedback.eligibleFeedbackCount,
|
|
4268
4360
|
budgetedOutFeedbackCount: learnerResult.state.sparseFeedback.budgetedOutFeedbackCount,
|
|
4269
|
-
supervisionCount,
|
|
4270
|
-
routerUpdateCount,
|
|
4271
|
-
routerNoOpReason,
|
|
4361
|
+
supervisionCount: surfacedSupervisionCount,
|
|
4362
|
+
routerUpdateCount: surfacedRouterUpdateCount,
|
|
4363
|
+
routerNoOpReason: surfacedRouterNoOpReason,
|
|
4272
4364
|
pending: plan.pending,
|
|
4273
4365
|
learnedRange: plan.learnedRange
|
|
4274
4366
|
},
|
|
@@ -4288,8 +4380,8 @@ function runLearnCommand(parsed) {
|
|
|
4288
4380
|
}
|
|
4289
4381
|
else {
|
|
4290
4382
|
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=${
|
|
4383
|
+
? `Scanned ${totalSessions} sessions, ${totalEvents} new events, no promotion. cycles=${learnerResult.cycles.length} stop=${learnerResult.stopReason} supervision=${surfacedSupervisionCount}.`
|
|
4384
|
+
: `Scanned ${totalSessions} sessions, ${totalEvents} new events, materialized ${materializedPackId}, promoted.${connectSummary} cycles=${learnerResult.cycles.length} supervision=${surfacedSupervisionCount}.`;
|
|
4293
4385
|
console.log(text);
|
|
4294
4386
|
console.log(`labels: source=${labelFlow.source} human=${labelFlow.humanLabelCount ?? "none"} self=${labelFlow.selfLabelCount ?? "none"} implicitPositive=${labelFlow.implicitPositiveCount ?? "none"} teacherArtifacts=${labelFlow.asyncTeacherArtifactCount ?? "none"}`);
|
|
4295
4387
|
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"}`);
|
|
@@ -5546,25 +5638,32 @@ export function runOperatorCli(argv = process.argv.slice(2)) {
|
|
|
5546
5638
|
teacherSnapshotPath: resolveOperatorTeacherSnapshotPath(activationRoot, statusOrRollback.input.teacherSnapshotPath)
|
|
5547
5639
|
};
|
|
5548
5640
|
const status = describeCurrentProfileBrainStatus(operatorInput);
|
|
5641
|
+
const tracedLearning = buildTracedLearningStatusSurface(activationRoot);
|
|
5642
|
+
const normalizedStatusAndReport = applyAttachmentPolicyTruth(status, statusOrRollback.json ? null : buildOperatorSurfaceReport(operatorInput));
|
|
5549
5643
|
if (statusOrRollback.json) {
|
|
5550
|
-
console.log(JSON.stringify(
|
|
5644
|
+
console.log(JSON.stringify({
|
|
5645
|
+
...normalizedStatusAndReport.status,
|
|
5646
|
+
tracedLearning
|
|
5647
|
+
}, null, 2));
|
|
5551
5648
|
}
|
|
5552
5649
|
else {
|
|
5553
|
-
const report =
|
|
5650
|
+
const report = normalizedStatusAndReport.report;
|
|
5554
5651
|
const providerConfig = readOpenClawBrainProviderConfigFromSources({
|
|
5555
5652
|
env: process.env,
|
|
5556
5653
|
activationRoot
|
|
5557
5654
|
});
|
|
5558
5655
|
if (statusOrRollback.detailed) {
|
|
5559
|
-
console.log(formatCurrentProfileStatusSummary(status, report, targetInspection, {
|
|
5656
|
+
console.log(formatCurrentProfileStatusSummary(normalizedStatusAndReport.status, report, targetInspection, {
|
|
5560
5657
|
openclawHome: statusOrRollback.openclawHome,
|
|
5561
|
-
providerConfig
|
|
5658
|
+
providerConfig,
|
|
5659
|
+
tracedLearning
|
|
5562
5660
|
}));
|
|
5563
5661
|
}
|
|
5564
5662
|
else {
|
|
5565
|
-
console.log(formatHumanFriendlyStatus(status, report, targetInspection, {
|
|
5663
|
+
console.log(formatHumanFriendlyStatus(normalizedStatusAndReport.status, report, targetInspection, {
|
|
5566
5664
|
openclawHome: statusOrRollback.openclawHome,
|
|
5567
|
-
providerConfig
|
|
5665
|
+
providerConfig,
|
|
5666
|
+
tracedLearning
|
|
5568
5667
|
}));
|
|
5569
5668
|
}
|
|
5570
5669
|
}
|
|
@@ -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