@openclawbrain/cli 0.4.29 → 0.4.31

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
@@ -562,7 +562,7 @@ function operatorCliHelp() {
562
562
  " openclawbrain-ops scan --session <trace.json> --root <path> [options] # compatibility alias",
563
563
  "",
564
564
  "Options:",
565
- " --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.",
566
566
  " --shared Set brain-attachment-policy to shared instead of dedicated (install/attach only).",
567
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.`,
568
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.",
@@ -602,6 +602,11 @@ function operatorCliHelp() {
602
602
  " 9. detach openclawbrain detach --openclaw-home <path> — remove the profile hookup only and keep brain data",
603
603
  " 10. uninstall openclawbrain uninstall --openclaw-home <path> --keep-data|--purge-data — remove the hookup and choose the data outcome explicitly",
604
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
+ "",
605
610
  "Advanced/operator surfaces:",
606
611
  " context preview the brain context that would be injected for a message",
607
612
  " rollback preview or apply active <- previous, active -> candidate pointer movement",
@@ -955,16 +960,69 @@ function summarizeStatusAttachmentTruth(input) {
955
960
  })
956
961
  };
957
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
+ }
958
1013
  function summarizeStatusHotfixBoundary(status) {
959
1014
  return describeOpenClawBrainHotfixBoundary({
960
1015
  hookInspection: status.hook,
961
- daemonInspection: inspectManagedLearnerService(status.host.activationRoot)
1016
+ daemonInspection: inspectDaemonRuntimeSurface(status.host.activationRoot)
962
1017
  });
963
1018
  }
964
1019
  function formatStatusHotfixBoundarySummary(boundary) {
965
1020
  return [
966
1021
  `boundary=${boundary.boundary}`,
967
1022
  `skew=${boundary.skew}`,
1023
+ `converge=${boundary.convergeState ?? "unverified"}`,
1024
+ `daemonSource=${boundary.daemonSource ?? "unverified"}`,
1025
+ `selectedHome=${boundary.selectedOpenClawHome === null ? "unverified" : shortenPath(boundary.selectedOpenClawHome)}`,
968
1026
  `daemon=${boundary.daemonPackage ?? "unverified"}`,
969
1027
  `hook=${boundary.hookPackage ?? "unverified"}`
970
1028
  ].join(" ");
@@ -1649,6 +1707,8 @@ function readInstallRuntimeFingerprint(openclawHome) {
1649
1707
  openclawHome,
1650
1708
  quiet: true
1651
1709
  });
1710
+ const activationRoot = resolvedActivationRoot.trim().length === 0 ? null : path.resolve(resolvedActivationRoot);
1711
+ const daemonSurface = activationRoot === null ? null : inspectDaemonRuntimeSurface(activationRoot);
1652
1712
  const { config } = readOpenClawJsonConfig(openclawHome);
1653
1713
  return {
1654
1714
  selectedInstall: selectedInstall === null
@@ -1664,10 +1724,15 @@ function readInstallRuntimeFingerprint(openclawHome) {
1664
1724
  hookPath: hook.hookPath,
1665
1725
  hookState: hook.installState,
1666
1726
  loadability: hook.loadability,
1667
- activationRoot: resolvedActivationRoot.trim().length === 0 ? null : path.resolve(resolvedActivationRoot),
1727
+ activationRoot,
1668
1728
  loaderSource: readTextFileIfExists(selectedInstall?.loaderEntryPath ?? null),
1669
1729
  runtimeGuardSource: readTextFileIfExists(selectedInstall?.runtimeGuardPath ?? null),
1670
- 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
1671
1736
  };
1672
1737
  }
1673
1738
  function runOpenClawBrainConvergePluginStep(openclawHome) {
@@ -1738,7 +1803,22 @@ function inspectInstallConvergeVerification(parsed) {
1738
1803
  status: normalizedStatusAndReport.status
1739
1804
  });
1740
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;
1741
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;
1742
1822
  return {
1743
1823
  targetInspection,
1744
1824
  status: normalizedStatusAndReport.status,
@@ -1747,11 +1827,9 @@ function inspectInstallConvergeVerification(parsed) {
1747
1827
  attachmentTruth,
1748
1828
  displayedStatus,
1749
1829
  routeFn,
1750
- nextStep: buildStatusNextStep(normalizedStatusAndReport.status, normalizedStatusAndReport.report, {
1751
- openclawHome: parsed.openclawHome,
1752
- installHook
1753
- }),
1754
- 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}`,
1755
1833
  facts: {
1756
1834
  installLayout: normalizedStatusAndReport.status.hook.installLayout ?? installHook.installLayout ?? null,
1757
1835
  installState: installHook.state,
@@ -1763,7 +1841,17 @@ function inspectInstallConvergeVerification(parsed) {
1763
1841
  loadProof: normalizedStatusAndReport.status.hook.loadProof,
1764
1842
  serveState: normalizedStatusAndReport.status.brainStatus.serveState,
1765
1843
  routeFnAvailable: routeFn.available,
1766
- 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
1767
1855
  }
1768
1856
  };
1769
1857
  }
