@openclawbrain/cli 0.4.14 → 0.4.15

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.
@@ -44,8 +44,26 @@ async function reportDiagnostic(input) {
44
44
  }
45
45
  warnedDiagnostics.add(input.key);
46
46
  }
47
- console.warn(input.message);
48
- await appendLocalDiagnosticLog(input.message);
47
+ const formatted = formatDiagnosticMessage(input);
48
+ console.warn(formatted);
49
+ await appendLocalDiagnosticLog(formatted);
50
+ }
51
+ function formatDiagnosticMessage(input) {
52
+ if (input.severity === undefined ||
53
+ input.actionability === undefined ||
54
+ input.summary === undefined ||
55
+ input.action === undefined) {
56
+ return input.message;
57
+ }
58
+ const detail = input.message.replace(/^\[openclawbrain\]\s*/, "");
59
+ return [
60
+ "[openclawbrain]",
61
+ `severity=${input.severity}`,
62
+ `actionability=${input.actionability}`,
63
+ `summary=${JSON.stringify(input.summary)}`,
64
+ `action=${JSON.stringify(input.action)}`,
65
+ `detail=${JSON.stringify(detail)}`
66
+ ].join(" ");
49
67
  }
50
68
  function announceStartupBreadcrumb() {
51
69
  if (isActivationRootPlaceholder(ACTIVATION_ROOT)) {
@@ -78,8 +96,12 @@ export default function register(api) {
78
96
  catch (error) {
79
97
  const detail = error instanceof Error ? error.message : String(error);
80
98
  void reportDiagnostic({
81
- key: `runtime-load-proof:${detail}`,
99
+ key: "runtime-load-proof-failed",
82
100
  once: true,
101
+ severity: "degraded",
102
+ actionability: "inspect_local_proof_write",
103
+ summary: "runtime-load proof write failed after hook registration",
104
+ action: "Inspect local filesystem permissions and the activation-root proof path if proof capture is expected.",
83
105
  message: `[openclawbrain] runtime load proof failed: ${detail}`
84
106
  });
85
107
  }
@@ -91,8 +113,12 @@ export default function register(api) {
91
113
  void reportDiagnostic({
92
114
  key: "registration-failed",
93
115
  once: true,
116
+ severity: "blocking",
117
+ actionability: "rerun_install",
118
+ summary: "extension registration threw before the runtime hook was fully attached",
119
+ action: "Rerun openclawbrain install --openclaw-home <path>; if it still fails, inspect the extension loader/runtime.",
94
120
  message: `[openclawbrain] extension registration failed: ${detail}`
95
121
  });
96
122
  }
97
123
  }
98
- //# sourceMappingURL=index.js.map
124
+ //# sourceMappingURL=index.js.map
@@ -22,10 +22,16 @@ export interface ExtensionCompileFailure {
22
22
  }
23
23
  export type ExtensionCompileResult = ExtensionCompileSuccess | ExtensionCompileFailure;
24
24
  export type ExtensionCompileRuntimeContext = (input: ExtensionCompileInput) => ExtensionCompileResult;
25
+ export type ExtensionDiagnosticSeverity = "degraded" | "blocking";
26
+ export type ExtensionDiagnosticActionability = "inspect_host_event_shape" | "inspect_host_registration_api" | "inspect_local_proof_write" | "inspect_runtime_compile" | "rerun_install";
25
27
  export interface ExtensionDiagnostic {
26
28
  key: string;
27
29
  message: string;
28
30
  once?: boolean;
31
+ severity?: ExtensionDiagnosticSeverity;
32
+ actionability?: ExtensionDiagnosticActionability;
33
+ summary?: string;
34
+ action?: string;
29
35
  }
30
36
  export interface ExtensionRegistrationApi {
31
37
  on(eventName: string, handler: (event: unknown, ctx: unknown) => Promise<Record<string, unknown>>, options?: {
@@ -5,12 +5,12 @@ export function validateExtensionRegistrationApi(api) {
5
5
  if (!isRecord(api) || typeof api.on !== "function") {
6
6
  return {
7
7
  ok: false,
8
- diagnostic: {
8
+ diagnostic: shapeDiagnostic({
9
9
  key: "registration-api-invalid",
10
10
  once: true,
11
11
  message: `[openclawbrain] extension inactive: host registration API is missing api.on(event, handler, options) ` +
12
12
  `(received=${describeValue(api)})`
13
- }
13
+ })
14
14
  };
15
15
  }
16
16
  return {
@@ -64,11 +64,11 @@ export function normalizePromptBuildEvent(event) {
64
64
  export function createBeforePromptBuildHandler(input) {
65
65
  return async (event, _ctx) => {
66
66
  if (isActivationRootPlaceholder(input.activationRoot)) {
67
- await input.reportDiagnostic({
67
+ await input.reportDiagnostic(shapeDiagnostic({
68
68
  key: "activation-root-placeholder",
69
69
  once: true,
70
70
  message: "[openclawbrain] BRAIN NOT YET LOADED: ACTIVATION_ROOT is still a placeholder. Install @openclawbrain/cli, then run: openclawbrain install --openclaw-home <path>"
71
- });
71
+ }));
72
72
  return {};
73
73
  }
74
74
  const normalized = normalizePromptBuildEvent(event);
@@ -103,11 +103,11 @@ export function createBeforePromptBuildHandler(input) {
103
103
  });
104
104
  if (!result.ok) {
105
105
  const mode = result.hardRequirementViolated ? "hard-fail" : "fail-open";
106
- await input.reportDiagnostic({
106
+ await input.reportDiagnostic(shapeDiagnostic({
107
107
  key: `compile-${mode}`,
108
108
  message: `[openclawbrain] ${mode}: ${result.error} ` +
109
109
  `(activationRoot=${input.activationRoot}, sessionId=${normalized.event.sessionId ?? "unknown"}, channel=${normalized.event.channel ?? "unknown"})`
110
- });
110
+ }));
111
111
  return {};
112
112
  }
113
113
  if (result.brainContext.length > 0) {
@@ -119,20 +119,20 @@ export function createBeforePromptBuildHandler(input) {
119
119
  }
120
120
  catch (error) {
121
121
  const detail = error instanceof Error ? error.stack ?? error.message : String(error);
122
- await input.reportDiagnostic({
122
+ await input.reportDiagnostic(shapeDiagnostic({
123
123
  key: "compile-threw",
124
124
  message: `[openclawbrain] compile threw: ${detail} ` +
125
125
  `(activationRoot=${input.activationRoot}, sessionId=${normalized.event.sessionId ?? "unknown"}, channel=${normalized.event.channel ?? "unknown"})`
126
- });
126
+ }));
127
127
  }
128
128
  return {};
129
129
  };
130
130
  }
131
131
  function failOpenDiagnostic(key, reason, detail) {
132
- return {
132
+ return shapeDiagnostic({
133
133
  key,
134
134
  message: `[openclawbrain] fail-open: ${reason} (${detail})`
135
- };
135
+ });
136
136
  }
137
137
  function normalizeOptionalScalarField(value, fieldName, warnings) {
138
138
  if (value === undefined || value === null) {
@@ -145,11 +145,11 @@ function normalizeOptionalScalarField(value, fieldName, warnings) {
145
145
  if (typeof value === "number" || typeof value === "bigint" || typeof value === "boolean") {
146
146
  return String(value);
147
147
  }
148
- warnings.push({
148
+ warnings.push(shapeDiagnostic({
149
149
  key: `runtime-${fieldName}-ignored`,
150
150
  message: `[openclawbrain] fail-open: ignored unsupported before_prompt_build ${fieldName} ` +
151
151
  `(${fieldName}=${describeValue(value)})`
152
- });
152
+ }));
153
153
  return undefined;
154
154
  }
155
155
  function normalizeOptionalNonNegativeIntegerField(value, fieldName, warnings) {
@@ -174,11 +174,11 @@ function normalizeOptionalNonNegativeIntegerField(value, fieldName, warnings) {
174
174
  }
175
175
  }
176
176
  }
177
- warnings.push({
177
+ warnings.push(shapeDiagnostic({
178
178
  key: `runtime-${fieldName}-ignored`,
179
179
  message: `[openclawbrain] fail-open: ignored unsupported before_prompt_build ${fieldName} ` +
180
180
  `(${fieldName}=${describeValue(value)})`
181
- });
181
+ }));
182
182
  return undefined;
183
183
  }
184
184
  function extractPromptMessage(message) {
@@ -264,7 +264,63 @@ function describeValue(value) {
264
264
  }
265
265
  return `${typeof value}(${String(value)})`;
266
266
  }
267
+ function shapeDiagnostic(diagnostic) {
268
+ if (diagnostic.severity !== undefined &&
269
+ diagnostic.actionability !== undefined &&
270
+ diagnostic.summary !== undefined &&
271
+ diagnostic.action !== undefined) {
272
+ return diagnostic;
273
+ }
274
+ if (diagnostic.key === "activation-root-placeholder") {
275
+ return {
276
+ ...diagnostic,
277
+ severity: "blocking",
278
+ actionability: "rerun_install",
279
+ summary: "extension hook is installed but ACTIVATION_ROOT is still unpinned",
280
+ action: "Run openclawbrain install --openclaw-home <path> to pin the runtime hook."
281
+ };
282
+ }
283
+ if (diagnostic.key === "registration-api-invalid") {
284
+ return {
285
+ ...diagnostic,
286
+ severity: "blocking",
287
+ actionability: "inspect_host_registration_api",
288
+ summary: "extension host registration API is missing or incompatible",
289
+ action: "Repair or upgrade the host extension API so api.on(event, handler, options) is available."
290
+ };
291
+ }
292
+ if (diagnostic.key === "compile-hard-fail") {
293
+ return {
294
+ ...diagnostic,
295
+ severity: "blocking",
296
+ actionability: "inspect_runtime_compile",
297
+ summary: "brain context compile hit a hard requirement",
298
+ action: "Inspect the activation root and compile error; rerun install if the pinned hook may be stale."
299
+ };
300
+ }
301
+ if (diagnostic.key === "compile-fail-open" || diagnostic.key === "compile-threw") {
302
+ return {
303
+ ...diagnostic,
304
+ severity: "degraded",
305
+ actionability: "inspect_runtime_compile",
306
+ summary: diagnostic.key === "compile-threw"
307
+ ? "brain context compile threw during before_prompt_build"
308
+ : "brain context compile failed open during before_prompt_build",
309
+ action: "Inspect the activation root and compile error if brain context is unexpectedly empty."
310
+ };
311
+ }
312
+ if (diagnostic.key.startsWith("runtime-")) {
313
+ return {
314
+ ...diagnostic,
315
+ severity: "degraded",
316
+ actionability: "inspect_host_event_shape",
317
+ summary: "before_prompt_build payload was partial or malformed",
318
+ action: "Inspect the host before_prompt_build event shape; OpenClawBrain fail-opened safely."
319
+ };
320
+ }
321
+ return diagnostic;
322
+ }
267
323
  function isRecord(value) {
268
324
  return typeof value === "object" && value !== null;
269
325
  }
270
- //# sourceMappingURL=runtime-guard.js.map
326
+ //# sourceMappingURL=runtime-guard.js.map
package/dist/src/cli.js CHANGED
@@ -9,7 +9,7 @@ import { DEFAULT_OLLAMA_EMBEDDING_MODEL, createOllamaEmbedder } from "@openclawb
9
9
  import { ensureManagedLearnerServiceForActivationRoot, inspectManagedLearnerService, removeManagedLearnerServiceForActivationRoot, parseDaemonArgs, runDaemonCommand } from "./daemon.js";
10
10
  import { exportBrain, importBrain } from "./import-export.js";
11
11
  import { buildNormalizedEventExport } from "@openclawbrain/contracts";
12
- import { buildTeacherSupervisionArtifactsFromNormalizedEventExport, createAlwaysOnLearningRuntimeState, describeAlwaysOnLearningRuntimeState, drainAlwaysOnLearningRuntime, loadOrInitBaseline, reindexCandidatePackBuildResultWithEmbedder, materializeAlwaysOnLearningCandidatePack, persistBaseline } from "./local-learner.js";
12
+ import { buildTeacherSupervisionArtifactsFromNormalizedEventExport, createAlwaysOnLearningRuntimeState, describeAlwaysOnLearningRuntimeState, drainAlwaysOnLearningRuntime, loadOrInitBaseline, materializeAlwaysOnLearningCandidatePack, persistBaseline } from "./local-learner.js";
13
13
  import { inspectActivationState, loadPackFromActivation, promoteCandidatePack, readLearningSpineLogEntries, stageCandidatePack } from "@openclawbrain/pack-format";
14
14
  import { resolveActivationRoot } from "./resolve-activation-root.js";
15
15
  import { describeOpenClawHomeInspection, discoverOpenClawHomes, formatOpenClawHomeLayout, formatOpenClawHomeProfileSource, inspectOpenClawHome } from "./openclaw-home-layout.js";
@@ -20,11 +20,12 @@ import { loadAttachmentPolicyDeclaration, resolveEffectiveAttachmentPolicyTruth,
20
20
  import { DEFAULT_WATCH_POLL_INTERVAL_SECONDS, buildNormalizedEventExportFromScannedEvents, bootstrapRuntimeAttach, buildOperatorSurfaceReport, clearOpenClawProfileRuntimeLoadProof, compileRuntimeContext, createAsyncTeacherLiveLoop, createOpenClawLocalSessionTail, createRuntimeEventExportScanner, describeCurrentProfileBrainStatus, formatOperatorRollbackReport, listOpenClawProfileRuntimeLoadProofs, loadRuntimeEventExportBundle, loadWatchTeacherSnapshotState, persistWatchTeacherSnapshot, rollbackRuntimeAttach, resolveAttachmentRuntimeLoadProofsPath, resolveOperatorTeacherSnapshotPath, resolveAsyncTeacherLiveLoopSnapshotPath, resolveWatchSessionTailCursorPath, resolveWatchStateRoot, resolveWatchTeacherSnapshotPath, scanLiveEventExport, scanRecordedSession, summarizeLearningPathFromMaterialization, summarizeNormalizedEventExportLabelFlow, writeScannedEventExportBundle } from "./index.js";
21
21
  import { appendLearningUpdateLogs } from "./learning-spine.js";
22
22
  import { buildPassiveLearningSessionExportFromOpenClawSessionStore } from "./local-session-passive-learning.js";
23
+ import { reindexMaterializationCandidateWithEmbedder } from "./materialization-embedder.js";
23
24
  import { summarizePackVectorEmbeddingState } from "./embedding-status.js";
24
25
  import { buildTracedLearningBridgePayloadFromRuntime, buildTracedLearningStatusSurface, persistTracedLearningBridgeState } from "./traced-learning-bridge.js";
25
26
  import { discoverOpenClawSessionStores, loadOpenClawSessionIndex, readOpenClawSessionFile } from "./session-store.js";
26
27
  import { readOpenClawBrainProviderDefaults, readOpenClawBrainProviderConfig, readOpenClawBrainProviderConfigFromSources, resolveOpenClawBrainProviderDefaultsPath } from "./provider-config.js";
27
- import { formatOperatorLearningPathSummary } from "./status-learning-path.js";
28
+ import { formatOperatorLearningAttributionSummary, formatOperatorLearningPathSummary } from "./status-learning-path.js";
28
29
  import { buildProofCommandForOpenClawHome, buildProofCommandHelpSection, captureOperatorProofBundle, formatOperatorProofResult, parseProofCliArgs } from "./proof-command.js";
29
30
  const OPENCLAWBRAIN_EMBEDDER_BASE_URL_ENV = "OPENCLAWBRAIN_EMBEDDER_BASE_URL";
30
31
  const OPENCLAWBRAIN_EMBEDDER_PROVIDER_ENV = "OPENCLAWBRAIN_EMBEDDER_PROVIDER";
@@ -707,6 +708,10 @@ function summarizeStatusHookLoad(installHook, status) {
707
708
  installState: installHook.state === "unknown" ? "unverified" : installHook.state,
708
709
  loadability: installHook.loadability,
709
710
  loadProof: status.hook.loadProof,
711
+ guardSeverity: status.hook.guardSeverity,
712
+ guardActionability: status.hook.guardActionability,
713
+ guardSummary: status.hook.guardSummary,
714
+ guardAction: status.hook.guardAction,
710
715
  detail: status.hook.detail
711
716
  };
712
717
  }
@@ -1077,7 +1082,7 @@ function summarizeStatusTeacher(report, providerConfig, localLlm) {
1077
1082
  detail: `${providerConfig.teacher.model} is enabled on Ollama, but no watch teacher snapshot is visible yet`
1078
1083
  };
1079
1084
  }
1080
- const stale = report.teacherLoop.latestFreshness === "stale" || report.teacherLoop.watchState === "stale_snapshot";
1085
+ const stale = report.teacherLoop.watchState === "stale_snapshot" || (report.teacherLoop.latestFreshness === "stale" && report.teacherLoop.lastNoOpReason !== "no_teacher_artifacts");
1081
1086
  const idle = report.teacherLoop.running === false &&
1082
1087
  (report.teacherLoop.queueDepth ?? 0) === 0 &&
1083
1088
  report.teacherLoop.failureMode === "none";
@@ -1344,7 +1349,7 @@ function buildCompactStatusHeader(status, report, options) {
1344
1349
  const tracedLearning = options.tracedLearning ?? buildTracedLearningStatusSurface(status.host.activationRoot);
1345
1350
  return [
1346
1351
  `lifecycle attach=${status.attachment.state} learner=${yesNo(status.passiveLearning.learnerRunning)} watch=${summarizeStatusWatchState(status)} export=${status.passiveLearning.exportState} promote=${summarizeStatusPromotionState(status)} serve=${summarizeStatusServeReality(status)}`,
1347
- `hook install=${hookLoad.installState} loadability=${hookLoad.loadability} loadProof=${hookLoad.loadProof} layout=${status.hook.installLayout ?? "unverified"} additional=${status.hook.additionalInstallCount ?? 0} detail=${hookLoad.detail}`,
1352
+ `hook install=${hookLoad.installState} loadability=${hookLoad.loadability} loadProof=${hookLoad.loadProof} layout=${status.hook.installLayout ?? "unverified"} additional=${status.hook.additionalInstallCount ?? 0} severity=${hookLoad.guardSeverity} actionability=${hookLoad.guardActionability} summary=${hookLoad.guardSummary}`,
1348
1353
  `attachTruth current=${attachmentTruth.currentProfileLabel} hook=${attachmentTruth.hookFiles} config=${attachmentTruth.configLoad} runtime=${attachmentTruth.runtimeLoad} watcher=${attachmentTruth.watcher} attachedSet=${formatAttachedProfileTruthCompactList(attachmentTruth.attachedProfiles)} why=${attachmentTruth.detail}`,
1349
1354
  `passive firstExport=${yesNo(status.passiveLearning.firstExportOccurred)} backlog=${status.passiveLearning.backlogState} pending=${formatStatusNullableNumber(status.passiveLearning.pendingLive)}/${formatStatusNullableNumber(status.passiveLearning.pendingBackfill)}`,
1350
1355
  `serving pack=${status.passiveLearning.currentServingPackId ?? "none"} lastExport=${status.passiveLearning.lastExportAt ?? "none"} lastPromotion=${status.passiveLearning.lastPromotionAt ?? "none"}`,
@@ -1353,6 +1358,7 @@ function buildCompactStatusHeader(status, report, options) {
1353
1358
  `changed ${status.passiveLearning.lastObservedDelta.explanation}`,
1354
1359
  `explain ${status.brain.summary}`,
1355
1360
  `graph blocks=${report.graph.blockCount ?? "none"} strongest=${report.graph.strongestBlockId ?? "none"} latest=${report.graph.latestMaterialization.packId ?? "none"} latestChanged=${yesNo(report.graph.latestMaterialization.changed)} connect=${formatCompactGraphConnectDiagnostics(report.graph.latestMaterialization.connectDiagnostics ?? report.graph.connectDiagnostics)}`,
1361
+ `attribution ${formatOperatorLearningAttributionSummary({ status })}`,
1356
1362
  `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}`,
1357
1363
  `embedder model=${embedder.model} provisioned=${yesNo(embedder.provisioned)} live=${yesNo(embedder.live)} why=${embedder.detail}`,
1358
1364
  `routeFn available=${yesNo(routeFn.available)} freshness=${routeFn.freshness} trained=${routeFn.trainedAt ?? "none"} updated=${routeFn.updatedAt ?? "none"} used=${routeFn.usedAt ?? "none"} why=${routeFn.detail}`,
@@ -1390,6 +1396,7 @@ function formatCurrentProfileStatusSummary(status, report, targetInspection, opt
1390
1396
  })}`,
1391
1397
  `host runtime=${status.host.runtimeOwner} activation=${status.host.activationRoot}`,
1392
1398
  `profile selector=${status.profile.selector}${profileIdSuffix} attachment=${status.attachment.state} policy=${status.attachment.policyMode}`,
1399
+ `guard severity=${status.hook.guardSeverity} actionability=${status.hook.guardActionability} action=${status.hook.guardAction} summary=${status.hook.guardSummary}`,
1393
1400
  `attachTruth current=${attachmentTruth.currentProfileLabel} hook=${attachmentTruth.hookFiles} config=${attachmentTruth.configLoad} runtime=${attachmentTruth.runtimeLoad} watcher=${attachmentTruth.watcher} detail=${attachmentTruth.detail}`,
1394
1401
  `attachedSet ${formatAttachedProfileTruthDetailedList(attachmentTruth.attachedProfiles)} proofPath=${shortenPath(attachmentTruth.runtimeProofPath)} proofError=${attachmentTruth.runtimeProofError ?? "none"}`,
1395
1402
  `manyProfile surface=${report.manyProfile.operatorSurface} policy=${report.manyProfile.declaredAttachmentPolicy} intent=${report.manyProfile.sameGatewayIntent} checkedProof=${report.manyProfile.checkedInProofTopology} sameGatewayProof=${yesNo(report.manyProfile.sameGatewayProof)} sharedWriteProof=${yesNo(report.manyProfile.sharedWriteSafetyProof)}`,
@@ -1410,6 +1417,7 @@ function formatCurrentProfileStatusSummary(status, report, targetInspection, opt
1410
1417
  learningPath: report.learningPath,
1411
1418
  tracedLearning
1412
1419
  })}`,
1420
+ `attribution ${formatOperatorLearningAttributionSummary({ status })}`,
1413
1421
  `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}`,
1414
1422
  `traced ${formatTracedLearningSurface(tracedLearning)}`,
1415
1423
  `teacherProof ${formatTeacherLoopSummary(report)}`,
@@ -1615,11 +1623,13 @@ function inspectInstallConvergeVerification(parsed) {
1615
1623
  openclawHome: parsed.openclawHome,
1616
1624
  installHook
1617
1625
  }),
1618
- summaryLine: `STATUS ${displayedStatus}; hook=${installHook.state}/${installHook.loadability}; runtime=${attachmentTruth.runtimeLoad}; loadProof=${normalizedStatusAndReport.status.hook.loadProof}; serve=${normalizedStatusAndReport.status.brainStatus.serveState}`,
1626
+ summaryLine: `STATUS ${displayedStatus}; hook=${installHook.state}/${installHook.loadability}; guard=${normalizedStatusAndReport.status.hook.guardSeverity}/${normalizedStatusAndReport.status.hook.guardActionability}; runtime=${attachmentTruth.runtimeLoad}; loadProof=${normalizedStatusAndReport.status.hook.loadProof}; serve=${normalizedStatusAndReport.status.brainStatus.serveState}`,
1619
1627
  facts: {
1620
1628
  installLayout: normalizedStatusAndReport.status.hook.installLayout ?? installHook.installLayout ?? null,
1621
1629
  installState: installHook.state,
1622
1630
  loadability: installHook.loadability,
1631
+ guardSeverity: normalizedStatusAndReport.status.hook.guardSeverity,
1632
+ guardActionability: normalizedStatusAndReport.status.hook.guardActionability,
1623
1633
  displayedStatus,
1624
1634
  runtimeLoad: attachmentTruth.runtimeLoad,
1625
1635
  loadProof: normalizedStatusAndReport.status.hook.loadProof,
@@ -3110,10 +3120,133 @@ function buildExtensionPluginManifest() {
3110
3120
  name: "OpenClawBrain",
3111
3121
  description: "Learned memory and context from OpenClawBrain",
3112
3122
  version: packageMetadata.version,
3123
+ uiHints: {
3124
+ brainRoot: {
3125
+ label: "Brain Root",
3126
+ help: "Directory containing OpenClawBrain state.db and immutable packs"
3127
+ },
3128
+ brainEmbeddingProvider: {
3129
+ label: "Embedding Provider",
3130
+ help: "Provider used for learned retrieval embeddings"
3131
+ },
3132
+ brainEmbeddingModel: {
3133
+ label: "Embedding Model",
3134
+ help: "Embedding model used for init, retrieval, and brain_teach"
3135
+ },
3136
+ brainEmbeddingBaseUrl: {
3137
+ label: "Embedding Base URL",
3138
+ help: "Optional base URL override for the embedding provider endpoint"
3139
+ },
3140
+ brainMaxCompileMs: {
3141
+ label: "Brain Compile Deadline",
3142
+ help: "Soft wall-clock deadline in milliseconds for brain assembly phase-boundary checks"
3143
+ },
3144
+ brainBudgetFraction: {
3145
+ label: "Brain Budget Fraction",
3146
+ help: "Fraction of the available token budget reserved for retrieval before final prompt clipping"
3147
+ },
3148
+ brainMaxHops: {
3149
+ label: "Brain Max Hops",
3150
+ help: "Maximum learned-retrieval graph expansion depth per query"
3151
+ },
3152
+ brainMaxFanoutPerNode: {
3153
+ label: "Brain Max Fanout Per Node",
3154
+ help: "Maximum accepted traversals from a single source node expansion"
3155
+ },
3156
+ brainMaxFrontierSize: {
3157
+ label: "Brain Max Frontier Size",
3158
+ help: "Maximum traversal frontier size during learned retrieval"
3159
+ },
3160
+ brainMaxSeeds: {
3161
+ label: "Brain Max Seeds",
3162
+ help: "Maximum seed nodes admitted into learned retrieval before graph expansion"
3163
+ },
3164
+ brainSemanticThreshold: {
3165
+ label: "Brain Semantic Threshold",
3166
+ help: "Minimum semantic similarity required for seed admission during learned retrieval"
3167
+ },
3168
+ brainShadowMode: {
3169
+ label: "Brain Shadow Mode",
3170
+ help: "Run learned retrieval for telemetry only without injecting brain context into prompts"
3171
+ },
3172
+ brainWorkerMode: {
3173
+ label: "Worker Mode",
3174
+ help: "Run the learner in a supervised child process (default) or fall back to in-process mode"
3175
+ },
3176
+ brainWorkerHeartbeatTimeoutMs: {
3177
+ label: "Worker Heartbeat Timeout",
3178
+ help: "Milliseconds to wait before treating the supervised learner worker as stalled"
3179
+ },
3180
+ brainWorkerRestartDelayMs: {
3181
+ label: "Worker Restart Delay",
3182
+ help: "Milliseconds to wait before restarting the supervised learner worker after exit or crash"
3183
+ }
3184
+ },
3113
3185
  configSchema: {
3114
3186
  type: "object",
3115
3187
  additionalProperties: false,
3116
- properties: {}
3188
+ properties: {
3189
+ brainEnabled: {
3190
+ type: "boolean"
3191
+ },
3192
+ brainRoot: {
3193
+ type: "string"
3194
+ },
3195
+ brainEmbeddingProvider: {
3196
+ type: "string"
3197
+ },
3198
+ brainEmbeddingModel: {
3199
+ type: "string"
3200
+ },
3201
+ brainEmbeddingBaseUrl: {
3202
+ type: "string"
3203
+ },
3204
+ brainMaxCompileMs: {
3205
+ type: "integer",
3206
+ minimum: 0
3207
+ },
3208
+ brainBudgetFraction: {
3209
+ type: "number",
3210
+ minimum: 0,
3211
+ maximum: 1
3212
+ },
3213
+ brainMaxHops: {
3214
+ type: "integer",
3215
+ minimum: 1
3216
+ },
3217
+ brainMaxFanoutPerNode: {
3218
+ type: "integer",
3219
+ minimum: 1
3220
+ },
3221
+ brainMaxFrontierSize: {
3222
+ type: "integer",
3223
+ minimum: 1
3224
+ },
3225
+ brainMaxSeeds: {
3226
+ type: "integer",
3227
+ minimum: 1
3228
+ },
3229
+ brainSemanticThreshold: {
3230
+ type: "number",
3231
+ minimum: 0,
3232
+ maximum: 1
3233
+ },
3234
+ brainShadowMode: {
3235
+ type: "boolean"
3236
+ },
3237
+ brainWorkerMode: {
3238
+ type: "string",
3239
+ enum: ["child", "in_process"]
3240
+ },
3241
+ brainWorkerHeartbeatTimeoutMs: {
3242
+ type: "integer",
3243
+ minimum: 1000
3244
+ },
3245
+ brainWorkerRestartDelayMs: {
3246
+ type: "integer",
3247
+ minimum: 0
3248
+ }
3249
+ }
3117
3250
  }
3118
3251
  }, null, 2) + "\n";
3119
3252
  }
@@ -4367,7 +4500,7 @@ function persistWatchTracedLearningBridgeSurface(input) {
4367
4500
  }
4368
4501
  }));
4369
4502
  }
4370
- function runLearnCommand(parsed) {
4503
+ async function runLearnCommand(parsed) {
4371
4504
  const learnStatePath = path.join(parsed.activationRoot, "learn-cli-state.json");
4372
4505
  const teacherSnapshotPath = resolveAsyncTeacherLiveLoopSnapshotPath(parsed.activationRoot);
4373
4506
  function isLearnRuntimeStateLike(value) {
@@ -4580,6 +4713,7 @@ function runLearnCommand(parsed) {
4580
4713
  return 0;
4581
4714
  }
4582
4715
  const learningExport = normalizedEventExport;
4716
+ const resolvedEmbedder = resolveCliEmbedderConfig(undefined, activationRoot);
4583
4717
  const serveTimeLearning = resolveServeTimeLearningRuntimeInput(activationRoot);
4584
4718
  const learnerResult = drainAlwaysOnLearningRuntime({
4585
4719
  packLabel: "learn-cli",
@@ -4602,7 +4736,11 @@ function runLearnCommand(parsed) {
4602
4736
  ...(serveTimeLearning.baselineState !== undefined ? { baselineState: serveTimeLearning.baselineState } : {}),
4603
4737
  activationRoot
4604
4738
  });
4605
- const lastMaterialization = learnerResult.materializations.at(-1) ?? null;
4739
+ let lastMaterialization = learnerResult.materializations.at(-1) ?? null;
4740
+ lastMaterialization = await reindexMaterializationCandidateWithEmbedder(lastMaterialization, resolvedEmbedder.embedder);
4741
+ if (lastMaterialization !== null && learnerResult.materializations.length > 0) {
4742
+ learnerResult.materializations[learnerResult.materializations.length - 1] = lastMaterialization;
4743
+ }
4606
4744
  const plan = describeAlwaysOnLearningRuntimeState(learnerResult.state, lastMaterialization);
4607
4745
  const learningPath = summarizeLearningPathFromMaterialization(lastMaterialization);
4608
4746
  const graphEvolution = lastMaterialization?.candidate.payloads.graph.evolution;
@@ -4995,14 +5133,9 @@ async function applyWatchMaterialization(activationRoot, snapshot, lastHandledMa
4995
5133
  failure: null
4996
5134
  };
4997
5135
  }
4998
- if (embedder !== null) {
4999
- materialization = {
5000
- ...materialization,
5001
- candidate: await reindexCandidatePackBuildResultWithEmbedder(materialization.candidate, embedder)
5002
- };
5003
- if (snapshot?.learner !== undefined && snapshot.learner !== null) {
5004
- snapshot.learner.lastMaterialization = materialization;
5005
- }
5136
+ materialization = await reindexMaterializationCandidateWithEmbedder(materialization, embedder);
5137
+ if (snapshot?.learner !== undefined && snapshot.learner !== null) {
5138
+ snapshot.learner.lastMaterialization = materialization;
5006
5139
  }
5007
5140
  const shortPackId = packId.length > 16 ? packId.slice(0, 16) : packId;
5008
5141
  const observedAt = new Date().toISOString();
@@ -5183,7 +5316,7 @@ function resolveWatchTeacherLabelerConfig(input, activationRoot) {
5183
5316
  warnings
5184
5317
  };
5185
5318
  }
5186
- function resolveWatchEmbedderConfig(input, activationRoot) {
5319
+ function resolveCliEmbedderConfig(input, activationRoot) {
5187
5320
  if (input !== undefined) {
5188
5321
  return {
5189
5322
  embedder: input,
@@ -5367,7 +5500,7 @@ export async function createWatchCommandRuntime(input) {
5367
5500
  log(`Scan root: ${shortenPath(scanRoot)}`);
5368
5501
  log(`State: cursor=${shortenPath(sessionTailCursorPath)} snapshot=${shortenPath(teacherSnapshotPath)}`);
5369
5502
  const resolvedTeacherLabeler = resolveWatchTeacherLabelerConfig(input.teacherLabeler, activationRoot);
5370
- const resolvedEmbedder = resolveWatchEmbedderConfig(input.embedder, activationRoot);
5503
+ const resolvedEmbedder = resolveCliEmbedderConfig(input.embedder, activationRoot);
5371
5504
  const teacherLabeler = resolvedTeacherLabeler.teacherLabeler;
5372
5505
  for (const warning of resolvedTeacherLabeler.warnings) {
5373
5506
  startupWarnings.push(`teacher_config_warning:${warning}`);
@@ -5988,7 +6121,12 @@ export function runOperatorCli(argv = process.argv.slice(2)) {
5988
6121
  return runHistoryCommand(parsed);
5989
6122
  }
5990
6123
  if (parsed.command === "learn") {
5991
- return runLearnCommand(parsed);
6124
+ runLearnCommand(parsed).then((code) => { process.exitCode = code; }, (error) => {
6125
+ console.error("[openclawbrain] learn failed");
6126
+ console.error(error instanceof Error ? error.stack ?? error.message : String(error));
6127
+ process.exitCode = 1;
6128
+ });
6129
+ return 0;
5992
6130
  }
5993
6131
  if (parsed.command === "watch") {
5994
6132
  // Watch is async — bridge to sync CLI entry by scheduling and returning 0.