@openclawbrain/cli 0.4.28 → 0.4.30

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/src/cli.js CHANGED
@@ -33,14 +33,19 @@ const OPENCLAWBRAIN_EMBEDDER_MODEL_ENV = "OPENCLAWBRAIN_EMBEDDER_MODEL";
33
33
  const OPENCLAWBRAIN_INSTALL_SKIP_EMBEDDER_PROVISION_ENV = "OPENCLAWBRAIN_INSTALL_SKIP_EMBEDDER_PROVISION";
34
34
  const LEGACY_COMPAT_PACKAGE_NAME = "@jonathangu/openclawbrain";
35
35
  const INSTALL_COMPATIBLE_LOCAL_TEACHER_MODEL_PREFIXES = [
36
+ "gemma4:31b",
37
+ "gemma4:27b",
38
+ "gemma4:12b",
36
39
  "unsloth-qwen3.5-27b:q4_k_m",
37
40
  "unsloth-qwen3.5-27b",
41
+ "qwen3.5:35b-a3b",
38
42
  "qwen3.5:32b",
39
43
  "qwen3.5:27b",
40
44
  "qwen3.5:14b",
41
45
  "qwen3.5:9b",
42
46
  "qwen3.5:8b",
43
47
  "qwen3:8b",
48
+ "qwen2.5:32b-instruct",
44
49
  "qwen2.5:7b"
45
50
  ];
46
51
  const DEFAULT_BOUNDED_JSONL_TAIL_BYTES = 4 * 1024 * 1024;
@@ -557,7 +562,7 @@ function operatorCliHelp() {
557
562
  " openclawbrain-ops scan --session <trace.json> --root <path> [options] # compatibility alias",
558
563
  "",
559
564
  "Options:",
560
- " --openclaw-home <path> OpenClaw home dir for install/attach/detach/uninstall (e.g. ~/.openclaw-Tern or ~/.openclaw). Also pins status/rollback/context/history/learn to that installed target when applicable.",
565
+ " --openclaw-home <path> OpenClaw home dir for install/attach/detach/uninstall (e.g. ./openclaw-cormorantai, ~/.openclaw-Tern, or ~/.openclaw). Also pins status/rollback/context/history/learn to that installed target when applicable.",
561
566
  " --shared Set brain-attachment-policy to shared instead of dedicated (install/attach only).",
562
567
  ` --skip-embedder-provision Skip the default Ollama ${DEFAULT_OLLAMA_EMBEDDING_MODEL} pull before install/attach bootstrap. Use only when intentionally deferring embedder setup. Also supports ${OPENCLAWBRAIN_INSTALL_SKIP_EMBEDDER_PROVISION_ENV}=1.`,
563
568
  " --activation-root <path> Explicit activation root for attach/watch/daemon and other stateful commands; install/attach default to sibling .openclawbrain/activation next to the selected OpenClaw home.",
@@ -597,6 +602,11 @@ function operatorCliHelp() {
597
602
  " 9. detach openclawbrain detach --openclaw-home <path> — remove the profile hookup only and keep brain data",
598
603
  " 10. uninstall openclawbrain uninstall --openclaw-home <path> --keep-data|--purge-data — remove the hookup and choose the data outcome explicitly",
599
604
  "",
605
+ "Examples:",
606
+ " openclawbrain install --openclaw-home ./openclaw-cormorantai",
607
+ " openclawbrain status --openclaw-home ./openclaw-cormorantai --detailed",
608
+ " openclawbrain proof --openclaw-home ./openclaw-cormorantai",
609
+ "",
600
610
  "Advanced/operator surfaces:",
601
611
  " context preview the brain context that would be injected for a message",
602
612
  " rollback preview or apply active <- previous, active -> candidate pointer movement",
@@ -950,16 +960,69 @@ function summarizeStatusAttachmentTruth(input) {
950
960
  })
951
961
  };
952
962
  }
963
+ let cachedCliPackageMetadata = null;
964
+ function resolveCliPackageManifestPath() {
965
+ const candidates = [
966
+ path.resolve(__dirname, "..", "..", "package.json"),
967
+ path.resolve(__dirname, "..", "package.json"),
968
+ ];
969
+ for (const candidate of candidates) {
970
+ if (existsSync(candidate)) {
971
+ return candidate;
972
+ }
973
+ }
974
+ throw new Error("OpenClawBrain CLI package manifest not found. Searched:\n" +
975
+ candidates.map((candidate) => ` - ${candidate}`).join("\n"));
976
+ }
977
+ function readCliPackageMetadata() {
978
+ if (cachedCliPackageMetadata !== null) {
979
+ return cachedCliPackageMetadata;
980
+ }
981
+ const manifest = JSON.parse(readFileSync(resolveCliPackageManifestPath(), "utf8"));
982
+ const name = typeof manifest.name === "string" && manifest.name.trim().length > 0
983
+ ? manifest.name.trim()
984
+ : "@openclawbrain/cli";
985
+ const version = typeof manifest.version === "string" && manifest.version.trim().length > 0
986
+ ? manifest.version.trim()
987
+ : "0.0.0";
988
+ cachedCliPackageMetadata = { name, version };
989
+ return cachedCliPackageMetadata;
990
+ }
991
+ function inspectDaemonRuntimeSurface(activationRoot) {
992
+ const managedInspection = inspectManagedLearnerService(activationRoot);
993
+ if (managedInspection.installed === true ||
994
+ managedInspection.configuredRuntimePath !== null ||
995
+ managedInspection.configuredRuntimePackageSpec !== null ||
996
+ managedInspection.configuredRuntimePackageName !== null ||
997
+ managedInspection.configuredRuntimePackageVersion !== null) {
998
+ return {
999
+ ...managedInspection,
1000
+ surfaceIdentitySource: "managed_service"
1001
+ };
1002
+ }
1003
+ const cliPackageMetadata = readCliPackageMetadata();
1004
+ return {
1005
+ ...managedInspection,
1006
+ configuredRuntimePath: canonicalizeExistingCliPath(__filename),
1007
+ configuredRuntimePackageName: cliPackageMetadata.name,
1008
+ configuredRuntimePackageVersion: cliPackageMetadata.version,
1009
+ configuredRuntimePackageSpec: null,
1010
+ surfaceIdentitySource: "current_cli"
1011
+ };
1012
+ }
953
1013
  function summarizeStatusHotfixBoundary(status) {
954
1014
  return describeOpenClawBrainHotfixBoundary({
955
1015
  hookInspection: status.hook,
956
- daemonInspection: inspectManagedLearnerService(status.host.activationRoot)
1016
+ daemonInspection: inspectDaemonRuntimeSurface(status.host.activationRoot)
957
1017
  });
958
1018
  }
