@openclawbrain/cli 0.4.14 → 0.4.16

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";
@@ -17,14 +17,15 @@ import { inspectOpenClawBrainHookStatus, inspectOpenClawBrainPluginAllowlist } f
17
17
  import { describeOpenClawBrainInstallIdentity, describeOpenClawBrainInstallLayout, findInstalledOpenClawBrainPlugin, getOpenClawBrainKnownPluginIds, normalizeOpenClawBrainPluginsConfig, pinInstalledOpenClawBrainPluginActivationRoot, resolveOpenClawBrainInstallTarget } from "./openclaw-plugin-install.js";
18
18
  import { buildOpenClawBrainConvergeRestartPlan, classifyOpenClawBrainConvergeVerification, describeOpenClawBrainConvergeChangeReasons, diffOpenClawBrainConvergeRuntimeFingerprint, finalizeOpenClawBrainConvergeResult, planOpenClawBrainConvergePluginAction } from "./install-converge.js";
19
19
  import { loadAttachmentPolicyDeclaration, resolveEffectiveAttachmentPolicyTruth, writeAttachmentPolicyDeclaration } from "./attachment-policy-truth.js";
20
- import { DEFAULT_WATCH_POLL_INTERVAL_SECONDS, buildNormalizedEventExportFromScannedEvents, bootstrapRuntimeAttach, buildOperatorSurfaceReport, clearOpenClawProfileRuntimeLoadProof, compileRuntimeContext, createAsyncTeacherLiveLoop, createOpenClawLocalSessionTail, createRuntimeEventExportScanner, describeCurrentProfileBrainStatus, formatOperatorRollbackReport, listOpenClawProfileRuntimeLoadProofs, loadRuntimeEventExportBundle, loadWatchTeacherSnapshotState, persistWatchTeacherSnapshot, rollbackRuntimeAttach, resolveAttachmentRuntimeLoadProofsPath, resolveOperatorTeacherSnapshotPath, resolveAsyncTeacherLiveLoopSnapshotPath, resolveWatchSessionTailCursorPath, resolveWatchStateRoot, resolveWatchTeacherSnapshotPath, scanLiveEventExport, scanRecordedSession, summarizeLearningPathFromMaterialization, summarizeNormalizedEventExportLabelFlow, writeScannedEventExportBundle } from "./index.js";
20
+ import { DEFAULT_WATCH_POLL_INTERVAL_SECONDS, buildNormalizedEventExportFromScannedEvents, bootstrapRuntimeAttach, buildOperatorSurfaceReport, clearOpenClawProfileRuntimeLoadProof, compileRuntimeContext, createAsyncTeacherLiveLoop, createOpenClawLocalSessionTail, createRuntimeEventExportScanner, describeCurrentProfileBrainStatus, formatOperatorRollbackReport, listOpenClawProfileRuntimeLoadProofs, loadRuntimeEventExportBundle, loadWatchTeacherSnapshotState, persistWatchTeacherSnapshot, rollbackRuntimeAttach, resolveAttachmentRuntimeLoadProofsPath, resolveOperatorTeacherSnapshotPath, resolveAsyncTeacherLiveLoopSnapshotPath, resolveWatchSessionTailCursorPath, resolveWatchStateRoot, resolveWatchTeacherSnapshotPath, scanLiveEventExport, scanRecordedSession, summarizeLearningPathFromMaterialization, summarizeNormalizedEventExportLabelFlow, summarizeTeacherNoArtifactCycle, writeScannedEventExportBundle } from "./index.js";
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";
@@ -681,14 +682,14 @@ const LEARNING_WARNING_MESSAGES = {
681
682
  passive_backfill_pending: "passive backfill remains queued",
682
683
  teacher_queue_full: "teacher queue is full",
683
684
  teacher_labels_stale: "teacher labels are stale",
684
- teacher_no_artifacts: "teacher produced no artifacts",
685
+ teacher_no_artifacts: "latest no-op cycle had teachable material but no new teacher artifact",
685
686
  teacher_snapshot_unavailable: "teacher snapshot is unavailable"
686
687
  };
