@openclawbrain/cli 0.4.19 → 0.4.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -1
- package/dist/src/cli.js +70 -7
- package/dist/src/daemon.js +83 -5
- package/dist/src/install-converge.js +39 -0
- package/dist/src/proof-command.js +22 -6
- package/dist/src/session-store.js +20 -0
- package/extension/index.ts +2 -2
- package/extension/runtime-guard.ts +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @openclawbrain/cli
|
|
2
2
|
|
|
3
|
-
Operator CLI for OpenClawBrain.
|
|
3
|
+
Operator CLI internals for OpenClawBrain.
|
|
4
4
|
|
|
5
5
|
The public front door is one command pinned to one OpenClaw home:
|
|
6
6
|
|
|
@@ -20,6 +20,8 @@ openclawbrain proof --openclaw-home ~/.openclaw
|
|
|
20
20
|
|
|
21
21
|
The intended canonical lane is the same install command with optional `--proof`. Until that lands cleanly across the operator surfaces, proof stays a separate follow-up command. `proof` writes `summary.md`, `steps.json`, `verdict.json`, raw step logs, and proof pointers under one bundle directory.
|
|
22
22
|
|
|
23
|
+
This package is part of the internal split architecture. Public docs should lead with OpenClawBrain and the `openclawbrain install` lane, not with package-pair trivia.
|
|
24
|
+
|
|
23
25
|
## Common commands
|
|
24
26
|
|
|
25
27
|
```bash
|
package/dist/src/cli.js
CHANGED
|
@@ -6,7 +6,7 @@ import { fileURLToPath, pathToFileURL } from "node:url";
|
|
|
6
6
|
const __filename = fileURLToPath(import.meta.url);
|
|
7
7
|
const __dirname = path.dirname(__filename);
|
|
8
8
|
import { DEFAULT_OLLAMA_EMBEDDING_MODEL, createOllamaEmbedder } from "@openclawbrain/compiler";
|
|
9
|
-
import { ensureManagedLearnerServiceForActivationRoot, inspectManagedLearnerService, removeManagedLearnerServiceForActivationRoot, parseDaemonArgs, runDaemonCommand } from "./daemon.js";
|
|
9
|
+
import { describeManagedLearnerServiceRuntimeGuard, ensureManagedLearnerServiceForActivationRoot, inspectManagedLearnerService, removeManagedLearnerServiceForActivationRoot, parseDaemonArgs, runDaemonCommand } from "./daemon.js";
|
|
10
10
|
import { exportBrain, importBrain } from "./import-export.js";
|
|
11
11
|
import { buildNormalizedEventExport } from "@openclawbrain/contracts";
|
|
12
12
|
import { buildTeacherSupervisionArtifactsFromNormalizedEventExport, createAlwaysOnLearningRuntimeState, describeAlwaysOnLearningRuntimeState, drainAlwaysOnLearningRuntime, loadOrInitBaseline, materializeAlwaysOnLearningCandidatePack, persistBaseline } from "./local-learner.js";
|
|
@@ -15,7 +15,7 @@ import { resolveActivationRoot } from "./resolve-activation-root.js";
|
|
|
15
15
|
import { describeOpenClawHomeInspection, discoverOpenClawHomes, formatOpenClawHomeLayout, formatOpenClawHomeProfileSource, inspectOpenClawHome } from "./openclaw-home-layout.js";
|
|
16
16
|
import { inspectOpenClawBrainHookStatus, inspectOpenClawBrainPluginAllowlist } from "./openclaw-hook-truth.js";
|
|
17
17
|
import { describeOpenClawBrainInstallIdentity, describeOpenClawBrainInstallLayout, findInstalledOpenClawBrainPlugin, getOpenClawBrainKnownPluginIds, normalizeOpenClawBrainPluginsConfig, pinInstalledOpenClawBrainPluginActivationRoot, resolveOpenClawBrainInstallTarget } from "./openclaw-plugin-install.js";
|
|
18
|
-
import { buildOpenClawBrainConvergeRestartPlan, classifyOpenClawBrainConvergeVerification, describeOpenClawBrainConvergeChangeReasons, diffOpenClawBrainConvergeRuntimeFingerprint, finalizeOpenClawBrainConvergeResult, planOpenClawBrainConvergePluginAction } from "./install-converge.js";
|
|
18
|
+
import { buildOpenClawBrainConvergeRestartPlan, classifyOpenClawBrainConvergeVerification, describeOpenClawBrainConvergeChangeReasons, diffOpenClawBrainConvergeRuntimeFingerprint, finalizeOpenClawBrainConvergeResult, planOpenClawBrainConvergePluginAction, shouldReplaceOpenClawBrainInstallBeforeConverge } from "./install-converge.js";
|
|
19
19
|
import { loadAttachmentPolicyDeclaration, resolveEffectiveAttachmentPolicyTruth, writeAttachmentPolicyDeclaration } from "./attachment-policy-truth.js";
|
|
20
20
|
import { DEFAULT_WATCH_POLL_INTERVAL_SECONDS, buildNormalizedEventExportFromScannedEvents, bootstrapRuntimeAttach, buildOperatorSurfaceReport, clearOpenClawProfileRuntimeLoadProof, compileRuntimeContext, createAsyncTeacherLiveLoop, createOpenClawLocalSessionTail, createRuntimeEventExportScanner, describeCurrentProfileBrainStatus, formatOperatorRollbackReport, listOpenClawProfileRuntimeLoadProofs, loadRuntimeEventExportBundle, loadWatchTeacherSnapshotState, persistWatchTeacherSnapshot, rollbackRuntimeAttach, resolveAttachmentRuntimeLoadProofsPath, resolveOperatorTeacherSnapshotPath, resolveAsyncTeacherLiveLoopSnapshotPath, resolveWatchSessionTailCursorPath, resolveWatchStateRoot, resolveWatchTeacherSnapshotPath, scanLiveEventExport, scanRecordedSession, summarizeLearningPathFromMaterialization, summarizeNormalizedEventExportLabelFlow, summarizeTeacherNoArtifactCycle, writeScannedEventExportBundle } from "./index.js";
|
|
21
21
|
import { appendLearningUpdateLogs } from "./learning-spine.js";
|
|
@@ -31,6 +31,7 @@ const OPENCLAWBRAIN_EMBEDDER_BASE_URL_ENV = "OPENCLAWBRAIN_EMBEDDER_BASE_URL";
|
|
|
31
31
|
const OPENCLAWBRAIN_EMBEDDER_PROVIDER_ENV = "OPENCLAWBRAIN_EMBEDDER_PROVIDER";
|
|
32
32
|
const OPENCLAWBRAIN_EMBEDDER_MODEL_ENV = "OPENCLAWBRAIN_EMBEDDER_MODEL";
|
|
33
33
|
const OPENCLAWBRAIN_INSTALL_SKIP_EMBEDDER_PROVISION_ENV = "OPENCLAWBRAIN_INSTALL_SKIP_EMBEDDER_PROVISION";
|
|
34
|
+
const LEGACY_COMPAT_PACKAGE_NAME = "@jonathangu/openclawbrain";
|
|
34
35
|
const INSTALL_COMPATIBLE_LOCAL_TEACHER_MODEL_PREFIXES = [
|
|
35
36
|
"qwen3.5:9b",
|
|
36
37
|
"qwen3.5:8b",
|
|
@@ -1621,6 +1622,13 @@ function readInstallRuntimeFingerprint(openclawHome) {
|
|
|
1621
1622
|
function runOpenClawBrainConvergePluginStep(openclawHome) {
|
|
1622
1623
|
const before = readInstallRuntimeFingerprint(openclawHome);
|
|
1623
1624
|
const plan = planOpenClawBrainConvergePluginAction(before);
|
|
1625
|
+
let uninstallCapture = null;
|
|
1626
|
+
if (plan.action === "install" && shouldReplaceOpenClawBrainInstallBeforeConverge(before)) {
|
|
1627
|
+
uninstallCapture = runCapturedExternalCommand("openclaw", ["plugins", "uninstall", plan.pluginId]);
|
|
1628
|
+
if (uninstallCapture.error !== null || uninstallCapture.exitCode !== 0) {
|
|
1629
|
+
throw new Error(`OpenClaw plugin-manager uninstall failed while migrating ${path.resolve(openclawHome)} onto the canonical plugin lane. Tried \`${uninstallCapture.shellCommand}\`. Detail: ${summarizeCapturedCommandFailure(uninstallCapture)}`);
|
|
1630
|
+
}
|
|
1631
|
+
}
|
|
1624
1632
|
const commandArgs = plan.action === "install"
|
|
1625
1633
|
? ["plugins", "install", plan.packageSpec]
|
|
1626
1634
|
: ["plugins", "update", plan.pluginId];
|
|
@@ -1651,7 +1659,7 @@ function runOpenClawBrainConvergePluginStep(openclawHome) {
|
|
|
1651
1659
|
changed: diff.changed,
|
|
1652
1660
|
changeReasons: diff.reasons,
|
|
1653
1661
|
detail: diff.changed
|
|
1654
|
-
? `${plan.action === "install" ? "
|
|
1662
|
+
? `${uninstallCapture !== null ? "Replaced legacy/plugin-shadow install and " : ""}${plan.action === "install" ? "installed" : "refreshed"} plugin-manager state: ${describeOpenClawBrainConvergeChangeReasons(diff.reasons)}`
|
|
1655
1663
|
: `${plan.action === "install" ? "Ran install" : "Ran update"} through the OpenClaw plugin manager, but no runtime-affecting plugin delta was detected`,
|
|
1656
1664
|
warning: null,
|
|
1657
1665
|
capture,
|
|
@@ -1722,6 +1730,27 @@ function buildAttachCommand(openclawHome, activationRoot = null) {
|
|
|
1722
1730
|
function buildInstallEmbedderProvisionCommand(baseUrl, model) {
|
|
1723
1731
|
return `OLLAMA_HOST=${quoteShellArg(baseUrl)} ollama pull ${quoteShellArg(model)}`;
|
|
1724
1732
|
}
|
|
1733
|
+
function buildCanonicalInstallRecoveryPath(parsed) {
|
|
1734
|
+
return [
|
|
1735
|
+
buildInstallCommand(parsed.openclawHome),
|
|
1736
|
+
"openclaw gateway restart",
|
|
1737
|
+
buildInstallStatusCommand(parsed.activationRoot),
|
|
1738
|
+
buildProofCommandForOpenClawHome(parsed.openclawHome)
|
|
1739
|
+
].join(" -> ");
|
|
1740
|
+
}
|
|
1741
|
+
function inspectInstallGuardrailWarnings(parsed) {
|
|
1742
|
+
const warnings = [];
|
|
1743
|
+
const runtimeFingerprint = readInstallRuntimeFingerprint(parsed.openclawHome);
|
|
1744
|
+
if (runtimeFingerprint.selectedInstall?.packageName === LEGACY_COMPAT_PACKAGE_NAME) {
|
|
1745
|
+
warnings.push(`Detected legacy compatibility plugin state (${LEGACY_COMPAT_PACKAGE_NAME}) for this OpenClaw home. Install is converging it back onto the single supported OpenClawBrain path.`);
|
|
1746
|
+
}
|
|
1747
|
+
const learnerInspection = inspectManagedLearnerService(parsed.activationRoot);
|
|
1748
|
+
const learnerRuntimeGuard = describeManagedLearnerServiceRuntimeGuard(learnerInspection);
|
|
1749
|
+
if (learnerRuntimeGuard.state !== "ok") {
|
|
1750
|
+
warnings.push(`${learnerRuntimeGuard.detail} Canonical recovery path: ${buildCanonicalInstallRecoveryPath(parsed)}`);
|
|
1751
|
+
}
|
|
1752
|
+
return warnings;
|
|
1753
|
+
}
|
|
1725
1754
|
function describeExecOutput(value) {
|
|
1726
1755
|
if (typeof value === "string") {
|
|
1727
1756
|
const normalized = value.trim();
|
|
@@ -3135,6 +3164,7 @@ function installExtensionFromLocalWorkspaceBuild(extensionDir) {
|
|
|
3135
3164
|
return [...LOCAL_WORKSPACE_EXTENSION_PACKAGES];
|
|
3136
3165
|
}
|
|
3137
3166
|
let cachedOpenClawPackageMetadata = null;
|
|
3167
|
+
let cachedOpenClawRuntimePackageMetadata = null;
|
|
3138
3168
|
function resolveOpenClawPackageManifestPath() {
|
|
3139
3169
|
const candidates = [
|
|
3140
3170
|
path.resolve(__dirname, "..", "package.json"),
|
|
@@ -3163,6 +3193,34 @@ function readOpenClawPackageMetadata() {
|
|
|
3163
3193
|
cachedOpenClawPackageMetadata = { name, version };
|
|
3164
3194
|
return cachedOpenClawPackageMetadata;
|
|
3165
3195
|
}
|
|
3196
|
+
function resolveOpenClawRuntimePackageManifestPath() {
|
|
3197
|
+
const candidates = [
|
|
3198
|
+
path.resolve(__dirname, "..", "..", "..", "openclaw", "package.json"),
|
|
3199
|
+
path.resolve(__dirname, "..", "..", "..", "..", "packages", "openclaw", "package.json"),
|
|
3200
|
+
];
|
|
3201
|
+
for (const candidate of candidates) {
|
|
3202
|
+
if (existsSync(candidate)) {
|
|
3203
|
+
return candidate;
|
|
3204
|
+
}
|
|
3205
|
+
}
|
|
3206
|
+
throw new Error("OpenClawBrain runtime package manifest not found. Searched:\n" +
|
|
3207
|
+
candidates.map((candidate) => ` - ${candidate}`).join("\n"));
|
|
3208
|
+
}
|
|
3209
|
+
function readOpenClawRuntimePackageMetadata() {
|
|
3210
|
+
if (cachedOpenClawRuntimePackageMetadata !== null) {
|
|
3211
|
+
return cachedOpenClawRuntimePackageMetadata;
|
|
3212
|
+
}
|
|
3213
|
+
const manifestPath = resolveOpenClawRuntimePackageManifestPath();
|
|
3214
|
+
const manifest = JSON.parse(readFileSync(manifestPath, "utf8"));
|
|
3215
|
+
const name = typeof manifest.name === "string" && manifest.name.trim().length > 0
|
|
3216
|
+
? manifest.name.trim()
|
|
3217
|
+
: "@openclawbrain/openclaw";
|
|
3218
|
+
const version = typeof manifest.version === "string" && manifest.version.trim().length > 0
|
|
3219
|
+
? manifest.version.trim()
|
|
3220
|
+
: "0.0.0";
|
|
3221
|
+
cachedOpenClawRuntimePackageMetadata = { name, version };
|
|
3222
|
+
return cachedOpenClawRuntimePackageMetadata;
|
|
3223
|
+
}
|
|
3166
3224
|
function buildExtensionIndexTs(activationRoot) {
|
|
3167
3225
|
const templatePath = resolveExtensionTemplatePath();
|
|
3168
3226
|
const template = readFileSync(templatePath, "utf8");
|
|
@@ -3170,6 +3228,10 @@ function buildExtensionIndexTs(activationRoot) {
|
|
|
3170
3228
|
}
|
|
3171
3229
|
function buildExtensionPackageJson() {
|
|
3172
3230
|
const packageMetadata = readOpenClawPackageMetadata();
|
|
3231
|
+
const runtimePackageMetadata = readOpenClawRuntimePackageMetadata();
|
|
3232
|
+
const dependencies = {
|
|
3233
|
+
[runtimePackageMetadata.name]: runtimePackageMetadata.version
|
|
3234
|
+
};
|
|
3173
3235
|
return JSON.stringify({
|
|
3174
3236
|
name: "openclawbrain",
|
|
3175
3237
|
version: packageMetadata.version,
|
|
@@ -3178,9 +3240,7 @@ function buildExtensionPackageJson() {
|
|
|
3178
3240
|
openclaw: {
|
|
3179
3241
|
extensions: ["index.ts"]
|
|
3180
3242
|
},
|
|
3181
|
-
dependencies
|
|
3182
|
-
[packageMetadata.name]: packageMetadata.version
|
|
3183
|
-
}
|
|
3243
|
+
dependencies
|
|
3184
3244
|
}, null, 2) + "\n";
|
|
3185
3245
|
}
|
|
3186
3246
|
function buildExtensionPluginManifest() {
|
|
@@ -3967,8 +4027,10 @@ function emitInstallConvergeResult(result, parsed) {
|
|
|
3967
4027
|
function runInstallCommand(parsed) {
|
|
3968
4028
|
let pluginResult = null;
|
|
3969
4029
|
let attachResult = null;
|
|
4030
|
+
let guardrailWarnings = [];
|
|
3970
4031
|
try {
|
|
3971
4032
|
validateOpenClawHome(parsed.openclawHome);
|
|
4033
|
+
guardrailWarnings = inspectInstallGuardrailWarnings(parsed);
|
|
3972
4034
|
pluginResult = runOpenClawBrainConvergePluginStep(parsed.openclawHome);
|
|
3973
4035
|
attachResult = executeProfileHookAttachCommand(parsed);
|
|
3974
4036
|
}
|
|
@@ -3976,7 +4038,7 @@ function runInstallCommand(parsed) {
|
|
|
3976
4038
|
const verdict = finalizeOpenClawBrainConvergeResult({
|
|
3977
4039
|
stepFailure: toErrorMessage(error),
|
|
3978
4040
|
verification: null,
|
|
3979
|
-
warnings:
|
|
4041
|
+
warnings: guardrailWarnings
|
|
3980
4042
|
});
|
|
3981
4043
|
const failureResult = {
|
|
3982
4044
|
command: "install",
|
|
@@ -4042,6 +4104,7 @@ function runInstallCommand(parsed) {
|
|
|
4042
4104
|
restartPerformed: restartPlan.required && restartPlan.automatic && restartError === null
|
|
4043
4105
|
});
|
|
4044
4106
|
const convergeWarnings = [];
|
|
4107
|
+
convergeWarnings.push(...guardrailWarnings);
|
|
4045
4108
|
if (pluginResult.warning) {
|
|
4046
4109
|
convergeWarnings.push(pluginResult.warning);
|
|
4047
4110
|
}
|
package/dist/src/daemon.js
CHANGED
|
@@ -23,6 +23,7 @@ const DEFAULT_SCAN_ROOT_DIRNAME = "event-exports";
|
|
|
23
23
|
const BASELINE_STATE_BASENAME = "baseline-state.json";
|
|
24
24
|
const SCANNER_CHECKPOINT_BASENAME = ".openclawbrain-scanner-checkpoint.json";
|
|
25
25
|
const CLI_PACKAGE_NAME = "@openclawbrain/cli";
|
|
26
|
+
const LEGACY_COMPAT_PACKAGE_NAME = "@jonathangu/openclawbrain";
|
|
26
27
|
const CLI_BIN_NAME = "openclawbrain";
|
|
27
28
|
const DEFAULT_DAEMON_COMMAND_RUNNER = (command) => execSync(command, {
|
|
28
29
|
encoding: "utf8",
|
|
@@ -198,6 +199,26 @@ function resolveCliPackageRoot(startDir) {
|
|
|
198
199
|
}
|
|
199
200
|
return null;
|
|
200
201
|
}
|
|
202
|
+
function readNearestPackageMetadataForPath(filePath) {
|
|
203
|
+
if (typeof filePath !== "string" || filePath.trim().length === 0) {
|
|
204
|
+
return null;
|
|
205
|
+
}
|
|
206
|
+
let currentDir = path.dirname(path.resolve(filePath));
|
|
207
|
+
while (true) {
|
|
208
|
+
const packageMetadata = readPackageMetadata(currentDir);
|
|
209
|
+
if (packageMetadata !== null) {
|
|
210
|
+
return {
|
|
211
|
+
root: currentDir,
|
|
212
|
+
...packageMetadata,
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
const parentDir = path.dirname(currentDir);
|
|
216
|
+
if (parentDir === currentDir) {
|
|
217
|
+
return null;
|
|
218
|
+
}
|
|
219
|
+
currentDir = parentDir;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
201
222
|
function resolveDaemonPackageManagerLaunchSpec(moduleDir) {
|
|
202
223
|
const cliPackageRoot = resolveCliPackageRoot(moduleDir);
|
|
203
224
|
const cliPackageMetadata = readPackageMetadata(cliPackageRoot);
|
|
@@ -244,14 +265,47 @@ function describeDaemonProgramArguments(programArguments) {
|
|
|
244
265
|
? programArguments[1]
|
|
245
266
|
: programArguments[0];
|
|
246
267
|
const runtimePackageSpec = programArguments.find((argument) => argument.startsWith("--package="))?.slice("--package=".length) ?? null;
|
|
268
|
+
const runtimePackageMetadata = runtimePath === null ? null : readNearestPackageMetadataForPath(runtimePath);
|
|
247
269
|
return {
|
|
248
270
|
configuredProgramArguments: programArguments,
|
|
249
271
|
configuredCommand: formatCommand(programArguments),
|
|
250
272
|
configuredRuntimePath: runtimePath,
|
|
251
273
|
configuredRuntimePackageSpec: runtimePackageSpec,
|
|
274
|
+
configuredRuntimePackageName: runtimePackageMetadata?.name ?? null,
|
|
275
|
+
configuredRuntimePackageVersion: runtimePackageMetadata?.version ?? null,
|
|
252
276
|
configuredRuntimeLooksEphemeral: runtimePath === null ? null : isNpxCachePath(runtimePath)
|
|
253
277
|
};
|
|
254
278
|
}
|
|
279
|
+
export function describeManagedLearnerServiceRuntimeGuard(inspection) {
|
|
280
|
+
if (inspection.installed !== true || inspection.configuredProgramArguments === null) {
|
|
281
|
+
return {
|
|
282
|
+
state: "ok",
|
|
283
|
+
reason: "not_installed",
|
|
284
|
+
detail: "No managed learner service is installed yet."
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
if (inspection.configuredRuntimeLooksEphemeral === true) {
|
|
288
|
+
return {
|
|
289
|
+
state: "refresh_required",
|
|
290
|
+
reason: "ephemeral_runtime",
|
|
291
|
+
detail: `Learner service still points at an ephemeral runtime path (${inspection.configuredRuntimePath ?? "unknown"}). Refresh it onto the durable OpenClawBrain CLI path before trusting background learning.`
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
const runtimePackageName = inspection.configuredRuntimePackageName;
|
|
295
|
+
const runtimePackageSpec = inspection.configuredRuntimePackageSpec;
|
|
296
|
+
if (runtimePackageName === LEGACY_COMPAT_PACKAGE_NAME || runtimePackageSpec?.startsWith(`${LEGACY_COMPAT_PACKAGE_NAME}@`) || runtimePackageSpec === LEGACY_COMPAT_PACKAGE_NAME) {
|
|
297
|
+
return {
|
|
298
|
+
state: "refresh_required",
|
|
299
|
+
reason: "legacy_compat_runtime",
|
|
300
|
+
detail: `Learner service still points at the retired compatibility package ${LEGACY_COMPAT_PACKAGE_NAME}${inspection.configuredRuntimePath ? ` (${inspection.configuredRuntimePath})` : ""}. Refresh it onto the single supported OpenClawBrain CLI path before trusting background learning.`
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
return {
|
|
304
|
+
state: "ok",
|
|
305
|
+
reason: "durable_runtime",
|
|
306
|
+
detail: `Learner service points at the durable ${CLI_PACKAGE_NAME} runtime.`
|
|
307
|
+
};
|
|
308
|
+
}
|
|
255
309
|
function resolveDaemonProgramArguments() {
|
|
256
310
|
for (const candidate of getOpenclawbrainCliScriptPathCandidates()) {
|
|
257
311
|
const cliScriptPath = resolveCliScriptCandidate(candidate);
|
|
@@ -465,7 +519,8 @@ export function inspectManagedLearnerService(activationRoot) {
|
|
|
465
519
|
}
|
|
466
520
|
export function ensureManagedLearnerServiceForActivationRoot(activationRoot) {
|
|
467
521
|
const inspection = inspectManagedLearnerServiceInternal(activationRoot);
|
|
468
|
-
|
|
522
|
+
const runtimeGuard = describeManagedLearnerServiceRuntimeGuard(inspection);
|
|
523
|
+
if (inspection.matchesRequestedActivationRoot === true && inspection.running && runtimeGuard.state === "ok") {
|
|
469
524
|
return {
|
|
470
525
|
state: "ensured",
|
|
471
526
|
reason: "already_running_exact_root",
|
|
@@ -473,12 +528,25 @@ export function ensureManagedLearnerServiceForActivationRoot(activationRoot) {
|
|
|
473
528
|
inspection
|
|
474
529
|
};
|
|
475
530
|
}
|
|
531
|
+
if (inspection.installed && runtimeGuard.state !== "ok") {
|
|
532
|
+
const stopResult = stopManagedLearnerService(activationRoot);
|
|
533
|
+
if (!stopResult.ok) {
|
|
534
|
+
return {
|
|
535
|
+
state: "deferred",
|
|
536
|
+
reason: "stale_runtime_refresh_failed",
|
|
537
|
+
detail: `${runtimeGuard.detail} Tried to stop the stale learner service first, but that failed: ${stopResult.message}`,
|
|
538
|
+
inspection: stopResult.inspection
|
|
539
|
+
};
|
|
540
|
+
}
|
|
541
|
+
}
|
|
476
542
|
const startResult = startManagedLearnerService(activationRoot);
|
|
477
543
|
if (startResult.ok) {
|
|
478
544
|
return {
|
|
479
|
-
state: "started",
|
|
480
|
-
reason: "started_exact_root",
|
|
481
|
-
detail:
|
|
545
|
+
state: runtimeGuard.state !== "ok" ? "refreshed" : "started",
|
|
546
|
+
reason: runtimeGuard.state !== "ok" ? runtimeGuard.reason : "started_exact_root",
|
|
547
|
+
detail: runtimeGuard.state !== "ok"
|
|
548
|
+
? `${runtimeGuard.detail} Refreshed the learner daemon onto the durable OpenClawBrain CLI path for ${startResult.inspection.requestedActivationRoot}.`
|
|
549
|
+
: `Started the background learner service for ${startResult.inspection.requestedActivationRoot}; passive learning can begin for this attached profile now.`,
|
|
482
550
|
inspection: startResult.inspection
|
|
483
551
|
};
|
|
484
552
|
}
|
|
@@ -940,11 +1008,21 @@ export function daemonStatus(activationRoot, json) {
|
|
|
940
1008
|
}
|
|
941
1009
|
if (daemonLaunchDescription.configuredRuntimePath !== null) {
|
|
942
1010
|
const runtimePackageSuffix = daemonLaunchDescription.configuredRuntimePackageSpec === null
|
|
943
|
-
?
|
|
1011
|
+
? daemonLaunchDescription.configuredRuntimePackageName === null
|
|
1012
|
+
? ""
|
|
1013
|
+
: ` (${daemonLaunchDescription.configuredRuntimePackageName}${daemonLaunchDescription.configuredRuntimePackageVersion === null ? "" : `@${daemonLaunchDescription.configuredRuntimePackageVersion}`})`
|
|
944
1014
|
: ` (${daemonLaunchDescription.configuredRuntimePackageSpec})`;
|
|
945
1015
|
const runtimeWarning = daemonLaunchDescription.configuredRuntimeLooksEphemeral ? " [ephemeral]" : "";
|
|
946
1016
|
console.log(` Runtime: ${daemonLaunchDescription.configuredRuntimePath}${runtimePackageSuffix}${runtimeWarning}`);
|
|
947
1017
|
}
|
|
1018
|
+
const runtimeGuard = describeManagedLearnerServiceRuntimeGuard({
|
|
1019
|
+
installed: plistInstalled,
|
|
1020
|
+
configuredProgramArguments,
|
|
1021
|
+
...daemonLaunchDescription,
|
|
1022
|
+
});
|
|
1023
|
+
if (runtimeGuard.state !== "ok") {
|
|
1024
|
+
console.log(` Guardrail: ${runtimeGuard.detail}`);
|
|
1025
|
+
}
|
|
948
1026
|
if (configuredProgramArguments !== null && configuredProgramArguments.length > 0) {
|
|
949
1027
|
console.log(` Program: ${configuredProgramArguments[0]}`);
|
|
950
1028
|
if (configuredProgramArguments.length > 1) {
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
const LEGACY_COMPAT_PACKAGE_NAME = "@jonathangu/openclawbrain";
|
|
2
|
+
|
|
1
3
|
const CHANGE_REASON_LABELS = {
|
|
2
4
|
install_identity: "plugin install identity changed",
|
|
3
5
|
install_layout: "authoritative install layout changed",
|
|
@@ -26,6 +28,14 @@ function installIdentityOf(fingerprint) {
|
|
|
26
28
|
});
|
|
27
29
|
}
|
|
28
30
|
|
|
31
|
+
export function shouldReplaceOpenClawBrainInstallBeforeConverge(fingerprint) {
|
|
32
|
+
const selectedInstall = fingerprint?.selectedInstall ?? null;
|
|
33
|
+
if (selectedInstall === null) {
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
return selectedInstall.packageName !== "@openclawbrain/openclaw";
|
|
37
|
+
}
|
|
38
|
+
|
|
29
39
|
export function planOpenClawBrainConvergePluginAction(fingerprint) {
|
|
30
40
|
const selectedInstall = fingerprint?.selectedInstall ?? null;
|
|
31
41
|
if (selectedInstall === null) {
|
|
@@ -36,6 +46,14 @@ export function planOpenClawBrainConvergePluginAction(fingerprint) {
|
|
|
36
46
|
reason: "no authoritative OpenClawBrain plugin install was discovered for this OpenClaw home",
|
|
37
47
|
};
|
|
38
48
|
}
|
|
49
|
+
if (selectedInstall.packageName === LEGACY_COMPAT_PACKAGE_NAME) {
|
|
50
|
+
return {
|
|
51
|
+
action: "install",
|
|
52
|
+
packageSpec: "@openclawbrain/openclaw",
|
|
53
|
+
pluginId: "openclawbrain",
|
|
54
|
+
reason: `the authoritative install still points at the retired compatibility package ${LEGACY_COMPAT_PACKAGE_NAME}, so converge must replace it with the canonical split-package plugin`,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
39
57
|
if (selectedInstall.installLayout !== "native_package_plugin") {
|
|
40
58
|
return {
|
|
41
59
|
action: "install",
|
|
@@ -130,6 +148,10 @@ export function classifyOpenClawBrainConvergeVerification(input) {
|
|
|
130
148
|
const runtimeLoad = input.runtimeLoad ?? "unverified";
|
|
131
149
|
const loadProof = input.loadProof ?? "unverified";
|
|
132
150
|
const serveState = input.serveState ?? "unknown";
|
|
151
|
+
const installedPackageName = input.installedPackageName ?? null;
|
|
152
|
+
if (installedPackageName === LEGACY_COMPAT_PACKAGE_NAME) {
|
|
153
|
+
blockingReasons.push(`installed plugin still references the retired compatibility package ${LEGACY_COMPAT_PACKAGE_NAME}; converge must replace it with @openclawbrain/openclaw`);
|
|
154
|
+
}
|
|
133
155
|
if (installLayout !== "native_package_plugin") {
|
|
134
156
|
blockingReasons.push("split-package native package plugin is not authoritative after converge");
|
|
135
157
|
}
|
|
@@ -160,6 +182,23 @@ export function classifyOpenClawBrainConvergeVerification(input) {
|
|
|
160
182
|
if (input.restartRequired === true && input.restartPerformed !== true && runtimeTruthAlreadyProven) {
|
|
161
183
|
warnings.push("automatic restart was not performed because install could not infer an exact OpenClaw profile token, but current status already proves runtime load");
|
|
162
184
|
}
|
|
185
|
+
// When runtime proof is green for the repaired profile, promote the proof
|
|
186
|
+
// surface to healthy rather than leaving stale degraded warnings that
|
|
187
|
+
// contradict the proven truth.
|
|
188
|
+
if (runtimeTruthAlreadyProven) {
|
|
189
|
+
if (serveState !== "serving_active_pack") {
|
|
190
|
+
warnings.push(`serve state is ${serveState}`);
|
|
191
|
+
}
|
|
192
|
+
if (input.awaitingFirstExport === true) {
|
|
193
|
+
warnings.push("the attached profile has not emitted its first export yet");
|
|
194
|
+
}
|
|
195
|
+
return {
|
|
196
|
+
state: warnings.length > 0 ? "warning" : "healthy",
|
|
197
|
+
manualActionRequired: false,
|
|
198
|
+
blockingReasons,
|
|
199
|
+
warnings,
|
|
200
|
+
};
|
|
201
|
+
}
|
|
163
202
|
if (displayedStatus !== "ok") {
|
|
164
203
|
warnings.push(`status is ${displayedStatus}`);
|
|
165
204
|
}
|
|
@@ -384,20 +384,33 @@ function buildHardeningSnapshot({ attachTruthLine, serveLine, routeFnLine, verdi
|
|
|
384
384
|
};
|
|
385
385
|
}
|
|
386
386
|
|
|
387
|
-
function hasPackagedHookSource(pluginInspectText) {
|
|
388
|
-
|
|
387
|
+
function hasPackagedHookSource(pluginInspectText, openclawHome) {
|
|
388
|
+
if (/Source:\s+.*(?:@openclawbrain[\\/]+openclaw|openclawbrain)[\\/]+dist[\\/]+extension[\\/]+index\.js/m.test(pluginInspectText)) {
|
|
389
|
+
return true;
|
|
390
|
+
}
|
|
391
|
+
const generatedShadowHookPath = canonicalizeExistingProofPath(path.join(openclawHome, "extensions", "openclawbrain", "index.ts"));
|
|
392
|
+
const sourceMatch = pluginInspectText.match(/^Source:\s+(.+)$/m);
|
|
393
|
+
const reportedSourcePath = canonicalizeExistingProofPath(sourceMatch?.[1] ?? "");
|
|
394
|
+
return reportedSourcePath === generatedShadowHookPath;
|
|
389
395
|
}
|
|
390
396
|
|
|
391
|
-
function buildVerdict({ steps, gatewayStatus, pluginInspect, statusSignals, breadcrumbs, runtimeLoadProofSnapshot, openclawHome }) {
|
|
397
|
+
function buildVerdict({ steps, gatewayStatus, pluginInspect, statusSignals, breadcrumbs, runtimeLoadProofSnapshot, coverageSnapshot, openclawHome }) {
|
|
392
398
|
const failedSteps = steps.filter((step) => step.resultClass !== "success" && step.skipped !== true);
|
|
393
399
|
const failedDetailedStatusStep = failedSteps.find((step) => step.stepId === "05-detailed-status");
|
|
394
400
|
const gatewayHealthy = /Runtime:\s+running/m.test(gatewayStatus) && /RPC probe:\s+ok/m.test(gatewayStatus);
|
|
395
401
|
const pluginLoaded = /Status:\s+loaded/m.test(pluginInspect);
|
|
396
|
-
const packagedHookPath = hasPackagedHookSource(pluginInspect);
|
|
402
|
+
const packagedHookPath = hasPackagedHookSource(pluginInspect, openclawHome);
|
|
397
403
|
const breadcrumbLoaded = breadcrumbs.afterBundleStart.some((entry) => entry.kind === "loaded");
|
|
398
404
|
const runtimeProofMatched = Array.isArray(runtimeLoadProofSnapshot?.value?.profiles)
|
|
399
405
|
&& runtimeLoadProofSnapshot.value.profiles.some((profile) => canonicalizeExistingProofPath(profile?.openclawHome ?? "") === canonicalizeExistingProofPath(openclawHome));
|
|
400
406
|
const runtimeTruthGaps = [];
|
|
407
|
+
const currentCoverageEntry = Array.isArray(coverageSnapshot?.profiles)
|
|
408
|
+
? coverageSnapshot.profiles.find((entry) => entry.current)
|
|
409
|
+
: null;
|
|
410
|
+
const currentProfileRuntimeCovered = currentCoverageEntry?.runtimeLoad === "proven";
|
|
411
|
+
const crossProfileCoverageGapOnly = currentProfileRuntimeCovered
|
|
412
|
+
&& (coverageSnapshot?.attachedProfileCount ?? 0) > (coverageSnapshot?.runtimeProvenCount ?? 0)
|
|
413
|
+
&& (statusSignals.proofError === null || statusSignals.proofError === "none");
|
|
401
414
|
const strongRuntimeTruth = statusSignals.loadProofReady
|
|
402
415
|
&& statusSignals.runtimeProven
|
|
403
416
|
&& statusSignals.serveActivePack
|
|
@@ -414,8 +427,10 @@ function buildVerdict({ steps, gatewayStatus, pluginInspect, statusSignals, brea
|
|
|
414
427
|
const warnings = [];
|
|
415
428
|
if (!statusSignals.statusOk) {
|
|
416
429
|
if (strongRuntimeTruth) {
|
|
417
|
-
|
|
418
|
-
|
|
430
|
+
if (!crossProfileCoverageGapOnly) {
|
|
431
|
+
warningCodes.push("status_warn");
|
|
432
|
+
warnings.push("detailed status did not return STATUS ok, but loadProof/runtime/serve/routeFn proofs stayed healthy");
|
|
433
|
+
}
|
|
419
434
|
}
|
|
420
435
|
else {
|
|
421
436
|
runtimeTruthGaps.push("status_ok");
|
|
@@ -870,6 +885,7 @@ export function captureOperatorProofBundle(options) {
|
|
|
870
885
|
statusSignals,
|
|
871
886
|
breadcrumbs,
|
|
872
887
|
runtimeLoadProofSnapshot,
|
|
888
|
+
coverageSnapshot,
|
|
873
889
|
openclawHome: options.openclawHome,
|
|
874
890
|
});
|
|
875
891
|
const hardeningSnapshot = buildHardeningSnapshot({
|
|
@@ -151,6 +151,26 @@ function parseOpenClawSessionRecord(value, lineNumber) {
|
|
|
151
151
|
parentId: expectNullableString(record.parentId, `${lineNumber}.parentId`),
|
|
152
152
|
timestamp: expectString(record.timestamp, `${lineNumber}.timestamp`)
|
|
153
153
|
};
|
|
154
|
+
case "custom_message": {
|
|
155
|
+
const data = {};
|
|
156
|
+
if (record.content !== undefined) {
|
|
157
|
+
data.content = record.content;
|
|
158
|
+
}
|
|
159
|
+
if (record.display !== undefined) {
|
|
160
|
+
data.display = record.display;
|
|
161
|
+
}
|
|
162
|
+
if (record.details !== undefined) {
|
|
163
|
+
data.details = record.details;
|
|
164
|
+
}
|
|
165
|
+
return {
|
|
166
|
+
type: "custom",
|
|
167
|
+
customType: expectString(record.customType, `${lineNumber}.customType`),
|
|
168
|
+
data,
|
|
169
|
+
id: expectString(record.id, `${lineNumber}.id`),
|
|
170
|
+
parentId: expectNullableString(record.parentId, `${lineNumber}.parentId`),
|
|
171
|
+
timestamp: expectString(record.timestamp, `${lineNumber}.timestamp`)
|
|
172
|
+
};
|
|
173
|
+
}
|
|
154
174
|
case "message":
|
|
155
175
|
return {
|
|
156
176
|
type,
|
package/extension/index.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* OpenClawBrain extension template — canonical, pre-built, versioned with the package.
|
|
3
3
|
*
|
|
4
4
|
* The placeholder __ACTIVATION_ROOT__ is replaced by
|
|
5
|
-
*
|
|
5
|
+
* OpenClawBrain's `openclawbrain install`
|
|
6
6
|
* with the real activation root path at install time.
|
|
7
7
|
*
|
|
8
8
|
* Design constraints:
|
|
@@ -91,7 +91,7 @@ function announceStartupBreadcrumb(): void {
|
|
|
91
91
|
if (isActivationRootPlaceholder(ACTIVATION_ROOT)) {
|
|
92
92
|
warnOnce(
|
|
93
93
|
"startup-brain-not-yet-loaded",
|
|
94
|
-
"[openclawbrain] BRAIN NOT YET LOADED: install has not pinned ACTIVATION_ROOT yet. Install
|
|
94
|
+
"[openclawbrain] BRAIN NOT YET LOADED: install has not pinned ACTIVATION_ROOT yet. Install OpenClawBrain, then run: openclawbrain install --openclaw-home <path>"
|
|
95
95
|
);
|
|
96
96
|
return;
|
|
97
97
|
}
|
|
@@ -162,7 +162,7 @@ export function createBeforePromptBuildHandler(input: {
|
|
|
162
162
|
key: "activation-root-placeholder",
|
|
163
163
|
once: true,
|
|
164
164
|
message:
|
|
165
|
-
"[openclawbrain] BRAIN NOT YET LOADED: ACTIVATION_ROOT is still a placeholder. Install
|
|
165
|
+
"[openclawbrain] BRAIN NOT YET LOADED: ACTIVATION_ROOT is still a placeholder. Install OpenClawBrain, then run: openclawbrain install --openclaw-home <path>"
|
|
166
166
|
}));
|
|
167
167
|
return {};
|
|
168
168
|
}
|
package/package.json
CHANGED