@@ -4065,6 +4153,8 @@ function emitInstallConvergeResult(result, parsed) {
4065
4153
  console.log(`Attach: ${result.attach.detail}`);
4066
4154
  console.log(`Restart: ${result.restart.detail}`);
4067
4155
  console.log(`Verify: ${result.verification.summaryLine}`);
4156
+ console.log(`Surface: ${result.verification.surface.summaryLine}`);
4157
+ console.log(`Paths: ${result.verification.surface.pathsLine}`);
4068
4158
  console.log(`Verdict: ${result.verdict.verdict}`);
4069
4159
  console.log(`Why: ${result.verdict.why}`);
4070
4160
  if (result.verdict.warnings.length > 0) {
@@ -4126,7 +4216,22 @@ function runInstallCommand(parsed) {
4126
4216
  loadProof: "unknown",
4127
4217
  serveState: "unknown",
4128
4218
  routeFnAvailable: false,
4129
- 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
+ }
4130
4235
  },
4131
4236
  verdict
4132
4237
  };
@@ -4211,7 +4316,22 @@ function runInstallCommand(parsed) {
4211
4316
  loadProof: verificationSnapshot.facts.loadProof,
4212
4317
  serveState: verificationSnapshot.facts.serveState,
4213
4318
  routeFnAvailable: verificationSnapshot.facts.routeFnAvailable,
4214
- 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
+ }
4215
4335
  },
4216
4336
  verdict,
4217
4337
  underlyingInstall: attachResult
@@ -1820,6 +1820,7 @@ interface OperatorHookSummary {
1820
1820
  manifestId: string | null;
1821
1821
  installId: string | null;
1822
1822
  packageName: string | null;
1823
+ packageVersion: string | null;
1823
1824
  installLayout: import("./openclaw-plugin-install.js").OpenClawBrainInstallLayout | null;
1824
1825
  additionalInstallCount: number;
1825
1826
  installState: CurrentProfileHookInstallStateV1;
package/dist/src/index.js CHANGED
@@ -7832,6 +7832,7 @@ function buildCurrentProfileBrainStatusFromReport(report, policyMode, profileId)
7832
7832
  manifestId: report.hook.manifestId,
7833
7833
  installId: report.hook.installId,
7834
7834
  packageName: report.hook.packageName,
7835
+ packageVersion: report.hook.packageVersion,
7835
7836
  installLayout: report.hook.installLayout,
7836
7837
  additionalInstallCount: report.hook.additionalInstallCount,
7837
7838
  installState: report.hook.installState,
@@ -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 {
@@ -205,7 +257,7 @@ export function inspectOpenClawBrainHookStatus(openclawHome) {
205
257
  manifestId: incompleteInstall?.manifestId ?? null,
206
258
  installId: incompleteInstall?.installId ?? null,
207
259
  packageName: incompleteInstall?.packageName ?? null,
208
- packageVersion: readInstalledHookPackageVersion(incompleteInstall?.packageJsonPath ?? null),
260
+ packageVersion: incompleteInstall?.packageVersion ?? readInstalledHookPackageVersion(incompleteInstall?.packageJsonPath ?? null) ?? incompleteInstall?.manifestVersion ?? null,
209
261
  installLayout: incompleteInstall?.installLayout ?? null,
210
262
  additionalInstallCount: installedPlugin.additionalInstalls.length,
211
263
  installState: "not_installed",
@@ -222,7 +274,7 @@ export function inspectOpenClawBrainHookStatus(openclawHome) {
222
274
  const allowlist = inspectOpenClawBrainPluginAllowlist(resolvedHome);
223
275
  const layoutLabel = describeOpenClawBrainInstallLayout(selectedInstall.installLayout);
224
276
  const identityDetail = describeOpenClawBrainInstallIdentity(selectedInstall);
225
- const packageVersion = readInstalledHookPackageVersion(selectedInstall.packageJsonPath);
277
+ const packageVersion = selectedInstall.packageVersion ?? readInstalledHookPackageVersion(selectedInstall.packageJsonPath) ?? selectedInstall.manifestVersion ?? null;
226
278
  const activationRootState = inspectInstalledHookActivationRoot(selectedInstall.loaderEntryPath);
227
279
  if (allowlist.state === "blocked") {
228
280
  return {
@@ -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
@@ -164,6 +164,10 @@ function inspectOpenClawBrainPluginInstall(extensionDir, pluginId) {
164
164
  const packageName = typeof packageJson.name === "string" && packageJson.name.trim().length > 0
165
165
  ? packageJson.name.trim()
166
166
  : null;
167
+ const packageVersion = typeof packageJson.version === "string" && packageJson.version.trim().length > 0
168
+ ? packageJson.version.trim()
169
+ : null;
170
+ const manifestVersion = normalizeOptionalString(manifest?.version);
167
171
  const installId = normalizeInstallId(packageName) ?? normalizeInstallId(path.basename(extensionDir));
168
172
  const installLayout = inferInstallLayout({
169
173
  extensionDir,
@@ -182,8 +186,10 @@ function inspectOpenClawBrainPluginInstall(extensionDir, pluginId) {
182
186
  runtimeGuardPath: loaderEntryPath === null ? null : resolveRuntimeGuardPath(loaderEntryPath),
183
187
  configuredEntries,
184
188
  manifestId,
189
+ manifestVersion,
185
190
  installId,
186
191
  packageName,
192
+ packageVersion,
187
193
  installLayout
188
194
  };
189
195
  }
@@ -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.29",
3
+ "version": "0.4.31",
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",