687
688
  const TEACHER_NO_OP_MESSAGES = {
688
689
  none: "the latest processed export produced teacher artifacts",
689
690
  duplicate_export: "the latest cycle was a no-op because the export was already seen",
690
691
  queue_full: "the latest cycle was a no-op because the teacher queue was full",
691
- no_teacher_artifacts: "the latest cycle was a no-op because no teacher artifacts were produced",
692
+ no_teacher_artifacts: "the latest cycle produced no new teacher artifacts",
692
693
  empty_scan: "the latest cycle was a no-op because the scanner did not produce any events",
693
694
  unavailable: "the latest cycle is not visible from the current operator snapshot"
694
695
  };
@@ -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,14 +1082,16 @@ 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";
1084
1089
  const healthy = report.teacherLoop.failureMode === "none" &&
1085
1090
  stale === false &&
1086
1091
  report.teacherLoop.watchState !== "not_visible";
1087
- const cycleDetail = TEACHER_NO_OP_MESSAGES[report.teacherLoop.lastNoOpReason] ?? "the latest teacher cycle detail is unavailable";
1092
+ const cycleDetail = report.teacherLoop.lastNoOpReason === "no_teacher_artifacts"
1093
+ ? summarizeTeacherNoArtifactCycle(report.teacherLoop.notes).detail
1094
+ : TEACHER_NO_OP_MESSAGES[report.teacherLoop.lastNoOpReason] ?? "the latest teacher cycle detail is unavailable";
1088
1095
  if (report.teacherLoop.failureMode !== "none" && report.teacherLoop.failureMode !== "unavailable") {
1089
1096
  return {
1090
1097
  model: providerConfig.teacher.model,
@@ -1344,7 +1351,7 @@ function buildCompactStatusHeader(status, report, options) {
1344
1351
  const tracedLearning = options.tracedLearning ?? buildTracedLearningStatusSurface(status.host.activationRoot);
1345
1352
  return [
1346
1353
  `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}`,
1354
+ `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
1355
  `attachTruth current=${attachmentTruth.currentProfileLabel} hook=${attachmentTruth.hookFiles} config=${attachmentTruth.configLoad} runtime=${attachmentTruth.runtimeLoad} watcher=${attachmentTruth.watcher} attachedSet=${formatAttachedProfileTruthCompactList(attachmentTruth.attachedProfiles)} why=${attachmentTruth.detail}`,
1349
1356
  `passive firstExport=${yesNo(status.passiveLearning.firstExportOccurred)} backlog=${status.passiveLearning.backlogState} pending=${formatStatusNullableNumber(status.passiveLearning.pendingLive)}/${formatStatusNullableNumber(status.passiveLearning.pendingBackfill)}`,
1350
1357
  `serving pack=${status.passiveLearning.currentServingPackId ?? "none"} lastExport=${status.passiveLearning.lastExportAt ?? "none"} lastPromotion=${status.passiveLearning.lastPromotionAt ?? "none"}`,
@@ -1353,6 +1360,7 @@ function buildCompactStatusHeader(status, report, options) {
1353
1360
  `changed ${status.passiveLearning.lastObservedDelta.explanation}`,
1354
1361
  `explain ${status.brain.summary}`,
1355
1362
  `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)}`,
1363
+ `attribution ${formatOperatorLearningAttributionSummary({ status })}`,
1356
1364
  `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
1365
  `embedder model=${embedder.model} provisioned=${yesNo(embedder.provisioned)} live=${yesNo(embedder.live)} why=${embedder.detail}`,
1358
1366
  `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 +1398,7 @@ function formatCurrentProfileStatusSummary(status, report, targetInspection, opt
1390
1398
  })}`,
1391
1399
  `host runtime=${status.host.runtimeOwner} activation=${status.host.activationRoot}`,
1392
1400
  `profile selector=${status.profile.selector}${profileIdSuffix} attachment=${status.attachment.state} policy=${status.attachment.policyMode}`,
1401
+ `guard severity=${status.hook.guardSeverity} actionability=${status.hook.guardActionability} action=${status.hook.guardAction} summary=${status.hook.guardSummary}`,
1393
1402
  `attachTruth current=${attachmentTruth.currentProfileLabel} hook=${attachmentTruth.hookFiles} config=${attachmentTruth.configLoad} runtime=${attachmentTruth.runtimeLoad} watcher=${attachmentTruth.watcher} detail=${attachmentTruth.detail}`,
1394
1403
  `attachedSet ${formatAttachedProfileTruthDetailedList(attachmentTruth.attachedProfiles)} proofPath=${shortenPath(attachmentTruth.runtimeProofPath)} proofError=${attachmentTruth.runtimeProofError ?? "none"}`,
1395
1404
  `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 +1419,7 @@ function formatCurrentProfileStatusSummary(status, report, targetInspection, opt
1410
1419
  learningPath: report.learningPath,
1411
1420
  tracedLearning
1412
1421
  })}`,
1422
+ `attribution ${formatOperatorLearningAttributionSummary({ status })}`,
1413
1423
  `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
1424
  `traced ${formatTracedLearningSurface(tracedLearning)}`,
1415
1425
  `teacherProof ${formatTeacherLoopSummary(report)}`,
@@ -1615,11 +1625,13 @@ function inspectInstallConvergeVerification(parsed) {
1615
1625
  openclawHome: parsed.openclawHome,
1616
1626
  installHook
1617
1627
  }),
1618
- summaryLine: `STATUS ${displayedStatus}; hook=${installHook.state}/${installHook.loadability}; runtime=${attachmentTruth.runtimeLoad}; loadProof=${normalizedStatusAndReport.status.hook.loadProof}; serve=${normalizedStatusAndReport.status.brainStatus.serveState}`,
1628
+ 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
1629
  facts: {
1620
1630
  installLayout: normalizedStatusAndReport.status.hook.installLayout ?? installHook.installLayout ?? null,
1621
1631
  installState: installHook.state,
1622
1632
  loadability: installHook.loadability,
1633
+ guardSeverity: normalizedStatusAndReport.status.hook.guardSeverity,
1634
+ guardActionability: normalizedStatusAndReport.status.hook.guardActionability,
1623
1635
  displayedStatus,
1624
1636
  runtimeLoad: attachmentTruth.runtimeLoad,
1625
1637
  loadProof: normalizedStatusAndReport.status.hook.loadProof,
@@ -3110,10 +3122,133 @@ function buildExtensionPluginManifest() {
3110
3122
  name: "OpenClawBrain",
3111
3123
  description: "Learned memory and context from OpenClawBrain",
3112
3124
  version: packageMetadata.version,
3125
+ uiHints: {
3126
+ brainRoot: {
3127
+ label: "Brain Root",
3128
+ help: "Directory containing OpenClawBrain state.db and immutable packs"
3129
+ },
3130
+ brainEmbeddingProvider: {
3131
+ label: "Embedding Provider",
3132
+ help: "Provider used for learned retrieval embeddings"
3133
+ },
3134
+ brainEmbeddingModel: {
3135
+ label: "Embedding Model",
3136
+ help: "Embedding model used for init, retrieval, and brain_teach"
3137
+ },
3138
+ brainEmbeddingBaseUrl: {
3139
+ label: "Embedding Base URL",
3140
+ help: "Optional base URL override for the embedding provider endpoint"
3141
+ },
3142
+ brainMaxCompileMs: {
3143
+ label: "Brain Compile Deadline",
3144
+ help: "Soft wall-clock deadline in milliseconds for brain assembly phase-boundary checks"
3145
+ },
3146
+ brainBudgetFraction: {
3147
+ label: "Brain Budget Fraction",
3148
+ help: "Fraction of the available token budget reserved for retrieval before final prompt clipping"
3149
+ },
3150
+ brainMaxHops: {
3151
+ label: "Brain Max Hops",
3152
+ help: "Maximum learned-retrieval graph expansion depth per query"
3153
+ },
3154
+ brainMaxFanoutPerNode: {
3155
+ label: "Brain Max Fanout Per Node",
3156
+ help: "Maximum accepted traversals from a single source node expansion"
3157
+ },
3158
+ brainMaxFrontierSize: {
3159
+ label: "Brain Max Frontier Size",
3160
+ help: "Maximum traversal frontier size during learned retrieval"
3161
+ },
3162
+ brainMaxSeeds: {
3163
+ label: "Brain Max Seeds",
3164
+ help: "Maximum seed nodes admitted into learned retrieval before graph expansion"
3165
+ },
3166
+ brainSemanticThreshold: {
3167
+ label: "Brain Semantic Threshold",
3168
+ help: "Minimum semantic similarity required for seed admission during learned retrieval"
3169
+ },
3170
+ brainShadowMode: {
3171
+ label: "Brain Shadow Mode",
3172
+ help: "Run learned retrieval for telemetry only without injecting brain context into prompts"
3173
+ },
3174
+ brainWorkerMode: {
3175
+ label: "Worker Mode",
3176
+ help: "Run the learner in a supervised child process (default) or fall back to in-process mode"
3177
+ },
3178
+ brainWorkerHeartbeatTimeoutMs: {
3179
+ label: "Worker Heartbeat Timeout",
3180
+ help: "Milliseconds to wait before treating the supervised learner worker as stalled"
3181
+ },
3182
+ brainWorkerRestartDelayMs: {
3183
+ label: "Worker Restart Delay",
3184
+ help: "Milliseconds to wait before restarting the supervised learner worker after exit or crash"
3185
+ }
3186
+ },
3113
3187
  configSchema: {
3114
3188
  type: "object",
3115
3189
  additionalProperties: false,
3116
- properties: {}
3190
+ properties: {
3191
+ brainEnabled: {
3192
+ type: "boolean"
3193
+ },
3194
+ brainRoot: {
3195
+ type: "string"
3196
+ },
3197
+ brainEmbeddingProvider: {
3198
+ type: "string"
3199
+ },
3200
+ brainEmbeddingModel: {
3201
+ type: "string"
3202
+ },
3203
+ brainEmbeddingBaseUrl: {
3204
+ type: "string"
3205
+ },
3206
+ brainMaxCompileMs: {
3207
+ type: "integer",
3208
+ minimum: 0
3209
+ },
3210
+ brainBudgetFraction: {
3211
+ type: "number",
3212
+ minimum: 0,
3213
+ maximum: 1
3214
+ },
3215
+ brainMaxHops: {
3216
+ type: "integer",
3217
+ minimum: 1
3218
+ },
3219
+ brainMaxFanoutPerNode: {
3220
+ type: "integer",
3221
+ minimum: 1
3222
+ },
3223
+ brainMaxFrontierSize: {
3224
+ type: "integer",
3225
+ minimum: 1
3226
+ },
3227
+ brainMaxSeeds: {
3228
+ type: "integer",
3229
+ minimum: 1
3230
+ },
3231
+ brainSemanticThreshold: {
3232
+ type: "number",
3233
+ minimum: 0,
3234
+ maximum: 1
3235
+ },
3236
+ brainShadowMode: {
3237
+ type: "boolean"
3238
+ },
3239
+ brainWorkerMode: {
3240
+ type: "string",
3241
+ enum: ["child", "in_process"]
3242
+ },
3243
+ brainWorkerHeartbeatTimeoutMs: {
3244
+ type: "integer",
3245
+ minimum: 1000
3246
+ },
3247
+ brainWorkerRestartDelayMs: {
3248
+ type: "integer",
3249
+ minimum: 0
3250
+ }
3251
+ }
3117
3252
  }
3118
3253
  }, null, 2) + "\n";
3119
3254
  }
@@ -4367,7 +4502,7 @@ function persistWatchTracedLearningBridgeSurface(input) {
4367
4502
  }
4368
4503
  }));
4369
4504
  }
4370
- function runLearnCommand(parsed) {
4505
+ async function runLearnCommand(parsed) {
4371
4506
  const learnStatePath = path.join(parsed.activationRoot, "learn-cli-state.json");
4372
4507
  const teacherSnapshotPath = resolveAsyncTeacherLiveLoopSnapshotPath(parsed.activationRoot);
4373
4508
  function isLearnRuntimeStateLike(value) {
@@ -4580,6 +4715,7 @@ function runLearnCommand(parsed) {
4580
4715
  return 0;
4581
4716
  }
4582
4717
  const learningExport = normalizedEventExport;
4718
+ const resolvedEmbedder = resolveCliEmbedderConfig(undefined, activationRoot);
4583
4719
  const serveTimeLearning = resolveServeTimeLearningRuntimeInput(activationRoot);
4584
4720
  const learnerResult = drainAlwaysOnLearningRuntime({
4585
4721
  packLabel: "learn-cli",
@@ -4602,7 +4738,11 @@ function runLearnCommand(parsed) {
4602
4738
  ...(serveTimeLearning.baselineState !== undefined ? { baselineState: serveTimeLearning.baselineState } : {}),
4603
4739
  activationRoot
4604
4740
  });
4605
- const lastMaterialization = learnerResult.materializations.at(-1) ?? null;
4741
+ let lastMaterialization = learnerResult.materializations.at(-1) ?? null;
4742
+ lastMaterialization = await reindexMaterializationCandidateWithEmbedder(lastMaterialization, resolvedEmbedder.embedder);
4743
+ if (lastMaterialization !== null && learnerResult.materializations.length > 0) {
4744
+ learnerResult.materializations[learnerResult.materializations.length - 1] = lastMaterialization;
4745
+ }
4606
4746
  const plan = describeAlwaysOnLearningRuntimeState(learnerResult.state, lastMaterialization);
4607
4747
  const learningPath = summarizeLearningPathFromMaterialization(lastMaterialization);
4608
4748
  const graphEvolution = lastMaterialization?.candidate.payloads.graph.evolution;
@@ -4995,14 +5135,9 @@ async function applyWatchMaterialization(activationRoot, snapshot, lastHandledMa
4995
5135
  failure: null
4996
5136
  };
4997
5137
  }
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
- }
5138
+ materialization = await reindexMaterializationCandidateWithEmbedder(materialization, embedder);
5139
+ if (snapshot?.learner !== undefined && snapshot.learner !== null) {
5140
+ snapshot.learner.lastMaterialization = materialization;
5006
5141
  }
5007
5142
  const shortPackId = packId.length > 16 ? packId.slice(0, 16) : packId;
5008
5143
  const observedAt = new Date().toISOString();
@@ -5183,7 +5318,7 @@ function resolveWatchTeacherLabelerConfig(input, activationRoot) {
5183
5318
  warnings
5184
5319
  };
5185
5320
  }
5186
- function resolveWatchEmbedderConfig(input, activationRoot) {
5321
+ function resolveCliEmbedderConfig(input, activationRoot) {
5187
5322
  if (input !== undefined) {
5188
5323
  return {
5189
5324
  embedder: input,
@@ -5367,7 +5502,7 @@ export async function createWatchCommandRuntime(input) {
5367
5502
  log(`Scan root: ${shortenPath(scanRoot)}`);
5368
5503
  log(`State: cursor=${shortenPath(sessionTailCursorPath)} snapshot=${shortenPath(teacherSnapshotPath)}`);
5369
5504
  const resolvedTeacherLabeler = resolveWatchTeacherLabelerConfig(input.teacherLabeler, activationRoot);
5370
- const resolvedEmbedder = resolveWatchEmbedderConfig(input.embedder, activationRoot);
5505
+ const resolvedEmbedder = resolveCliEmbedderConfig(input.embedder, activationRoot);
5371
5506
  const teacherLabeler = resolvedTeacherLabeler.teacherLabeler;
5372
5507
  for (const warning of resolvedTeacherLabeler.warnings) {
5373
5508
  startupWarnings.push(`teacher_config_warning:${warning}`);
@@ -5988,7 +6123,12 @@ export function runOperatorCli(argv = process.argv.slice(2)) {
5988
6123
  return runHistoryCommand(parsed);
5989
6124
  }
5990
6125
  if (parsed.command === "learn") {
5991
- return runLearnCommand(parsed);
6126
+ runLearnCommand(parsed).then((code) => { process.exitCode = code; }, (error) => {
6127
+ console.error("[openclawbrain] learn failed");
6128
+ console.error(error instanceof Error ? error.stack ?? error.message : String(error));
6129
+ process.exitCode = 1;
6130
+ });
6131
+ return 0;
5992
6132
  }
5993
6133
  if (parsed.command === "watch") {
5994
6134
  // Watch is async — bridge to sync CLI entry by scheduling and returning 0.