959
1019
  function formatStatusHotfixBoundarySummary(boundary) {
960
1020
  return [
961
1021
  `boundary=${boundary.boundary}`,
962
1022
  `skew=${boundary.skew}`,
1023
+ `converge=${boundary.convergeState ?? "unverified"}`,
1024
+ `daemonSource=${boundary.daemonSource ?? "unverified"}`,
1025
+ `selectedHome=${boundary.selectedOpenClawHome === null ? "unverified" : shortenPath(boundary.selectedOpenClawHome)}`,
963
1026
  `daemon=${boundary.daemonPackage ?? "unverified"}`,
964
1027
  `hook=${boundary.hookPackage ?? "unverified"}`
965
1028
  ].join(" ");
@@ -1644,6 +1707,8 @@ function readInstallRuntimeFingerprint(openclawHome) {
1644
1707
  openclawHome,
1645
1708
  quiet: true
1646
1709
  });
1710
+ const activationRoot = resolvedActivationRoot.trim().length === 0 ? null : path.resolve(resolvedActivationRoot);
1711
+ const daemonSurface = activationRoot === null ? null : inspectDaemonRuntimeSurface(activationRoot);
1647
1712
  const { config } = readOpenClawJsonConfig(openclawHome);
1648
1713
  return {
1649
1714
  selectedInstall: selectedInstall === null
@@ -1659,10 +1724,15 @@ function readInstallRuntimeFingerprint(openclawHome) {
1659
1724
  hookPath: hook.hookPath,
1660
1725
  hookState: hook.installState,
1661
1726
  loadability: hook.loadability,
1662
- activationRoot: resolvedActivationRoot.trim().length === 0 ? null : path.resolve(resolvedActivationRoot),
1727
+ activationRoot,
1663
1728
  loaderSource: readTextFileIfExists(selectedInstall?.loaderEntryPath ?? null),
1664
1729
  runtimeGuardSource: readTextFileIfExists(selectedInstall?.runtimeGuardPath ?? null),
1665
- pluginsConfig: JSON.stringify(config.plugins ?? null)
1730
+ pluginsConfig: JSON.stringify(config.plugins ?? null),
1731
+ daemonRuntimePath: daemonSurface?.configuredRuntimePath ?? null,
1732
+ daemonRuntimePackageName: daemonSurface?.configuredRuntimePackageName ?? null,
1733
+ daemonRuntimePackageVersion: daemonSurface?.configuredRuntimePackageVersion ?? null,
1734
+ daemonRuntimePackageSpec: daemonSurface?.configuredRuntimePackageSpec ?? null,
1735
+ daemonRuntimeSource: daemonSurface?.surfaceIdentitySource ?? null
1666
1736
  };
1667
1737
  }
1668
1738
  function runOpenClawBrainConvergePluginStep(openclawHome) {
@@ -1733,7 +1803,22 @@ function inspectInstallConvergeVerification(parsed) {
1733
1803
  status: normalizedStatusAndReport.status
1734
1804
  });
1735
1805
  const displayedStatus = summarizeDisplayedStatus(normalizedStatusAndReport.status, installHook);
1806
+ const hotfixBoundary = summarizeStatusHotfixBoundary(normalizedStatusAndReport.status);
1807
+ const surfaceSummary = hotfixBoundary.convergeState === "half_converged"
1808
+ ? `daemon runtime and installed hook/runtime-guard are half-converged: ${hotfixBoundary.detail}`
1809
+ : hotfixBoundary.convergeState === "warning"
1810
+ ? `daemon runtime and installed hook/runtime-guard identity is not fully proven: ${hotfixBoundary.detail}`
1811
+ : hotfixBoundary.convergeState === "unverified"
1812
+ ? `daemon runtime and installed hook/runtime-guard convergence is unverified: ${hotfixBoundary.detail}`
1813
+ : hotfixBoundary.detail;
1736
1814
  const routeFn = summarizeStatusRouteFn(normalizedStatusAndReport.status, normalizedStatusAndReport.report);
1815
+ const defaultNextStep = buildStatusNextStep(normalizedStatusAndReport.status, normalizedStatusAndReport.report, {
1816
+ openclawHome: parsed.openclawHome,
1817
+ installHook
1818
+ });
1819
+ const nextStep = hotfixBoundary.convergeState === "half_converged"
1820
+ ? `Daemon runtime and installed hook/runtime-guard are half-converged for ${shortenPath(parsed.openclawHome)} (${hotfixBoundary.skew}); refresh the daemon-side CLI/runtime surface, then rerun ${buildInstallCommand(parsed.openclawHome)}.`
1821
+ : defaultNextStep;
1737
1822
  return {
1738
1823
  targetInspection,
1739
1824
  status: normalizedStatusAndReport.status,
@@ -1742,11 +1827,9 @@ function inspectInstallConvergeVerification(parsed) {
1742
1827
  attachmentTruth,
1743
1828
  displayedStatus,
1744
1829
  routeFn,
1745
- nextStep: buildStatusNextStep(normalizedStatusAndReport.status, normalizedStatusAndReport.report, {
1746
- openclawHome: parsed.openclawHome,
1747
- installHook
1748
- }),
1749
- 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}`,
1830
+ hotfixBoundary,
1831
+ nextStep,
1832
+ summaryLine: `STATUS ${displayedStatus}; hook=${installHook.state}/${installHook.loadability}; surface=${hotfixBoundary.convergeState}/${hotfixBoundary.skew}; guard=${normalizedStatusAndReport.status.hook.guardSeverity}/${normalizedStatusAndReport.status.hook.guardActionability}; runtime=${attachmentTruth.runtimeLoad}; loadProof=${normalizedStatusAndReport.status.hook.loadProof}; serve=${normalizedStatusAndReport.status.brainStatus.serveState}`,
1750
1833
  facts: {
1751
1834
  installLayout: normalizedStatusAndReport.status.hook.installLayout ?? installHook.installLayout ?? null,
1752
1835
  installState: installHook.state,
@@ -1758,7 +1841,17 @@ function inspectInstallConvergeVerification(parsed) {
1758
1841
  loadProof: normalizedStatusAndReport.status.hook.loadProof,
1759
1842
  serveState: normalizedStatusAndReport.status.brainStatus.serveState,
1760
1843
  routeFnAvailable: routeFn.available,
1761
- awaitingFirstExport: normalizedStatusAndReport.status.brainStatus.awaitingFirstExport
1844
+ awaitingFirstExport: normalizedStatusAndReport.status.brainStatus.awaitingFirstExport,
1845
+ surfaceBoundary: hotfixBoundary.boundary,
1846
+ surfaceSkew: hotfixBoundary.skew,
1847
+ surfaceConvergeState: hotfixBoundary.convergeState,
1848
+ surfaceSummary,
1849
+ daemonPackage: hotfixBoundary.daemonPackage,
1850
+ hookPackage: hotfixBoundary.hookPackage,
1851
+ daemonPath: hotfixBoundary.daemonPath,
1852
+ hookPath: hotfixBoundary.hookPath,
1853
+ runtimeGuardPath: hotfixBoundary.runtimeGuardPath,
1854
+ selectedOpenClawHome: hotfixBoundary.selectedOpenClawHome
1762
1855
  }
1763
1856
  };
1764
1857
  }
@@ -4060,6 +4153,8 @@ function emitInstallConvergeResult(result, parsed) {
4060
4153
  console.log(`Attach: ${result.attach.detail}`);
4061
4154
  console.log(`Restart: ${result.restart.detail}`);
4062
4155
  console.log(`Verify: ${result.verification.summaryLine}`);
4156
+ console.log(`Surface: ${result.verification.surface.summaryLine}`);
4157
+ console.log(`Paths: ${result.verification.surface.pathsLine}`);
4063
4158
  console.log(`Verdict: ${result.verdict.verdict}`);
4064
4159
  console.log(`Why: ${result.verdict.why}`);
4065
4160
  if (result.verdict.warnings.length > 0) {
@@ -4121,7 +4216,22 @@ function runInstallCommand(parsed) {
4121
4216
  loadProof: "unknown",
4122
4217
  serveState: "unknown",
4123
4218
  routeFnAvailable: false,
4124
- awaitingFirstExport: false
4219
+ awaitingFirstExport: false,
4220
+ surface: {
4221
+ summaryLine: "daemon/runtime hook surface identity was not verified because install failed earlier",
4222
+ pathsLine: "daemonPath=none hookPath=unverified runtimeGuard=unverified",
4223
+ boundary: "unknown",
4224
+ skew: "unknown",
4225
+ convergeState: "unknown",
4226
+ daemonSource: "unknown",
4227
+ daemonPackage: null,
4228
+ hookPackage: null,
4229
+ detail: "install failed before daemon/runtime hook surface identity could be verified",
4230
+ daemonPath: null,
4231
+ hookPath: null,
4232
+ runtimeGuardPath: null,
4233
+ selectedOpenClawHome: parsed.openclawHome
4234
+ }
4125
4235
  },
4126
4236
  verdict
4127
4237
  };
@@ -4206,7 +4316,22 @@ function runInstallCommand(parsed) {
4206
4316
  loadProof: verificationSnapshot.facts.loadProof,
4207
4317
  serveState: verificationSnapshot.facts.serveState,
4208
4318
  routeFnAvailable: verificationSnapshot.facts.routeFnAvailable,
4209
- awaitingFirstExport: verificationSnapshot.facts.awaitingFirstExport
4319
+ awaitingFirstExport: verificationSnapshot.facts.awaitingFirstExport,
4320
+ surface: {
4321
+ summaryLine: `boundary=${verificationSnapshot.hotfixBoundary.boundary}; skew=${verificationSnapshot.hotfixBoundary.skew}; converge=${verificationSnapshot.hotfixBoundary.convergeState}; daemonSource=${verificationSnapshot.hotfixBoundary.daemonSource}; selectedHome=${verificationSnapshot.hotfixBoundary.selectedOpenClawHome === null ? "unverified" : shortenPath(verificationSnapshot.hotfixBoundary.selectedOpenClawHome)}; daemon=${verificationSnapshot.hotfixBoundary.daemonPackage ?? "unverified"}; hook=${verificationSnapshot.hotfixBoundary.hookPackage ?? "unverified"}; detail=${verificationSnapshot.hotfixBoundary.detail}`,
4322
+ pathsLine: `daemonPath=${verificationSnapshot.hotfixBoundary.daemonPath === null ? "none" : shortenPath(verificationSnapshot.hotfixBoundary.daemonPath)} hookPath=${verificationSnapshot.hotfixBoundary.hookPath === null ? "unverified" : shortenPath(verificationSnapshot.hotfixBoundary.hookPath)} runtimeGuard=${verificationSnapshot.hotfixBoundary.runtimeGuardPath === null ? "unverified" : shortenPath(verificationSnapshot.hotfixBoundary.runtimeGuardPath)}`,
4323
+ boundary: verificationSnapshot.hotfixBoundary.boundary,
4324
+ skew: verificationSnapshot.hotfixBoundary.skew,
4325
+ convergeState: verificationSnapshot.hotfixBoundary.convergeState,
4326
+ daemonSource: verificationSnapshot.hotfixBoundary.daemonSource,
4327
+ daemonPackage: verificationSnapshot.hotfixBoundary.daemonPackage,
4328
+ hookPackage: verificationSnapshot.hotfixBoundary.hookPackage,
4329
+ detail: verificationSnapshot.hotfixBoundary.detail,
4330
+ daemonPath: verificationSnapshot.hotfixBoundary.daemonPath,
4331
+ hookPath: verificationSnapshot.hotfixBoundary.hookPath,
4332
+ runtimeGuardPath: verificationSnapshot.hotfixBoundary.runtimeGuardPath,
4333
+ selectedOpenClawHome: verificationSnapshot.hotfixBoundary.selectedOpenClawHome
4334
+ }
4210
4335
  },
4211
4336
  verdict,
4212
4337
  underlyingInstall: attachResult
@@ -10,8 +10,22 @@ const CHANGE_REASON_LABELS = {
10
10
  loader_source: "loader source changed",
11
11
  runtime_guard_source: "runtime-guard source changed",
12
12
  plugins_config: "OpenClaw plugins config changed",
13
+ daemon_runtime_path: "daemon runtime path changed",
14
+ daemon_runtime_identity: "daemon runtime identity changed",
13
15
  };
14
16
 
17
+ const GATEWAY_RESTART_CHANGE_REASONS = new Set([
18
+ "install_identity",
19
+ "install_layout",
20
+ "hook_path",
21
+ "hook_state",
22
+ "loadability",
23
+ "activation_root",
24
+ "loader_source",
25
+ "runtime_guard_source",
26
+ "plugins_config",
27
+ ]);
28
+
15
29
  function sameValue(left, right) {
16
30
  return left === right;
17
31
  }
@@ -28,6 +42,32 @@ function installIdentityOf(fingerprint) {
28
42
  });
29
43
  }
30
44
 
45
+ function daemonRuntimeIdentityOf(fingerprint) {
46
+ return JSON.stringify({
47
+ runtimePackageName: fingerprint?.daemonRuntimePackageName ?? null,
48
+ runtimePackageVersion: fingerprint?.daemonRuntimePackageVersion ?? null,
49
+ runtimePackageSpec: fingerprint?.daemonRuntimePackageSpec ?? null,
50
+ runtimeSource: fingerprint?.daemonRuntimeSource ?? null,
51
+ });
52
+ }
53
+
54
+ function describeSurfaceHalfConvergedReason(input) {
55
+ const boundary = input.surfaceBoundary ?? null;
56
+ const skew = input.surfaceSkew ?? null;
57
+ const selectedOpenClawHome = input.selectedOpenClawHome ?? null;
58
+ const daemonPackage = input.daemonPackage ?? null;
59
+ const hookPackage = input.hookPackage ?? null;
60
+ const boundarySuffix = boundary === null
61
+ ? ""
62
+ : skew === null || skew === "unverified"
63
+ ? ` (${boundary})`
64
+ : ` (${boundary}/${skew})`;
65
+ const packageSuffix = daemonPackage === null && hookPackage === null
66
+ ? ""
67
+ : `; daemon=${daemonPackage ?? "unverified"} hook=${hookPackage ?? "unverified"}`;
68
+ return `daemon runtime and installed hook/runtime-guard are half-converged${selectedOpenClawHome === null ? "" : ` for ${selectedOpenClawHome}`}${boundarySuffix}${packageSuffix}`;
69
+ }
70
+
31
71
  export function shouldReplaceOpenClawBrainInstallBeforeConverge(fingerprint) {
32
72
  const selectedInstall = fingerprint?.selectedInstall ?? null;
33
73
  if (selectedInstall === null) {
@@ -99,6 +139,12 @@ export function diffOpenClawBrainConvergeRuntimeFingerprint(before, after) {
99
139
  if (!sameValue(before?.pluginsConfig ?? null, after?.pluginsConfig ?? null)) {
100
140
  reasons.push("plugins_config");
101
141
  }
142
+ if (!sameValue(before?.daemonRuntimePath ?? null, after?.daemonRuntimePath ?? null)) {
143
+ reasons.push("daemon_runtime_path");
144
+ }
145
+ if (!sameValue(daemonRuntimeIdentityOf(before), daemonRuntimeIdentityOf(after))) {
146
+ reasons.push("daemon_runtime_identity");
147
+ }
102
148
  return {
103
149
  changed: reasons.length > 0,
104
150
  reasons,
@@ -114,12 +160,15 @@ export function describeOpenClawBrainConvergeChangeReasons(reasons) {
114
160
 
115
161
  export function buildOpenClawBrainConvergeRestartPlan(input) {
116
162
  const changeReasons = input.changeReasons ?? [];
117
- if (changeReasons.length === 0) {
163
+ const gatewayRestartReasons = changeReasons.filter((reason) => GATEWAY_RESTART_CHANGE_REASONS.has(reason));
164
+ if (gatewayRestartReasons.length === 0) {
118
165
  return {
119
166
  required: false,
120
167
  automatic: false,
121
- reason: "no_runtime_affecting_changes",
122
- detail: "Skipped gateway restart because converge did not change plugin files, hook wiring, or the pinned activation root.",
168
+ reason: changeReasons.length === 0 ? "no_runtime_affecting_changes" : "daemon_only_runtime_changes",
169
+ detail: changeReasons.length === 0
170
+ ? "Skipped gateway restart because converge did not change plugin files, hook wiring, or the pinned activation root."
171
+ : "Skipped gateway restart because converge only changed the daemon runtime surface; the selected OpenClaw hook/runtime-guard wiring did not change.",
123
172
  };
124
173
  }
125
174
  if (input.profileName === null) {
@@ -149,6 +198,13 @@ export function classifyOpenClawBrainConvergeVerification(input) {
149
198
  const loadProof = input.loadProof ?? "unverified";
150
199
  const serveState = input.serveState ?? "unknown";
151
200
  const installedPackageName = input.installedPackageName ?? null;
201
+ const surfaceConvergeState = input.surfaceConvergeState ?? null;
202
+ const surfaceBoundary = input.surfaceBoundary ?? null;
203
+ const surfaceSkew = input.surfaceSkew ?? null;
204
+ const selectedOpenClawHome = input.selectedOpenClawHome ?? null;
205
+ const daemonPackage = input.daemonPackage ?? null;
206
+ const hookPackage = input.hookPackage ?? null;
207
+ const surfaceSummary = input.surfaceSummary ?? null;
152
208
  if (installedPackageName === LEGACY_COMPAT_PACKAGE_NAME) {
153
209
  blockingReasons.push(`installed plugin still references the retired compatibility package ${LEGACY_COMPAT_PACKAGE_NAME}; converge must replace it with @openclawbrain/openclaw`);
154
210
  }
@@ -164,6 +220,15 @@ export function classifyOpenClawBrainConvergeVerification(input) {
164
220
  if (displayedStatus === "fail") {
165
221
  blockingReasons.push("status still reports fail");
166
222
  }
223
+ if (surfaceConvergeState === "half_converged") {
224
+ blockingReasons.push(surfaceSummary ?? describeSurfaceHalfConvergedReason({
225
+ surfaceBoundary,
226
+ surfaceSkew,
227
+ selectedOpenClawHome,
228
+ daemonPackage,
229
+ hookPackage,
230
+ }));
231
+ }
167
232
  const runtimeTruthAlreadyProven = runtimeLoad === "proven"
168
233
  && loadProof === "status_probe_ready"
169
234
  && installState === "installed"
@@ -182,6 +247,12 @@ export function classifyOpenClawBrainConvergeVerification(input) {
182
247
  if (input.restartRequired === true && input.restartPerformed !== true && runtimeTruthAlreadyProven) {
183
248
  warnings.push("automatic restart was not performed because install could not infer an exact OpenClaw profile token, but current status already proves runtime load");
184
249
  }
250
+ if (surfaceConvergeState === "warning") {
251
+ warnings.push(surfaceSummary ?? "daemon runtime vs installed hook/runtime-guard identity is not fully proven from this verification snapshot");
252
+ }
253
+ if (surfaceConvergeState === "unverified") {
254
+ warnings.push(surfaceSummary ?? "daemon runtime vs installed hook/runtime-guard convergence is not fully proven from this verification snapshot");
255
+ }
185
256
  // When runtime proof is green for the repaired profile, promote the proof
186
257
  // surface to healthy rather than leaving stale degraded warnings that
187
258
  // contradict the proven truth.
@@ -66,6 +66,58 @@ function normalizeSurfacePath(filePath) {
66
66
  ? path.resolve(filePath)
67
67
  : null;
68
68
  }
69
+ function classifyRuntimeSurfaceConvergeState(input) {
70
+ const reasons = [];
71
+ if (input.scope === "activation_root_only") {
72
+ reasons.push("selected_openclaw_home_unverified");
73
+ return {
74
+ state: "unverified",
75
+ reasons
76
+ };
77
+ }
78
+ if (input.daemonPath === null) {
79
+ reasons.push("daemon_surface_unverified");
80
+ }
81
+ if (input.hookPath === null && input.runtimeGuardPath === null) {
82
+ reasons.push("installed_surface_unverified");
83
+ return {
84
+ state: input.daemonPath === null ? "unverified" : "half_converged",
85
+ reasons
86
+ };
87
+ }
88
+ if (input.hookLoadability !== "loadable") {
89
+ reasons.push("installed_surface_not_loadable");
90
+ return {
91
+ state: input.daemonPath === null ? "unverified" : "half_converged",
92
+ reasons
93
+ };
94
+ }
95
+ if (input.daemonPath === null) {
96
+ return {
97
+ state: "unverified",
98
+ reasons
99
+ };
100
+ }
101
+ if (input.samePath) {
102
+ reasons.push("same_path");
103
+ return {
104
+ state: "converged",
105
+ reasons
106
+ };
107
+ }
108
+ if (input.daemonVersion !== null && input.hookVersion !== null) {
109
+ reasons.push(input.daemonVersion === input.hookVersion ? "split_path_same_version" : "version_skew");
110
+ return {
111
+ state: input.daemonVersion === input.hookVersion ? "converged" : "half_converged",
112
+ reasons
113
+ };
114
+ }
115
+ reasons.push("version_unverified");
116
+ return {
117
+ state: "unverified",
118
+ reasons
119
+ };
120
+ }
69
121
  function inspectInstalledHookActivationRoot(loaderEntryPath) {
70
122
  let content;
71
123
  try {
@@ -328,77 +380,101 @@ export function describeOpenClawBrainHotfixBoundary(input) {
328
380
  const daemonPath = normalizeSurfacePath(daemonInspection?.configuredRuntimePath ?? null);
329
381
  const hookPath = normalizeSurfacePath(hookInspection.hookPath);
330
382
  const runtimeGuardPath = normalizeSurfacePath(hookInspection.runtimeGuardPath);
383
+ const selectedOpenClawHome = typeof hookInspection.openclawHome === "string" && hookInspection.openclawHome.trim().length > 0
384
+ ? path.resolve(hookInspection.openclawHome)
385
+ : null;
386
+ const daemonSource = daemonInspection?.surfaceIdentitySource ?? (daemonPath === null ? "unverified" : "managed_service");
387
+ const daemonPackageName = daemonInspection?.configuredRuntimePackageName ?? null;
388
+ const daemonPackageVersion = daemonInspection?.configuredRuntimePackageVersion ?? null;
389
+ const hookPackageName = hookInspection.packageName ?? null;
390
+ const hookPackageVersion = hookInspection.packageVersion ?? null;
331
391
  const daemonPackage = formatPackageIdentity(daemonInspection?.configuredRuntimePackageName ?? null, daemonInspection?.configuredRuntimePackageVersion ?? null, daemonInspection?.configuredRuntimePackageSpec ?? null);
332
392
  const hookPackage = formatPackageIdentity(hookInspection.packageName, hookInspection.packageVersion);
393
+ const samePath = daemonPath === hookPath || daemonPath === runtimeGuardPath;
394
+ const daemonVersion = daemonPackageVersion;
395
+ const hookVersion = hookPackageVersion;
396
+ let boundary;
397
+ let skew;
398
+ let displayedHookPath = hookPath;
399
+ let displayedRuntimeGuardPath = runtimeGuardPath;
400
+ let displayedDaemonPackage = daemonPackage;
401
+ let displayedHookPackage = hookPackage;
402
+ let guidance;
403
+ let detail;
333
404
  if (hookInspection.scope === "activation_root_only") {
334
- return {
335
- boundary: "hook_surface_unverified",
336
- skew: "unverified",
337
- daemonPath,
338
- hookPath: null,
339
- runtimeGuardPath: null,
340
- daemonPackage,
341
- hookPackage: null,
342
- guidance: "Pin --openclaw-home before patching the installed hook/runtime-guard surface; activation-root-only status does not prove which OpenClaw install you would be changing.",
343
- detail: daemonPath === null
344
- ? "activation-root-only status does not expose the installed hook/runtime-guard surface."
345
- : `daemon runtime path ${shortenPath(daemonPath)} is visible, but activation-root-only status still does not expose the installed hook/runtime-guard surface.`
346
- };
405
+ boundary = "hook_surface_unverified";
406
+ skew = "unverified";
407
+ displayedHookPath = null;
408
+ displayedRuntimeGuardPath = null;
409
+ displayedHookPackage = null;
410
+ guidance = "Pin --openclaw-home before patching the installed hook/runtime-guard surface; activation-root-only status does not prove which OpenClaw install you would be changing.";
411
+ detail = daemonPath === null
412
+ ? "activation-root-only status does not expose the installed hook/runtime-guard surface."
413
+ : `daemon runtime path ${shortenPath(daemonPath)} is visible, but activation-root-only status still does not expose the installed hook/runtime-guard surface.`;
347
414
  }
348
- if (hookPath === null && runtimeGuardPath === null) {
349
- return {
350
- boundary: "hook_surface_unverified",
351
- skew: "unverified",
352
- daemonPath,
353
- hookPath: null,
354
- runtimeGuardPath: null,
355
- daemonPackage,
356
- hookPackage,
357
- guidance: daemonPath === null
358
- ? "Repair or reinstall the installed hook surface before patching OpenClaw load behavior."
359
- : "Patch the daemon runtime path for background watch fixes, but repair or reinstall the installed hook/runtime-guard surface before patching OpenClaw load behavior.",
360
- detail: daemonPath === null
361
- ? "no verified daemon runtime path or installed hook/runtime-guard path is visible from this status snapshot."
362
- : `daemon runtime path ${shortenPath(daemonPath)} is visible, but the installed hook/runtime-guard surface is not yet loadable.`
363
- };
415
+ else if (hookPath === null && runtimeGuardPath === null) {
416
+ boundary = "hook_surface_unverified";
417
+ skew = "unverified";
418
+ displayedHookPath = null;
419
+ displayedRuntimeGuardPath = null;
420
+ guidance = daemonPath === null
421
+ ? "Repair or reinstall the installed hook surface before patching OpenClaw load behavior."
422
+ : "Patch the daemon runtime path for background watch fixes, but repair or reinstall the installed hook/runtime-guard surface before patching OpenClaw load behavior.";
423
+ detail = daemonPath === null
424
+ ? "no verified daemon runtime path or installed hook/runtime-guard path is visible from this status snapshot."
425
+ : `daemon runtime path ${shortenPath(daemonPath)} is visible, but the installed hook/runtime-guard surface is not yet loadable.`;
364
426
  }
365
- if (daemonPath === null) {
366
- return {
367
- boundary: "daemon_surface_only",
368
- skew: "unverified",
369
- daemonPath: null,
370
- hookPath,
371
- runtimeGuardPath,
372
- daemonPackage: null,
373
- hookPackage,
374
- guidance: "Patch the installed hook/runtime-guard surface for OpenClaw load fixes. No configured daemon runtime path is visible from status.",
375
- detail: `installed hook loads from ${hookPath === null ? "unverified" : shortenPath(hookPath)}${runtimeGuardPath === null ? "" : ` with runtime-guard ${shortenPath(runtimeGuardPath)}`}, but no configured daemon runtime path is visible.`
376
- };
427
+ else if (daemonPath === null) {
428
+ boundary = "daemon_surface_only";
429
+ skew = "unverified";
430
+ displayedDaemonPackage = null;
431
+ guidance = "Patch the installed hook/runtime-guard surface for OpenClaw load fixes. No configured daemon runtime path is visible from status.";
432
+ detail = `installed hook loads from ${hookPath === null ? "unverified" : shortenPath(hookPath)}${runtimeGuardPath === null ? "" : ` with runtime-guard ${shortenPath(runtimeGuardPath)}`}, but no configured daemon runtime path is visible.`;
377
433
  }
378
- const samePath = daemonPath === hookPath || daemonPath === runtimeGuardPath;
379
- const daemonVersion = daemonInspection?.configuredRuntimePackageVersion ?? null;
380
- const hookVersion = hookInspection.packageVersion ?? null;
381
- const skew = samePath
382
- ? "same_path"
383
- : daemonVersion !== null && hookVersion !== null
384
- ? daemonVersion === hookVersion
385
- ? "split_path_same_version"
386
- : "split_path_version_skew"
387
- : "split_path_version_unverified";
434
+ else {
435
+ skew = samePath
436
+ ? "same_path"
437
+ : daemonVersion !== null && hookVersion !== null
438
+ ? daemonVersion === hookVersion
439
+ ? "split_path_same_version"
440
+ : "split_path_version_skew"
441
+ : "split_path_version_unverified";
442
+ boundary = samePath ? "same_surface" : "split_surfaces";
443
+ guidance = samePath
444
+ ? "Daemon and installed hook paths currently collapse to the same file; verify both surfaces before patching anyway."
445
+ : "Patch the daemon runtime path for background watch/learner fixes. Patch the installed hook/runtime-guard paths for OpenClaw load fixes.";
446
+ detail = samePath
447
+ ? `daemon runtime path ${shortenPath(daemonPath)} currently resolves to the same file as the installed OpenClaw hook surface.`
448
+ : `daemon background watch runs from ${shortenPath(daemonPath)}${daemonPackage === null ? "" : ` (${daemonPackage})`}; OpenClaw loads the installed hook from ${hookPath === null ? "unverified" : shortenPath(hookPath)}${hookPackage === null ? "" : ` (${hookPackage})`}${runtimeGuardPath === null ? "" : ` and runtime-guard ${shortenPath(runtimeGuardPath)}`}.`;
449
+ }
450
+ const converge = classifyRuntimeSurfaceConvergeState({
451
+ scope: hookInspection.scope,
452
+ daemonPath,
453
+ hookPath: displayedHookPath,
454
+ runtimeGuardPath: displayedRuntimeGuardPath,
455
+ hookLoadability: hookInspection.loadability,
456
+ daemonVersion,
457
+ hookVersion,
458
+ samePath
459
+ });
388
460
  return {
389
- boundary: samePath ? "same_surface" : "split_surfaces",
461
+ boundary,
390
462
  skew,
391
463
  daemonPath,
392
- hookPath,
393
- runtimeGuardPath,
394
- daemonPackage,
395
- hookPackage,
396
- guidance: samePath
397
- ? "Daemon and installed hook paths currently collapse to the same file; verify both surfaces before patching anyway."
398
- : "Patch the daemon runtime path for background watch/learner fixes. Patch the installed hook/runtime-guard paths for OpenClaw load fixes.",
399
- detail: samePath
400
- ? `daemon runtime path ${shortenPath(daemonPath)} currently resolves to the same file as the installed OpenClaw hook surface.`
401
- : `daemon background watch runs from ${shortenPath(daemonPath)}${daemonPackage === null ? "" : ` (${daemonPackage})`}; OpenClaw loads the installed hook from ${hookPath === null ? "unverified" : shortenPath(hookPath)}${hookPackage === null ? "" : ` (${hookPackage})`}${runtimeGuardPath === null ? "" : ` and runtime-guard ${shortenPath(runtimeGuardPath)}`}.`
464
+ hookPath: displayedHookPath,
465
+ runtimeGuardPath: displayedRuntimeGuardPath,
466
+ daemonPackage: displayedDaemonPackage,
467
+ hookPackage: displayedHookPackage,
468
+ daemonPackageName,
469
+ daemonPackageVersion,
470
+ hookPackageName,
471
+ hookPackageVersion,
472
+ daemonSource,
473
+ selectedOpenClawHome,
474
+ convergeState: converge.state,
475
+ convergeReasons: converge.reasons,
476
+ guidance,
477
+ detail
402
478
  };
403
479
  }
404
480
  //# sourceMappingURL=openclaw-hook-truth.js.map
@@ -279,6 +279,7 @@ function extractStartupBreadcrumbs(logText, bundleStartedAtIso) {
279
279
  }
280
280
 
281
281
  function extractStatusSignals(statusText) {
282
+ const surface = extractKeyValuePairs(extractDetailedStatusLine(statusText, "surface"));
282
283
  return {
283
284
  statusOk: /^STATUS ok$/m.test(statusText),
284
285
  loadProofReady: /loadProof=status_probe_ready/.test(statusText),
@@ -288,6 +289,11 @@ function extractStatusSignals(statusText) {
288
289
  routeFnAvailable: /routeFn\s+available=yes/.test(statusText),
289
290
  proofPath: statusText.match(/proofPath=([^\s]+)/)?.[1] ?? null,
290
291
  proofError: statusText.match(/proofError=([^\s]+)/)?.[1] ?? null,
292
+ surfaceBoundary: surface.boundary ?? null,
293
+ surfaceSkew: surface.skew ?? null,
294
+ surfaceConvergeState: surface.converge ?? null,
295
+ surfaceDaemonSource: surface.daemonSource ?? null,
296
+ surfaceSelectedHome: surface.selectedHome ?? null,
291
297
  };
292
298
  }
293
299
  function extractDetailedStatusLine(statusText, prefix) {
@@ -378,10 +384,11 @@ function buildCoverageSnapshot({ attachedSetLine, runtimeLoadProofSnapshot, open
378
384
  profiles
379
385
  };
380
386
  }
381
- function buildHardeningSnapshot({ attachTruthLine, serveLine, routeFnLine, verdict, statusSignals }) {
387
+ function buildHardeningSnapshot({ attachTruthLine, serveLine, routeFnLine, surfaceLine, verdict, statusSignals }) {
382
388
  const attachTruth = extractKeyValuePairs(attachTruthLine);
383
389
  const serve = extractKeyValuePairs(serveLine);
384
390
  const routeFn = extractKeyValuePairs(routeFnLine);
391
+ const surface = extractKeyValuePairs(surfaceLine);
385
392
  return {
386
393
  contract: "openclaw_operator_hardening_snapshot.v1",
387
394
  generatedAt: new Date().toISOString(),
@@ -391,6 +398,7 @@ function buildHardeningSnapshot({ attachTruthLine, serveLine, routeFnLine, verdi
391
398
  runtimeProven: statusSignals.runtimeProven,
392
399
  serveActivePack: statusSignals.serveActivePack,
393
400
  routeFnAvailable: statusSignals.routeFnAvailable,
401
+ surfaceConverged: statusSignals.surfaceConvergeState === "converged",
394
402
  },
395
403
  attachTruth: {
396
404
  current: attachTruth.current ?? null,
@@ -399,6 +407,15 @@ function buildHardeningSnapshot({ attachTruthLine, serveLine, routeFnLine, verdi
399
407
  runtime: attachTruth.runtime ?? null,
400
408
  watcher: attachTruth.watcher ?? null,
401
409
  },
410
+ surface: {
411
+ boundary: surface.boundary ?? null,
412
+ skew: surface.skew ?? null,
413
+ converge: surface.converge ?? null,
414
+ daemonSource: surface.daemonSource ?? null,
415
+ selectedHome: surface.selectedHome ?? null,
416
+ daemon: surface.daemon ?? null,
417
+ hook: surface.hook ?? null,
418
+ },
402
419
  serve: {
403
420
  state: serve.state ?? null,
404
421
  failOpen: serve.failOpen ?? null,
@@ -450,6 +467,8 @@ function buildVerdict({ steps, gatewayStatus, pluginInspect, statusSignals, brea
450
467
  && statusSignals.runtimeProven
451
468
  && statusSignals.serveActivePack
452
469
  && statusSignals.routeFnAvailable;
470
+ const surfaceConverged = statusSignals.surfaceConvergeState === "converged";
471
+ const surfaceHalfConverged = statusSignals.surfaceConvergeState === "half_converged";
453
472
  if (!statusSignals.loadProofReady)
454
473
  runtimeTruthGaps.push("load_proof");
455
474
  if (!statusSignals.runtimeProven)
@@ -458,6 +477,9 @@ function buildVerdict({ steps, gatewayStatus, pluginInspect, statusSignals, brea
458
477
  runtimeTruthGaps.push("serve_active_pack");
459
478
  if (!statusSignals.routeFnAvailable)
460
479
  runtimeTruthGaps.push("route_fn");
480
+ if (surfaceHalfConverged) {
481
+ runtimeTruthGaps.push("surface_converged");
482
+ }
461
483
  const warningCodes = [];
462
484
  const warnings = [];
463
485
  if (!statusSignals.statusOk) {
@@ -495,6 +517,14 @@ function buildVerdict({ steps, gatewayStatus, pluginInspect, statusSignals, brea
495
517
  ? "runtime-load-proof snapshot did not include the current openclaw home"
496
518
  : "runtime-load-proof snapshot was missing");
497
519
  }
520
+ if (surfaceHalfConverged) {
521
+ warningCodes.push(`surface_half_converged:${statusSignals.surfaceSkew ?? "unknown"}`);
522
+ warnings.push(`detailed status reported daemon-vs-installed-surface half-converged (${statusSignals.surfaceSkew ?? "unknown"})`);
523
+ }
524
+ else if (!surfaceConverged) {
525
+ warningCodes.push(`surface_unverified:${statusSignals.surfaceConvergeState ?? "unknown"}`);
526
+ warnings.push("detailed status did not fully prove daemon-vs-installed-surface convergence");
527
+ }
498
528
  if (statusSignals.proofError !== null && statusSignals.proofError !== "none") {
499
529
  warningCodes.push(`proof_error:${statusSignals.proofError}`);
500
530
  warnings.push(`detailed status reported proofError=${statusSignals.proofError}`);
@@ -546,7 +576,7 @@ function buildVerdict({ steps, gatewayStatus, pluginInspect, statusSignals, brea
546
576
  };
547
577
  }
548
578
 
549
- function buildSummary({ options, steps, verdict, gatewayStatusText, pluginInspectText, statusSignals, breadcrumbs, runtimeLoadProofSnapshot, surfaceLine, surfacesLine, hotfixLine, guardLine, feedbackLine, attributionLine, attributionCoverageLine, learningPathLine, learningFlowLine, learningHealthLine, coverageSnapshot, hardeningSnapshot }) {
579
+ function buildSummary({ options, steps, verdict, gatewayStatusText, pluginInspectText, statusSignals, breadcrumbs, runtimeLoadProofSnapshot, surfaceLine, surfacesLine, surfaceNoteLine, hotfixLine, guardLine, feedbackLine, attributionLine, attributionCoverageLine, learningPathLine, learningFlowLine, learningHealthLine, coverageSnapshot, hardeningSnapshot }) {
550
580
  const passed = [];
551
581
  const missing = [];
552
582
  const warnings = Array.isArray(verdict.warnings) ? verdict.warnings : [];
@@ -574,11 +604,20 @@ function buildSummary({ options, steps, verdict, gatewayStatusText, pluginInspec
574
604
  if (statusSignals.routeFnAvailable) {
575
605
  passed.push("detailed status reported routeFn available=yes");
576
606
  }
607
+ if (statusSignals.surfaceConvergeState === "converged") {
608
+ passed.push("detailed status reported daemon-vs-installed-surface convergence");
609
+ }
577
610
  if (breadcrumbs.afterBundleStart.some((entry) => entry.kind === "loaded")) {
578
611
  passed.push("startup log contained a post-bundle [openclawbrain] BRAIN LOADED breadcrumb");
579
612
  }
580
613
  if (!statusSignals.loadProofReady)
581
614
  missing.push("detailed status did not prove hook load");
615
+ if (statusSignals.surfaceConvergeState === "half_converged") {
616
+ missing.push(`detailed status reported daemon-vs-installed-surface half-converged (${statusSignals.surfaceSkew ?? "unknown"})`);
617
+ }
618
+ else if (statusSignals.surfaceConvergeState !== "converged") {
619
+ missing.push("detailed status did not fully prove daemon-vs-installed-surface convergence");
620
+ }
582
621
  if (!breadcrumbs.afterBundleStart.some((entry) => entry.kind === "loaded"))
583
622
  missing.push("no post-bundle startup breadcrumb was found");
584
623
  if (runtimeLoadProofSnapshot.path === null)
@@ -609,6 +648,9 @@ function buildSummary({ options, steps, verdict, gatewayStatusText, pluginInspec
609
648
  ...(surfacesLine === null
610
649
  ? ["- hotfix paths line not reported by detailed status"]
611
650
  : [`- ${surfacesLine}`]),
651
+ ...(surfaceNoteLine === null
652
+ ? ["- hotfix detail line not reported by detailed status"]
653
+ : [`- ${surfaceNoteLine}`]),
612
654
  ...(hotfixLine === null
613
655
  ? ["- hotfix guidance line not reported by detailed status"]
614
656
  : [`- ${hotfixLine}`]),
@@ -649,7 +691,8 @@ function buildSummary({ options, steps, verdict, gatewayStatusText, pluginInspec
649
691
  : coverageSnapshot.profiles.map((entry) => `- ${entry.current ? "*" : ""}${entry.label} coverage=${entry.coverageState} hook=${entry.hookFiles} config=${entry.configLoad} runtime=${entry.runtimeLoad} loadedAt=${entry.loadedAt ?? "none"}`)),
650
692
  "",
651
693
  "## Hardening snapshot",
652
- `- status signals: statusOk=${hardeningSnapshot.statusSignals.statusOk} loadProofReady=${hardeningSnapshot.statusSignals.loadProofReady} runtimeProven=${hardeningSnapshot.statusSignals.runtimeProven} serveActivePack=${hardeningSnapshot.statusSignals.serveActivePack} routeFnAvailable=${hardeningSnapshot.statusSignals.routeFnAvailable}`,
694
+ `- status signals: statusOk=${hardeningSnapshot.statusSignals.statusOk} loadProofReady=${hardeningSnapshot.statusSignals.loadProofReady} runtimeProven=${hardeningSnapshot.statusSignals.runtimeProven} serveActivePack=${hardeningSnapshot.statusSignals.serveActivePack} routeFnAvailable=${hardeningSnapshot.statusSignals.routeFnAvailable} surfaceConverged=${hardeningSnapshot.statusSignals.surfaceConverged}`,
695
+ `- surface: boundary=${hardeningSnapshot.surface.boundary ?? "none"} skew=${hardeningSnapshot.surface.skew ?? "none"} converge=${hardeningSnapshot.surface.converge ?? "none"} daemonSource=${hardeningSnapshot.surface.daemonSource ?? "none"} daemon=${hardeningSnapshot.surface.daemon ?? "none"} hook=${hardeningSnapshot.surface.hook ?? "none"} selectedHome=${hardeningSnapshot.surface.selectedHome ?? "none"}`,
653
696
  `- serve: state=${hardeningSnapshot.serve.state ?? "none"} failOpen=${hardeningSnapshot.serve.failOpen ?? "none"} hardFail=${hardeningSnapshot.serve.hardFail ?? "none"} usedRouteFn=${hardeningSnapshot.serve.usedRouteFn ?? "none"}`,
654
697
  `- attachTruth: current=${hardeningSnapshot.attachTruth.current ?? "none"} hook=${hardeningSnapshot.attachTruth.hook ?? "none"} config=${hardeningSnapshot.attachTruth.config ?? "none"} runtime=${hardeningSnapshot.attachTruth.runtime ?? "none"}`,
655
698
  `- proof verdict: ${hardeningSnapshot.verdict.verdict} severity=${hardeningSnapshot.verdict.severity} warnings=${hardeningSnapshot.verdict.warningCount}`,
@@ -702,6 +745,7 @@ export function buildProofCommandHelpSection() {
702
745
  return {
703
746
  usage: " openclawbrain proof --openclaw-home <path> [options]",
704
747
  optionLines: [
748
+ " --openclaw-home <path> Target one explicit OpenClaw home (for example ./openclaw-cormorantai or ~/.openclaw).",
705
749
  " --output-dir <path> Bundle directory for proof artifacts (proof only). Defaults to ./artifacts/operator-proof-<timestamp>.",
706
750
  " --skip-install Capture proof without rerunning install first (proof only).",
707
751
  " --skip-restart Capture proof without restarting OpenClaw first (proof only).",
@@ -937,6 +981,7 @@ export function captureOperatorProofBundle(options) {
937
981
  const statusSignals = extractStatusSignals(statusCapture.stdout);
938
982
  const surfaceLine = extractDetailedStatusLine(statusCapture.stdout, "surface");
939
983
  const surfacesLine = extractDetailedStatusLine(statusCapture.stdout, "surfaces");
984
+ const surfaceNoteLine = extractDetailedStatusLine(statusCapture.stdout, "surfaceNote");
940
985
  const hotfixLine = extractDetailedStatusLine(statusCapture.stdout, "hotfix");
941
986
  const attachTruthLine = extractDetailedStatusLine(statusCapture.stdout, "attachTruth");
942
987
  const attachedSetLine = extractDetailedStatusLine(statusCapture.stdout, "attachedSet");
@@ -978,6 +1023,7 @@ export function captureOperatorProofBundle(options) {
978
1023
  attachTruthLine,
979
1024
  serveLine,
980
1025
  routeFnLine,
1026
+ surfaceLine,
981
1027
  verdict,
982
1028
  statusSignals,
983
1029
  });
@@ -1004,6 +1050,7 @@ export function captureOperatorProofBundle(options) {
1004
1050
  runtimeLoadProofError: runtimeLoadProofSnapshot.error,
1005
1051
  surfaceLine,
1006
1052
  surfacesLine,
1053
+ surfaceNoteLine,
1007
1054
  hotfixLine,
1008
1055
  guardLine,
1009
1056
  learningFlowLine,
@@ -1025,6 +1072,7 @@ export function captureOperatorProofBundle(options) {
1025
1072
  runtimeLoadProofSnapshot,
1026
1073
  surfaceLine,
1027
1074
  surfacesLine,
1075
+ surfaceNoteLine,
1028
1076
  hotfixLine,
1029
1077
  guardLine,
1030
1078
  learningFlowLine,
@@ -1051,6 +1099,7 @@ export function captureOperatorProofBundle(options) {
1051
1099
  statusSignals,
1052
1100
  surfaceLine,
1053
1101
  surfacesLine,
1102
+ surfaceNoteLine,
1054
1103
  hotfixLine,
1055
1104
  guardLine,
1056
1105
  learningFlowLine,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openclawbrain/cli",
3
- "version": "0.4.28",
3
+ "version": "0.4.30",
4
4
  "description": "OpenClawBrain operator CLI package with install/status helpers, daemon controls, and import/export tooling.",
5
5
  "type": "module",
6
6
  "main": "./dist/src/index.js",