@openclawbrain/cli 0.4.13 → 0.4.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -11
- package/dist/extension/index.js +29 -3
- package/dist/extension/index.js.map +1 -1
- package/dist/extension/runtime-guard.d.ts +8 -0
- package/dist/extension/runtime-guard.js +100 -12
- package/dist/extension/runtime-guard.js.map +1 -1
- package/dist/src/attachment-truth.d.ts +32 -22
- package/dist/src/attachment-truth.js +338 -186
- package/dist/src/cli.d.ts +13 -1
- package/dist/src/cli.js +595 -113
- package/dist/src/index.d.ts +242 -3
- package/dist/src/index.js +1029 -38
- package/dist/src/install-converge.js +217 -0
- package/dist/src/learning-spine.d.ts +2 -1
- package/dist/src/learning-spine.js +49 -19
- package/dist/src/local-learner.d.ts +30 -0
- package/dist/src/local-learner.js +298 -179
- package/dist/src/local-session-passive-learning.js +28 -2
- package/dist/src/materialization-embedder.js +11 -0
- package/dist/src/openclaw-hook-truth.d.ts +6 -0
- package/dist/src/openclaw-hook-truth.js +27 -0
- package/dist/src/proof-command.js +301 -42
- package/dist/src/runtime-core.js +658 -0
- package/dist/src/status-learning-path.js +32 -2
- package/dist/src/teacher-decision-match.js +277 -0
- package/dist/src/teacher-labeler.js +4 -30
- package/dist/src/traced-learning-bridge.js +17 -1
- package/extension/index.ts +35 -4
- package/extension/runtime-guard.ts +92 -14
- package/package.json +4 -3
package/dist/src/cli.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { execFileSync, execSync } from "node:child_process";
|
|
2
|
+
import { execFileSync, execSync, spawnSync } from "node:child_process";
|
|
3
3
|
import { existsSync, mkdirSync, readFileSync, readdirSync, readSync, openSync, closeSync, realpathSync, rmSync, statSync, writeFileSync, symlinkSync } from "node:fs";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
@@ -9,21 +9,23 @@ import { DEFAULT_OLLAMA_EMBEDDING_MODEL, createOllamaEmbedder } from "@openclawb
|
|
|
9
9
|
import { ensureManagedLearnerServiceForActivationRoot, inspectManagedLearnerService, removeManagedLearnerServiceForActivationRoot, parseDaemonArgs, runDaemonCommand } from "./daemon.js";
|
|
10
10
|
import { exportBrain, importBrain } from "./import-export.js";
|
|
11
11
|
import { buildNormalizedEventExport } from "@openclawbrain/contracts";
|
|
12
|
-
import { buildTeacherSupervisionArtifactsFromNormalizedEventExport, createAlwaysOnLearningRuntimeState, describeAlwaysOnLearningRuntimeState, drainAlwaysOnLearningRuntime, loadOrInitBaseline,
|
|
12
|
+
import { buildTeacherSupervisionArtifactsFromNormalizedEventExport, createAlwaysOnLearningRuntimeState, describeAlwaysOnLearningRuntimeState, drainAlwaysOnLearningRuntime, loadOrInitBaseline, materializeAlwaysOnLearningCandidatePack, persistBaseline } from "./local-learner.js";
|
|
13
13
|
import { inspectActivationState, loadPackFromActivation, promoteCandidatePack, readLearningSpineLogEntries, stageCandidatePack } from "@openclawbrain/pack-format";
|
|
14
14
|
import { resolveActivationRoot } from "./resolve-activation-root.js";
|
|
15
15
|
import { describeOpenClawHomeInspection, discoverOpenClawHomes, formatOpenClawHomeLayout, formatOpenClawHomeProfileSource, inspectOpenClawHome } from "./openclaw-home-layout.js";
|
|
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
19
|
import { loadAttachmentPolicyDeclaration, resolveEffectiveAttachmentPolicyTruth, writeAttachmentPolicyDeclaration } from "./attachment-policy-truth.js";
|
|
19
20
|
import { DEFAULT_WATCH_POLL_INTERVAL_SECONDS, buildNormalizedEventExportFromScannedEvents, bootstrapRuntimeAttach, buildOperatorSurfaceReport, clearOpenClawProfileRuntimeLoadProof, compileRuntimeContext, createAsyncTeacherLiveLoop, createOpenClawLocalSessionTail, createRuntimeEventExportScanner, describeCurrentProfileBrainStatus, formatOperatorRollbackReport, listOpenClawProfileRuntimeLoadProofs, loadRuntimeEventExportBundle, loadWatchTeacherSnapshotState, persistWatchTeacherSnapshot, rollbackRuntimeAttach, resolveAttachmentRuntimeLoadProofsPath, resolveOperatorTeacherSnapshotPath, resolveAsyncTeacherLiveLoopSnapshotPath, resolveWatchSessionTailCursorPath, resolveWatchStateRoot, resolveWatchTeacherSnapshotPath, scanLiveEventExport, scanRecordedSession, summarizeLearningPathFromMaterialization, summarizeNormalizedEventExportLabelFlow, writeScannedEventExportBundle } from "./index.js";
|
|
20
21
|
import { appendLearningUpdateLogs } from "./learning-spine.js";
|
|
21
22
|
import { buildPassiveLearningSessionExportFromOpenClawSessionStore } from "./local-session-passive-learning.js";
|
|
23
|
+
import { reindexMaterializationCandidateWithEmbedder } from "./materialization-embedder.js";
|
|
22
24
|
import { summarizePackVectorEmbeddingState } from "./embedding-status.js";
|
|
23
25
|
import { buildTracedLearningBridgePayloadFromRuntime, buildTracedLearningStatusSurface, persistTracedLearningBridgeState } from "./traced-learning-bridge.js";
|
|
24
26
|
import { discoverOpenClawSessionStores, loadOpenClawSessionIndex, readOpenClawSessionFile } from "./session-store.js";
|
|
25
27
|
import { readOpenClawBrainProviderDefaults, readOpenClawBrainProviderConfig, readOpenClawBrainProviderConfigFromSources, resolveOpenClawBrainProviderDefaultsPath } from "./provider-config.js";
|
|
26
|
-
import { formatOperatorLearningPathSummary } from "./status-learning-path.js";
|
|
28
|
+
import { formatOperatorLearningAttributionSummary, formatOperatorLearningPathSummary } from "./status-learning-path.js";
|
|
27
29
|
import { buildProofCommandForOpenClawHome, buildProofCommandHelpSection, captureOperatorProofBundle, formatOperatorProofResult, parseProofCliArgs } from "./proof-command.js";
|
|
28
30
|
const OPENCLAWBRAIN_EMBEDDER_BASE_URL_ENV = "OPENCLAWBRAIN_EMBEDDER_BASE_URL";
|
|
29
31
|
const OPENCLAWBRAIN_EMBEDDER_PROVIDER_ENV = "OPENCLAWBRAIN_EMBEDDER_PROVIDER";
|
|
@@ -516,7 +518,7 @@ function operatorCliHelp() {
|
|
|
516
518
|
" --help Show this help.",
|
|
517
519
|
"",
|
|
518
520
|
"Lifecycle flow:",
|
|
519
|
-
" 1. install openclawbrain install —
|
|
521
|
+
" 1. install openclawbrain install — converge one OpenClaw home: plugin-manager install/update/repair, hook repair, conditional restart, then status verification",
|
|
520
522
|
" 2. attach openclawbrain attach --openclaw-home <path> [--activation-root <path>] — explicit reattach/manual hook path for known brain data; use install first",
|
|
521
523
|
" 3. status openclawbrain status --activation-root <path> — answer \"How's the brain?\" for that boundary",
|
|
522
524
|
" 4. status --detailed openclawbrain status --activation-root <path> --detailed — explain serve path, freshness, backlog, and failure mode",
|
|
@@ -706,6 +708,10 @@ function summarizeStatusHookLoad(installHook, status) {
|
|
|
706
708
|
installState: installHook.state === "unknown" ? "unverified" : installHook.state,
|
|
707
709
|
loadability: installHook.loadability,
|
|
708
710
|
loadProof: status.hook.loadProof,
|
|
711
|
+
guardSeverity: status.hook.guardSeverity,
|
|
712
|
+
guardActionability: status.hook.guardActionability,
|
|
713
|
+
guardSummary: status.hook.guardSummary,
|
|
714
|
+
guardAction: status.hook.guardAction,
|
|
709
715
|
detail: status.hook.detail
|
|
710
716
|
};
|
|
711
717
|
}
|
|
@@ -1076,7 +1082,7 @@ function summarizeStatusTeacher(report, providerConfig, localLlm) {
|
|
|
1076
1082
|
detail: `${providerConfig.teacher.model} is enabled on Ollama, but no watch teacher snapshot is visible yet`
|
|
1077
1083
|
};
|
|
1078
1084
|
}
|
|
1079
|
-
const stale = report.teacherLoop.
|
|
1085
|
+
const stale = report.teacherLoop.watchState === "stale_snapshot" || (report.teacherLoop.latestFreshness === "stale" && report.teacherLoop.lastNoOpReason !== "no_teacher_artifacts");
|
|
1080
1086
|
const idle = report.teacherLoop.running === false &&
|
|
1081
1087
|
(report.teacherLoop.queueDepth ?? 0) === 0 &&
|
|
1082
1088
|
report.teacherLoop.failureMode === "none";
|
|
@@ -1343,7 +1349,7 @@ function buildCompactStatusHeader(status, report, options) {
|
|
|
1343
1349
|
const tracedLearning = options.tracedLearning ?? buildTracedLearningStatusSurface(status.host.activationRoot);
|
|
1344
1350
|
return [
|
|
1345
1351
|
`lifecycle attach=${status.attachment.state} learner=${yesNo(status.passiveLearning.learnerRunning)} watch=${summarizeStatusWatchState(status)} export=${status.passiveLearning.exportState} promote=${summarizeStatusPromotionState(status)} serve=${summarizeStatusServeReality(status)}`,
|
|
1346
|
-
`hook install=${hookLoad.installState} loadability=${hookLoad.loadability} loadProof=${hookLoad.loadProof} layout=${status.hook.installLayout ?? "unverified"} additional=${status.hook.additionalInstallCount ?? 0}
|
|
1352
|
+
`hook install=${hookLoad.installState} loadability=${hookLoad.loadability} loadProof=${hookLoad.loadProof} layout=${status.hook.installLayout ?? "unverified"} additional=${status.hook.additionalInstallCount ?? 0} severity=${hookLoad.guardSeverity} actionability=${hookLoad.guardActionability} summary=${hookLoad.guardSummary}`,
|
|
1347
1353
|
`attachTruth current=${attachmentTruth.currentProfileLabel} hook=${attachmentTruth.hookFiles} config=${attachmentTruth.configLoad} runtime=${attachmentTruth.runtimeLoad} watcher=${attachmentTruth.watcher} attachedSet=${formatAttachedProfileTruthCompactList(attachmentTruth.attachedProfiles)} why=${attachmentTruth.detail}`,
|
|
1348
1354
|
`passive firstExport=${yesNo(status.passiveLearning.firstExportOccurred)} backlog=${status.passiveLearning.backlogState} pending=${formatStatusNullableNumber(status.passiveLearning.pendingLive)}/${formatStatusNullableNumber(status.passiveLearning.pendingBackfill)}`,
|
|
1349
1355
|
`serving pack=${status.passiveLearning.currentServingPackId ?? "none"} lastExport=${status.passiveLearning.lastExportAt ?? "none"} lastPromotion=${status.passiveLearning.lastPromotionAt ?? "none"}`,
|
|
@@ -1352,6 +1358,7 @@ function buildCompactStatusHeader(status, report, options) {
|
|
|
1352
1358
|
`changed ${status.passiveLearning.lastObservedDelta.explanation}`,
|
|
1353
1359
|
`explain ${status.brain.summary}`,
|
|
1354
1360
|
`graph blocks=${report.graph.blockCount ?? "none"} strongest=${report.graph.strongestBlockId ?? "none"} latest=${report.graph.latestMaterialization.packId ?? "none"} latestChanged=${yesNo(report.graph.latestMaterialization.changed)} connect=${formatCompactGraphConnectDiagnostics(report.graph.latestMaterialization.connectDiagnostics ?? report.graph.connectDiagnostics)}`,
|
|
1361
|
+
`attribution ${formatOperatorLearningAttributionSummary({ status })}`,
|
|
1355
1362
|
`teacher model=${teacher.model} enabled=${yesNo(teacher.enabled)} healthy=${yesNo(teacher.healthy)} stale=${yesNo(teacher.stale)} idle=${yesNo(teacher.idle)} cycle=${teacher.latestCycle} why=${teacher.detail}`,
|
|
1356
1363
|
`embedder model=${embedder.model} provisioned=${yesNo(embedder.provisioned)} live=${yesNo(embedder.live)} why=${embedder.detail}`,
|
|
1357
1364
|
`routeFn available=${yesNo(routeFn.available)} freshness=${routeFn.freshness} trained=${routeFn.trainedAt ?? "none"} updated=${routeFn.updatedAt ?? "none"} used=${routeFn.usedAt ?? "none"} why=${routeFn.detail}`,
|
|
@@ -1389,6 +1396,7 @@ function formatCurrentProfileStatusSummary(status, report, targetInspection, opt
|
|
|
1389
1396
|
})}`,
|
|
1390
1397
|
`host runtime=${status.host.runtimeOwner} activation=${status.host.activationRoot}`,
|
|
1391
1398
|
`profile selector=${status.profile.selector}${profileIdSuffix} attachment=${status.attachment.state} policy=${status.attachment.policyMode}`,
|
|
1399
|
+
`guard severity=${status.hook.guardSeverity} actionability=${status.hook.guardActionability} action=${status.hook.guardAction} summary=${status.hook.guardSummary}`,
|
|
1392
1400
|
`attachTruth current=${attachmentTruth.currentProfileLabel} hook=${attachmentTruth.hookFiles} config=${attachmentTruth.configLoad} runtime=${attachmentTruth.runtimeLoad} watcher=${attachmentTruth.watcher} detail=${attachmentTruth.detail}`,
|
|
1393
1401
|
`attachedSet ${formatAttachedProfileTruthDetailedList(attachmentTruth.attachedProfiles)} proofPath=${shortenPath(attachmentTruth.runtimeProofPath)} proofError=${attachmentTruth.runtimeProofError ?? "none"}`,
|
|
1394
1402
|
`manyProfile surface=${report.manyProfile.operatorSurface} policy=${report.manyProfile.declaredAttachmentPolicy} intent=${report.manyProfile.sameGatewayIntent} checkedProof=${report.manyProfile.checkedInProofTopology} sameGatewayProof=${yesNo(report.manyProfile.sameGatewayProof)} sharedWriteProof=${yesNo(report.manyProfile.sharedWriteSafetyProof)}`,
|
|
@@ -1409,6 +1417,7 @@ function formatCurrentProfileStatusSummary(status, report, targetInspection, opt
|
|
|
1409
1417
|
learningPath: report.learningPath,
|
|
1410
1418
|
tracedLearning
|
|
1411
1419
|
})}`,
|
|
1420
|
+
`attribution ${formatOperatorLearningAttributionSummary({ status })}`,
|
|
1412
1421
|
`learning state=${report.learning.backlogState} bootstrapped=${yesNo(report.learning.bootstrapped)} mode=${report.learning.mode} next=${report.learning.nextPriorityLane} priority=${report.learning.nextPriorityBucket} pending=${report.learning.pendingLive ?? "none"}/${report.learning.pendingBackfill ?? "none"} buckets=${formatLearningBuckets(report)} warn=${formatLearningWarnings(report)} lastPack=${report.learning.lastMaterializedPackId ?? "none"} detail=${report.learning.detail}`,
|
|
1413
1422
|
`traced ${formatTracedLearningSurface(tracedLearning)}`,
|
|
1414
1423
|
`teacherProof ${formatTeacherLoopSummary(report)}`,
|
|
@@ -1452,6 +1461,184 @@ function buildGatewayRestartCommand(profileId) {
|
|
|
1452
1461
|
function buildGatewayStatusCommand(profileId) {
|
|
1453
1462
|
return `env -i HOME="$HOME" PATH="$PATH" openclaw --profile ${quoteShellArg(profileId)} gateway status`;
|
|
1454
1463
|
}
|
|
1464
|
+
function buildGatewayRestartArgs(profileId) {
|
|
1465
|
+
return profileId === null ? ["gateway", "restart"] : ["gateway", "restart", "--profile", profileId];
|
|
1466
|
+
}
|
|
1467
|
+
function shellJoin(parts) {
|
|
1468
|
+
return parts
|
|
1469
|
+
.map((part) => {
|
|
1470
|
+
if (/^[A-Za-z0-9_./:@=-]+$/.test(part)) {
|
|
1471
|
+
return part;
|
|
1472
|
+
}
|
|
1473
|
+
return JSON.stringify(part);
|
|
1474
|
+
})
|
|
1475
|
+
.join(" ");
|
|
1476
|
+
}
|
|
1477
|
+
function runCapturedExternalCommand(command, args, options = {}) {
|
|
1478
|
+
const result = spawnSync(command, args, {
|
|
1479
|
+
cwd: options.cwd ?? process.cwd(),
|
|
1480
|
+
env: options.env ?? process.env,
|
|
1481
|
+
encoding: "utf8",
|
|
1482
|
+
stdio: "pipe"
|
|
1483
|
+
});
|
|
1484
|
+
return {
|
|
1485
|
+
command,
|
|
1486
|
+
args,
|
|
1487
|
+
shellCommand: shellJoin([command, ...args]),
|
|
1488
|
+
stdout: result.stdout ?? "",
|
|
1489
|
+
stderr: result.stderr ?? "",
|
|
1490
|
+
exitCode: typeof result.status === "number" ? result.status : null,
|
|
1491
|
+
signal: result.signal ?? null,
|
|
1492
|
+
error: result.error ? toErrorMessage(result.error) : null
|
|
1493
|
+
};
|
|
1494
|
+
}
|
|
1495
|
+
function summarizeCapturedCommandFailure(capture) {
|
|
1496
|
+
const parts = [];
|
|
1497
|
+
if (capture.error !== null) {
|
|
1498
|
+
parts.push(capture.error);
|
|
1499
|
+
}
|
|
1500
|
+
if (capture.stderr.trim().length > 0) {
|
|
1501
|
+
parts.push(capture.stderr.trim());
|
|
1502
|
+
}
|
|
1503
|
+
if (capture.stdout.trim().length > 0) {
|
|
1504
|
+
parts.push(capture.stdout.trim());
|
|
1505
|
+
}
|
|
1506
|
+
if (capture.exitCode !== null) {
|
|
1507
|
+
parts.push(`exitCode=${capture.exitCode}`);
|
|
1508
|
+
}
|
|
1509
|
+
return parts.length === 0 ? "no command output was captured" : parts.join(" | ");
|
|
1510
|
+
}
|
|
1511
|
+
function readTextFileIfExists(filePath) {
|
|
1512
|
+
if (filePath === null || !existsSync(filePath)) {
|
|
1513
|
+
return null;
|
|
1514
|
+
}
|
|
1515
|
+
try {
|
|
1516
|
+
return readFileSync(filePath, "utf8");
|
|
1517
|
+
}
|
|
1518
|
+
catch {
|
|
1519
|
+
return null;
|
|
1520
|
+
}
|
|
1521
|
+
}
|
|
1522
|
+
function readInstallRuntimeFingerprint(openclawHome) {
|
|
1523
|
+
const installedPlugin = findInstalledOpenClawBrainPlugin(openclawHome);
|
|
1524
|
+
const selectedInstall = installedPlugin.selectedInstall;
|
|
1525
|
+
const hook = inspectOpenClawBrainHookStatus(openclawHome);
|
|
1526
|
+
const resolvedActivationRoot = resolveActivationRoot({
|
|
1527
|
+
openclawHome,
|
|
1528
|
+
quiet: true
|
|
1529
|
+
});
|
|
1530
|
+
const { config } = readOpenClawJsonConfig(openclawHome);
|
|
1531
|
+
return {
|
|
1532
|
+
selectedInstall: selectedInstall === null
|
|
1533
|
+
? null
|
|
1534
|
+
: {
|
|
1535
|
+
extensionDir: selectedInstall.extensionDir,
|
|
1536
|
+
manifestId: selectedInstall.manifestId,
|
|
1537
|
+
installId: selectedInstall.installId,
|
|
1538
|
+
packageName: selectedInstall.packageName,
|
|
1539
|
+
installLayout: selectedInstall.installLayout
|
|
1540
|
+
},
|
|
1541
|
+
installLayout: hook.installLayout,
|
|
1542
|
+
hookPath: hook.hookPath,
|
|
1543
|
+
hookState: hook.installState,
|
|
1544
|
+
loadability: hook.loadability,
|
|
1545
|
+
activationRoot: resolvedActivationRoot.trim().length === 0 ? null : path.resolve(resolvedActivationRoot),
|
|
1546
|
+
loaderSource: readTextFileIfExists(selectedInstall?.loaderEntryPath ?? null),
|
|
1547
|
+
runtimeGuardSource: readTextFileIfExists(selectedInstall?.runtimeGuardPath ?? null),
|
|
1548
|
+
pluginsConfig: JSON.stringify(config.plugins ?? null)
|
|
1549
|
+
};
|
|
1550
|
+
}
|
|
1551
|
+
function runOpenClawBrainConvergePluginStep(openclawHome) {
|
|
1552
|
+
const before = readInstallRuntimeFingerprint(openclawHome);
|
|
1553
|
+
const plan = planOpenClawBrainConvergePluginAction(before);
|
|
1554
|
+
const commandArgs = plan.action === "install"
|
|
1555
|
+
? ["plugins", "install", plan.packageSpec]
|
|
1556
|
+
: ["plugins", "update", plan.pluginId];
|
|
1557
|
+
const capture = runCapturedExternalCommand("openclaw", commandArgs);
|
|
1558
|
+
if (capture.error !== null || capture.exitCode !== 0) {
|
|
1559
|
+
const hasAuthoritativeNativePlugin = before.selectedInstall !== null && before.installLayout === "native_package_plugin";
|
|
1560
|
+
if (plan.action === "update" && hasAuthoritativeNativePlugin) {
|
|
1561
|
+
return {
|
|
1562
|
+
plan,
|
|
1563
|
+
command: capture.shellCommand,
|
|
1564
|
+
changed: false,
|
|
1565
|
+
changeReasons: [],
|
|
1566
|
+
detail: `Skipped plugin-manager refresh because the existing split-package plugin is already authoritative and \
|
|
1567
|
+
\`${capture.shellCommand}\` failed: ${summarizeCapturedCommandFailure(capture)}`,
|
|
1568
|
+
warning: `plugin-manager refresh skipped after \`${capture.shellCommand}\` failed; keeping the existing authoritative split-package plugin for this converge run`,
|
|
1569
|
+
capture,
|
|
1570
|
+
before,
|
|
1571
|
+
after: before
|
|
1572
|
+
};
|
|
1573
|
+
}
|
|
1574
|
+
throw new Error(`OpenClaw plugin-manager ${plan.action} failed for ${path.resolve(openclawHome)}. Tried \`${capture.shellCommand}\`. Detail: ${summarizeCapturedCommandFailure(capture)}`);
|
|
1575
|
+
}
|
|
1576
|
+
const after = readInstallRuntimeFingerprint(openclawHome);
|
|
1577
|
+
const diff = diffOpenClawBrainConvergeRuntimeFingerprint(before, after);
|
|
1578
|
+
return {
|
|
1579
|
+
plan,
|
|
1580
|
+
command: capture.shellCommand,
|
|
1581
|
+
changed: diff.changed,
|
|
1582
|
+
changeReasons: diff.reasons,
|
|
1583
|
+
detail: diff.changed
|
|
1584
|
+
? `${plan.action === "install" ? "Installed" : "Refreshed"} plugin-manager state: ${describeOpenClawBrainConvergeChangeReasons(diff.reasons)}`
|
|
1585
|
+
: `${plan.action === "install" ? "Ran install" : "Ran update"} through the OpenClaw plugin manager, but no runtime-affecting plugin delta was detected`,
|
|
1586
|
+
warning: null,
|
|
1587
|
+
capture,
|
|
1588
|
+
before,
|
|
1589
|
+
after
|
|
1590
|
+
};
|
|
1591
|
+
}
|
|
1592
|
+
function inspectInstallConvergeVerification(parsed) {
|
|
1593
|
+
const targetInspection = inspectOpenClawHome(parsed.openclawHome);
|
|
1594
|
+
const operatorInput = {
|
|
1595
|
+
activationRoot: parsed.activationRoot,
|
|
1596
|
+
eventExportPath: null,
|
|
1597
|
+
teacherSnapshotPath: resolveOperatorTeacherSnapshotPath(parsed.activationRoot, null),
|
|
1598
|
+
updatedAt: null,
|
|
1599
|
+
brainAttachmentPolicy: null,
|
|
1600
|
+
openclawHome: parsed.openclawHome,
|
|
1601
|
+
...(targetInspection.profileId === null ? {} : { profileId: targetInspection.profileId })
|
|
1602
|
+
};
|
|
1603
|
+
const status = describeCurrentProfileBrainStatus(operatorInput);
|
|
1604
|
+
const report = buildOperatorSurfaceReport(operatorInput);
|
|
1605
|
+
const normalizedStatusAndReport = applyAttachmentPolicyTruth(status, report);
|
|
1606
|
+
const installHook = summarizeStatusInstallHook(parsed.openclawHome);
|
|
1607
|
+
const attachmentTruth = summarizeStatusAttachmentTruth({
|
|
1608
|
+
activationRoot: parsed.activationRoot,
|
|
1609
|
+
openclawHome: parsed.openclawHome,
|
|
1610
|
+
status: normalizedStatusAndReport.status
|
|
1611
|
+
});
|
|
1612
|
+
const displayedStatus = summarizeDisplayedStatus(normalizedStatusAndReport.status, installHook);
|
|
1613
|
+
const routeFn = summarizeStatusRouteFn(normalizedStatusAndReport.status, normalizedStatusAndReport.report);
|
|
1614
|
+
return {
|
|
1615
|
+
targetInspection,
|
|
1616
|
+
status: normalizedStatusAndReport.status,
|
|
1617
|
+
report: normalizedStatusAndReport.report,
|
|
1618
|
+
installHook,
|
|
1619
|
+
attachmentTruth,
|
|
1620
|
+
displayedStatus,
|
|
1621
|
+
routeFn,
|
|
1622
|
+
nextStep: buildStatusNextStep(normalizedStatusAndReport.status, normalizedStatusAndReport.report, {
|
|
1623
|
+
openclawHome: parsed.openclawHome,
|
|
1624
|
+
installHook
|
|
1625
|
+
}),
|
|
1626
|
+
summaryLine: `STATUS ${displayedStatus}; hook=${installHook.state}/${installHook.loadability}; guard=${normalizedStatusAndReport.status.hook.guardSeverity}/${normalizedStatusAndReport.status.hook.guardActionability}; runtime=${attachmentTruth.runtimeLoad}; loadProof=${normalizedStatusAndReport.status.hook.loadProof}; serve=${normalizedStatusAndReport.status.brainStatus.serveState}`,
|
|
1627
|
+
facts: {
|
|
1628
|
+
installLayout: normalizedStatusAndReport.status.hook.installLayout ?? installHook.installLayout ?? null,
|
|
1629
|
+
installState: installHook.state,
|
|
1630
|
+
loadability: installHook.loadability,
|
|
1631
|
+
guardSeverity: normalizedStatusAndReport.status.hook.guardSeverity,
|
|
1632
|
+
guardActionability: normalizedStatusAndReport.status.hook.guardActionability,
|
|
1633
|
+
displayedStatus,
|
|
1634
|
+
runtimeLoad: attachmentTruth.runtimeLoad,
|
|
1635
|
+
loadProof: normalizedStatusAndReport.status.hook.loadProof,
|
|
1636
|
+
serveState: normalizedStatusAndReport.status.brainStatus.serveState,
|
|
1637
|
+
routeFnAvailable: routeFn.available,
|
|
1638
|
+
awaitingFirstExport: normalizedStatusAndReport.status.brainStatus.awaitingFirstExport
|
|
1639
|
+
}
|
|
1640
|
+
};
|
|
1641
|
+
}
|
|
1455
1642
|
function buildInstallCommand(openclawHome) {
|
|
1456
1643
|
return `openclawbrain install --openclaw-home ${quoteShellArg(openclawHome)}`;
|
|
1457
1644
|
}
|
|
@@ -2933,10 +3120,133 @@ function buildExtensionPluginManifest() {
|
|
|
2933
3120
|
name: "OpenClawBrain",
|
|
2934
3121
|
description: "Learned memory and context from OpenClawBrain",
|
|
2935
3122
|
version: packageMetadata.version,
|
|
3123
|
+
uiHints: {
|
|
3124
|
+
brainRoot: {
|
|
3125
|
+
label: "Brain Root",
|
|
3126
|
+
help: "Directory containing OpenClawBrain state.db and immutable packs"
|
|
3127
|
+
},
|
|
3128
|
+
brainEmbeddingProvider: {
|
|
3129
|
+
label: "Embedding Provider",
|
|
3130
|
+
help: "Provider used for learned retrieval embeddings"
|
|
3131
|
+
},
|
|
3132
|
+
brainEmbeddingModel: {
|
|
3133
|
+
label: "Embedding Model",
|
|
3134
|
+
help: "Embedding model used for init, retrieval, and brain_teach"
|
|
3135
|
+
},
|
|
3136
|
+
brainEmbeddingBaseUrl: {
|
|
3137
|
+
label: "Embedding Base URL",
|
|
3138
|
+
help: "Optional base URL override for the embedding provider endpoint"
|
|
3139
|
+
},
|
|
3140
|
+
brainMaxCompileMs: {
|
|
3141
|
+
label: "Brain Compile Deadline",
|
|
3142
|
+
help: "Soft wall-clock deadline in milliseconds for brain assembly phase-boundary checks"
|
|
3143
|
+
},
|
|
3144
|
+
brainBudgetFraction: {
|
|
3145
|
+
label: "Brain Budget Fraction",
|
|
3146
|
+
help: "Fraction of the available token budget reserved for retrieval before final prompt clipping"
|
|
3147
|
+
},
|
|
3148
|
+
brainMaxHops: {
|
|
3149
|
+
label: "Brain Max Hops",
|
|
3150
|
+
help: "Maximum learned-retrieval graph expansion depth per query"
|
|
3151
|
+
},
|
|
3152
|
+
brainMaxFanoutPerNode: {
|
|
3153
|
+
label: "Brain Max Fanout Per Node",
|
|
3154
|
+
help: "Maximum accepted traversals from a single source node expansion"
|
|
3155
|
+
},
|
|
3156
|
+
brainMaxFrontierSize: {
|
|
3157
|
+
label: "Brain Max Frontier Size",
|
|
3158
|
+
help: "Maximum traversal frontier size during learned retrieval"
|
|
3159
|
+
},
|
|
3160
|
+
brainMaxSeeds: {
|
|
3161
|
+
label: "Brain Max Seeds",
|
|
3162
|
+
help: "Maximum seed nodes admitted into learned retrieval before graph expansion"
|
|
3163
|
+
},
|
|
3164
|
+
brainSemanticThreshold: {
|
|
3165
|
+
label: "Brain Semantic Threshold",
|
|
3166
|
+
help: "Minimum semantic similarity required for seed admission during learned retrieval"
|
|
3167
|
+
},
|
|
3168
|
+
brainShadowMode: {
|
|
3169
|
+
label: "Brain Shadow Mode",
|
|
3170
|
+
help: "Run learned retrieval for telemetry only without injecting brain context into prompts"
|
|
3171
|
+
},
|
|
3172
|
+
brainWorkerMode: {
|
|
3173
|
+
label: "Worker Mode",
|
|
3174
|
+
help: "Run the learner in a supervised child process (default) or fall back to in-process mode"
|
|
3175
|
+
},
|
|
3176
|
+
brainWorkerHeartbeatTimeoutMs: {
|
|
3177
|
+
label: "Worker Heartbeat Timeout",
|
|
3178
|
+
help: "Milliseconds to wait before treating the supervised learner worker as stalled"
|
|
3179
|
+
},
|
|
3180
|
+
brainWorkerRestartDelayMs: {
|
|
3181
|
+
label: "Worker Restart Delay",
|
|
3182
|
+
help: "Milliseconds to wait before restarting the supervised learner worker after exit or crash"
|
|
3183
|
+
}
|
|
3184
|
+
},
|
|
2936
3185
|
configSchema: {
|
|
2937
3186
|
type: "object",
|
|
2938
3187
|
additionalProperties: false,
|
|
2939
|
-
properties: {
|
|
3188
|
+
properties: {
|
|
3189
|
+
brainEnabled: {
|
|
3190
|
+
type: "boolean"
|
|
3191
|
+
},
|
|
3192
|
+
brainRoot: {
|
|
3193
|
+
type: "string"
|
|
3194
|
+
},
|
|
3195
|
+
brainEmbeddingProvider: {
|
|
3196
|
+
type: "string"
|
|
3197
|
+
},
|
|
3198
|
+
brainEmbeddingModel: {
|
|
3199
|
+
type: "string"
|
|
3200
|
+
},
|
|
3201
|
+
brainEmbeddingBaseUrl: {
|
|
3202
|
+
type: "string"
|
|
3203
|
+
},
|
|
3204
|
+
brainMaxCompileMs: {
|
|
3205
|
+
type: "integer",
|
|
3206
|
+
minimum: 0
|
|
3207
|
+
},
|
|
3208
|
+
brainBudgetFraction: {
|
|
3209
|
+
type: "number",
|
|
3210
|
+
minimum: 0,
|
|
3211
|
+
maximum: 1
|
|
3212
|
+
},
|
|
3213
|
+
brainMaxHops: {
|
|
3214
|
+
type: "integer",
|
|
3215
|
+
minimum: 1
|
|
3216
|
+
},
|
|
3217
|
+
brainMaxFanoutPerNode: {
|
|
3218
|
+
type: "integer",
|
|
3219
|
+
minimum: 1
|
|
3220
|
+
},
|
|
3221
|
+
brainMaxFrontierSize: {
|
|
3222
|
+
type: "integer",
|
|
3223
|
+
minimum: 1
|
|
3224
|
+
},
|
|
3225
|
+
brainMaxSeeds: {
|
|
3226
|
+
type: "integer",
|
|
3227
|
+
minimum: 1
|
|
3228
|
+
},
|
|
3229
|
+
brainSemanticThreshold: {
|
|
3230
|
+
type: "number",
|
|
3231
|
+
minimum: 0,
|
|
3232
|
+
maximum: 1
|
|
3233
|
+
},
|
|
3234
|
+
brainShadowMode: {
|
|
3235
|
+
type: "boolean"
|
|
3236
|
+
},
|
|
3237
|
+
brainWorkerMode: {
|
|
3238
|
+
type: "string",
|
|
3239
|
+
enum: ["child", "in_process"]
|
|
3240
|
+
},
|
|
3241
|
+
brainWorkerHeartbeatTimeoutMs: {
|
|
3242
|
+
type: "integer",
|
|
3243
|
+
minimum: 1000
|
|
3244
|
+
},
|
|
3245
|
+
brainWorkerRestartDelayMs: {
|
|
3246
|
+
type: "integer",
|
|
3247
|
+
minimum: 0
|
|
3248
|
+
}
|
|
3249
|
+
}
|
|
2940
3250
|
}
|
|
2941
3251
|
}, null, 2) + "\n";
|
|
2942
3252
|
}
|
|
@@ -3238,7 +3548,7 @@ function runHistoryCommand(parsed) {
|
|
|
3238
3548
|
}
|
|
3239
3549
|
return 0;
|
|
3240
3550
|
}
|
|
3241
|
-
function
|
|
3551
|
+
function executeProfileHookAttachCommand(parsed) {
|
|
3242
3552
|
const steps = [];
|
|
3243
3553
|
const commandLabel = parsed.command.toUpperCase();
|
|
3244
3554
|
const isInstall = parsed.command === "install";
|
|
@@ -3458,106 +3768,273 @@ function runProfileHookAttachCommand(parsed) {
|
|
|
3458
3768
|
? `Install: kept healthy active pack ${activationPlan.activePackId} in place`
|
|
3459
3769
|
: `Attach: rewired the profile hook to healthy active pack ${activationPlan.activePackId}`
|
|
3460
3770
|
];
|
|
3461
|
-
|
|
3462
|
-
|
|
3463
|
-
|
|
3464
|
-
|
|
3465
|
-
|
|
3466
|
-
|
|
3467
|
-
|
|
3468
|
-
|
|
3469
|
-
|
|
3470
|
-
|
|
3471
|
-
|
|
3472
|
-
|
|
3771
|
+
return {
|
|
3772
|
+
command: parsed.command,
|
|
3773
|
+
commandLabel,
|
|
3774
|
+
openclawHome: parsed.openclawHome,
|
|
3775
|
+
openclawHomeSource: parsed.openclawHomeSource,
|
|
3776
|
+
openclawTarget: {
|
|
3777
|
+
layout: targetInspection.layout,
|
|
3778
|
+
detail: describeOpenClawHomeInspection(targetInspection),
|
|
3779
|
+
profileId: targetInspection.profileId,
|
|
3780
|
+
profileSource: targetInspection.profileSource,
|
|
3781
|
+
configuredProfileIds: targetInspection.configuredProfileIds
|
|
3782
|
+
},
|
|
3783
|
+
activationRoot: parsed.activationRoot,
|
|
3784
|
+
resolvedInputs: {
|
|
3785
|
+
activationRoot: {
|
|
3786
|
+
value: parsed.activationRoot,
|
|
3787
|
+
source: parsed.activationRootSource
|
|
3473
3788
|
},
|
|
3474
|
-
|
|
3475
|
-
|
|
3476
|
-
|
|
3477
|
-
|
|
3478
|
-
|
|
3479
|
-
|
|
3480
|
-
|
|
3481
|
-
|
|
3482
|
-
|
|
3483
|
-
|
|
3789
|
+
workspaceId: {
|
|
3790
|
+
value: parsed.workspaceId,
|
|
3791
|
+
source: parsed.workspaceIdSource
|
|
3792
|
+
}
|
|
3793
|
+
},
|
|
3794
|
+
workspaceId: parsed.workspaceId,
|
|
3795
|
+
shared: parsed.shared,
|
|
3796
|
+
embedderProvision: embedderProvision === null
|
|
3797
|
+
? null
|
|
3798
|
+
: {
|
|
3799
|
+
skipped: parsed.skipEmbedderProvision,
|
|
3800
|
+
source: parsed.skipEmbedderProvisionSource,
|
|
3801
|
+
model: embedderProvision.model,
|
|
3802
|
+
baseUrl: embedderProvision.baseUrl
|
|
3484
3803
|
},
|
|
3485
|
-
|
|
3486
|
-
|
|
3487
|
-
|
|
3488
|
-
|
|
3489
|
-
:
|
|
3490
|
-
|
|
3491
|
-
|
|
3492
|
-
|
|
3493
|
-
|
|
3494
|
-
|
|
3495
|
-
|
|
3496
|
-
|
|
3497
|
-
|
|
3498
|
-
|
|
3499
|
-
|
|
3500
|
-
|
|
3501
|
-
|
|
3502
|
-
|
|
3503
|
-
|
|
3504
|
-
detectedLocally: providerDefaults.defaults.teacher.detectedLocally ?? false
|
|
3505
|
-
},
|
|
3506
|
-
embedder: providerDefaults.defaults.embedder === undefined
|
|
3507
|
-
? null
|
|
3508
|
-
: {
|
|
3509
|
-
provider: providerDefaults.defaults.embedder.provider ?? null,
|
|
3510
|
-
model: providerDefaults.defaults.embedder.model ?? null
|
|
3511
|
-
},
|
|
3512
|
-
teacherBaseUrl: providerDefaults.defaults.teacherBaseUrl ?? null,
|
|
3513
|
-
embedderBaseUrl: providerDefaults.defaults.embedderBaseUrl ?? null
|
|
3514
|
-
},
|
|
3515
|
-
pluginConfigRepair,
|
|
3516
|
-
learnerService,
|
|
3517
|
-
brainFeedback: {
|
|
3518
|
-
hookPath: brainFeedback.hookPath,
|
|
3519
|
-
hookLayout: brainFeedback.hookLayout,
|
|
3520
|
-
providerDefaultsPath: brainFeedback.providerDefaultsPath,
|
|
3521
|
-
profile: brainFeedback.profile,
|
|
3522
|
-
attachment: brainFeedback.attachment,
|
|
3523
|
-
restart: brainFeedback.restart,
|
|
3524
|
-
embedder: brainFeedback.embedder,
|
|
3525
|
-
teacher: brainFeedback.teacher,
|
|
3526
|
-
learnerService: brainFeedback.learnerService,
|
|
3527
|
-
startup: brainFeedback.startup,
|
|
3528
|
-
provedNow: brainFeedback.provedNow,
|
|
3529
|
-
notYetProved: brainFeedback.notYetProved,
|
|
3530
|
-
lines: brainFeedback.lines
|
|
3804
|
+
providerDefaults: providerDefaults === null
|
|
3805
|
+
? null
|
|
3806
|
+
: {
|
|
3807
|
+
path: providerDefaults.path,
|
|
3808
|
+
teacher: providerDefaults.defaults.teacher === undefined
|
|
3809
|
+
? null
|
|
3810
|
+
: {
|
|
3811
|
+
provider: providerDefaults.defaults.teacher.provider ?? null,
|
|
3812
|
+
model: providerDefaults.defaults.teacher.model ?? null,
|
|
3813
|
+
detectedLocally: providerDefaults.defaults.teacher.detectedLocally ?? false
|
|
3814
|
+
},
|
|
3815
|
+
embedder: providerDefaults.defaults.embedder === undefined
|
|
3816
|
+
? null
|
|
3817
|
+
: {
|
|
3818
|
+
provider: providerDefaults.defaults.embedder.provider ?? null,
|
|
3819
|
+
model: providerDefaults.defaults.embedder.model ?? null
|
|
3820
|
+
},
|
|
3821
|
+
teacherBaseUrl: providerDefaults.defaults.teacherBaseUrl ?? null,
|
|
3822
|
+
embedderBaseUrl: providerDefaults.defaults.embedderBaseUrl ?? null
|
|
3531
3823
|
},
|
|
3532
|
-
|
|
3533
|
-
|
|
3534
|
-
|
|
3535
|
-
|
|
3536
|
-
|
|
3537
|
-
|
|
3538
|
-
|
|
3824
|
+
pluginConfigRepair,
|
|
3825
|
+
learnerService,
|
|
3826
|
+
brainFeedback: {
|
|
3827
|
+
hookPath: brainFeedback.hookPath,
|
|
3828
|
+
hookLayout: brainFeedback.hookLayout,
|
|
3829
|
+
providerDefaultsPath: brainFeedback.providerDefaultsPath,
|
|
3830
|
+
profile: brainFeedback.profile,
|
|
3831
|
+
attachment: brainFeedback.attachment,
|
|
3832
|
+
restart: brainFeedback.restart,
|
|
3833
|
+
embedder: brainFeedback.embedder,
|
|
3834
|
+
teacher: brainFeedback.teacher,
|
|
3835
|
+
learnerService: brainFeedback.learnerService,
|
|
3836
|
+
startup: brainFeedback.startup,
|
|
3837
|
+
provedNow: brainFeedback.provedNow,
|
|
3838
|
+
notYetProved: brainFeedback.notYetProved,
|
|
3839
|
+
lines: brainFeedback.lines
|
|
3840
|
+
},
|
|
3841
|
+
extensionDir,
|
|
3842
|
+
lifecycleSummary,
|
|
3843
|
+
preflightSummary,
|
|
3844
|
+
restartGuidance,
|
|
3845
|
+
nextSteps,
|
|
3846
|
+
steps
|
|
3847
|
+
};
|
|
3848
|
+
}
|
|
3849
|
+
function emitProfileHookAttachCommandResult(result, parsed) {
|
|
3850
|
+
if (parsed.json) {
|
|
3851
|
+
console.log(JSON.stringify(result, null, 2));
|
|
3852
|
+
return;
|
|
3539
3853
|
}
|
|
3540
|
-
|
|
3541
|
-
|
|
3542
|
-
|
|
3543
|
-
|
|
3544
|
-
|
|
3545
|
-
|
|
3546
|
-
|
|
3547
|
-
|
|
3548
|
-
|
|
3549
|
-
|
|
3550
|
-
|
|
3551
|
-
|
|
3552
|
-
|
|
3553
|
-
|
|
3554
|
-
console.log(`Embedder: ${buildInstallEmbedderProvisionCommand(embedderProvision.baseUrl, embedderProvision.model)}`);
|
|
3555
|
-
}
|
|
3854
|
+
console.log(`${result.commandLabel} complete\n`);
|
|
3855
|
+
console.log("Brain feedback:");
|
|
3856
|
+
for (const line of result.brainFeedback.lines) {
|
|
3857
|
+
console.log(` ${line}`);
|
|
3858
|
+
}
|
|
3859
|
+
console.log(`Restart: ${result.restartGuidance}`);
|
|
3860
|
+
if (result.brainFeedback.restart.gatewayStatusCommand !== null) {
|
|
3861
|
+
console.log(`Gateway: Confirm OpenClaw after restart: ${result.brainFeedback.restart.gatewayStatusCommand}`);
|
|
3862
|
+
}
|
|
3863
|
+
console.log(`Check: ${buildInstallStatusCommand(result.activationRoot)}`);
|
|
3864
|
+
console.log(`Proof: ${buildProofCommandForOpenClawHome(result.openclawHome)}`);
|
|
3865
|
+
console.log(`Learner: ${buildLearnerServiceStatusCommand(result.activationRoot)}`);
|
|
3866
|
+
if (result.embedderProvision !== null && result.embedderProvision.skipped) {
|
|
3867
|
+
console.log(`Embedder: ${buildInstallEmbedderProvisionCommand(result.embedderProvision.baseUrl, result.embedderProvision.model)}`);
|
|
3556
3868
|
}
|
|
3869
|
+
}
|
|
3870
|
+
function runProfileHookAttachCommand(parsed) {
|
|
3871
|
+
const result = executeProfileHookAttachCommand(parsed);
|
|
3872
|
+
emitProfileHookAttachCommandResult(result, parsed);
|
|
3557
3873
|
return 0;
|
|
3558
3874
|
}
|
|
3875
|
+
function emitInstallConvergeResult(result, parsed) {
|
|
3876
|
+
if (parsed.json) {
|
|
3877
|
+
console.log(JSON.stringify(result, null, 2));
|
|
3878
|
+
return;
|
|
3879
|
+
}
|
|
3880
|
+
const heading = result.verdict.verdict === "failed"
|
|
3881
|
+
? "INSTALL converge failed"
|
|
3882
|
+
: "INSTALL converge complete";
|
|
3883
|
+
console.log(`${heading}\n`);
|
|
3884
|
+
console.log(`Plugin: ${result.plugin.detail}`);
|
|
3885
|
+
console.log(`Attach: ${result.attach.detail}`);
|
|
3886
|
+
console.log(`Restart: ${result.restart.detail}`);
|
|
3887
|
+
console.log(`Verify: ${result.verification.summaryLine}`);
|
|
3888
|
+
console.log(`Verdict: ${result.verdict.verdict}`);
|
|
3889
|
+
console.log(`Why: ${result.verdict.why}`);
|
|
3890
|
+
if (result.verdict.warnings.length > 0) {
|
|
3891
|
+
console.log(`Warnings: ${result.verdict.warnings.join("; ")}`);
|
|
3892
|
+
}
|
|
3893
|
+
console.log(`Next: ${result.verification.nextStep}`);
|
|
3894
|
+
console.log(`Proof: ${buildProofCommandForOpenClawHome(result.openclawHome)}`);
|
|
3895
|
+
console.log(`Learner: ${buildLearnerServiceStatusCommand(result.activationRoot)}`);
|
|
3896
|
+
}
|
|
3559
3897
|
function runInstallCommand(parsed) {
|
|
3560
|
-
|
|
3898
|
+
let pluginResult = null;
|
|
3899
|
+
let attachResult = null;
|
|
3900
|
+
try {
|
|
3901
|
+
validateOpenClawHome(parsed.openclawHome);
|
|
3902
|
+
pluginResult = runOpenClawBrainConvergePluginStep(parsed.openclawHome);
|
|
3903
|
+
attachResult = executeProfileHookAttachCommand(parsed);
|
|
3904
|
+
}
|
|
3905
|
+
catch (error) {
|
|
3906
|
+
const verdict = finalizeOpenClawBrainConvergeResult({
|
|
3907
|
+
stepFailure: toErrorMessage(error),
|
|
3908
|
+
verification: null,
|
|
3909
|
+
warnings: []
|
|
3910
|
+
});
|
|
3911
|
+
const failureResult = {
|
|
3912
|
+
command: "install",
|
|
3913
|
+
openclawHome: parsed.openclawHome,
|
|
3914
|
+
activationRoot: parsed.activationRoot,
|
|
3915
|
+
plugin: pluginResult ?? {
|
|
3916
|
+
action: null,
|
|
3917
|
+
command: null,
|
|
3918
|
+
changed: false,
|
|
3919
|
+
changeReasons: [],
|
|
3920
|
+
detail: "plugin-manager converge did not complete"
|
|
3921
|
+
},
|
|
3922
|
+
attach: attachResult ?? {
|
|
3923
|
+
changed: false,
|
|
3924
|
+
changeReasons: [],
|
|
3925
|
+
detail: "under-the-hood install/attach repair did not complete",
|
|
3926
|
+
hookLayout: null,
|
|
3927
|
+
hookPath: null
|
|
3928
|
+
},
|
|
3929
|
+
restart: {
|
|
3930
|
+
required: false,
|
|
3931
|
+
automatic: false,
|
|
3932
|
+
performed: false,
|
|
3933
|
+
command: null,
|
|
3934
|
+
detail: "restart did not run because install failed earlier",
|
|
3935
|
+
error: null
|
|
3936
|
+
},
|
|
3937
|
+
verification: {
|
|
3938
|
+
summaryLine: "verification did not run because install failed earlier",
|
|
3939
|
+
nextStep: "Fix the install failure and rerun openclawbrain install.",
|
|
3940
|
+
displayedStatus: "unknown",
|
|
3941
|
+
installState: "unknown",
|
|
3942
|
+
loadability: "unknown",
|
|
3943
|
+
runtimeLoad: "unknown",
|
|
3944
|
+
loadProof: "unknown",
|
|
3945
|
+
serveState: "unknown",
|
|
3946
|
+
routeFnAvailable: false,
|
|
3947
|
+
awaitingFirstExport: false
|
|
3948
|
+
},
|
|
3949
|
+
verdict
|
|
3950
|
+
};
|
|
3951
|
+
emitInstallConvergeResult(failureResult, parsed);
|
|
3952
|
+
return 1;
|
|
3953
|
+
}
|
|
3954
|
+
const attachDiff = diffOpenClawBrainConvergeRuntimeFingerprint(pluginResult.after, readInstallRuntimeFingerprint(parsed.openclawHome));
|
|
3955
|
+
const changeReasons = [...new Set([...pluginResult.changeReasons, ...attachDiff.reasons])];
|
|
3956
|
+
const restartPlan = buildOpenClawBrainConvergeRestartPlan({
|
|
3957
|
+
profileName: attachResult.brainFeedback.profile.exactProfileName,
|
|
3958
|
+
changeReasons
|
|
3959
|
+
});
|
|
3960
|
+
let restartCapture = null;
|
|
3961
|
+
let restartError = null;
|
|
3962
|
+
if (restartPlan.required && restartPlan.automatic) {
|
|
3963
|
+
restartCapture = runCapturedExternalCommand("openclaw", buildGatewayRestartArgs(attachResult.brainFeedback.profile.exactProfileName));
|
|
3964
|
+
if (restartCapture.error !== null || restartCapture.exitCode !== 0) {
|
|
3965
|
+
restartError = `Automatic restart failed after converge. Tried \`${restartCapture.shellCommand}\`. Detail: ${summarizeCapturedCommandFailure(restartCapture)}`;
|
|
3966
|
+
}
|
|
3967
|
+
}
|
|
3968
|
+
const verificationSnapshot = inspectInstallConvergeVerification(parsed);
|
|
3969
|
+
const verification = classifyOpenClawBrainConvergeVerification({
|
|
3970
|
+
...verificationSnapshot.facts,
|
|
3971
|
+
restartRequired: restartPlan.required,
|
|
3972
|
+
restartPerformed: restartPlan.required && restartPlan.automatic && restartError === null
|
|
3973
|
+
});
|
|
3974
|
+
const convergeWarnings = [];
|
|
3975
|
+
if (pluginResult.warning) {
|
|
3976
|
+
convergeWarnings.push(pluginResult.warning);
|
|
3977
|
+
}
|
|
3978
|
+
if (restartError !== null) {
|
|
3979
|
+
convergeWarnings.push(restartError);
|
|
3980
|
+
}
|
|
3981
|
+
const verdict = finalizeOpenClawBrainConvergeResult({
|
|
3982
|
+
stepFailure: null,
|
|
3983
|
+
verification,
|
|
3984
|
+
warnings: convergeWarnings
|
|
3985
|
+
});
|
|
3986
|
+
const result = {
|
|
3987
|
+
command: "install",
|
|
3988
|
+
openclawHome: parsed.openclawHome,
|
|
3989
|
+
activationRoot: parsed.activationRoot,
|
|
3990
|
+
plugin: {
|
|
3991
|
+
action: pluginResult.plan.action,
|
|
3992
|
+
command: pluginResult.command,
|
|
3993
|
+
changed: pluginResult.changed,
|
|
3994
|
+
changeReasons: pluginResult.changeReasons,
|
|
3995
|
+
detail: pluginResult.detail
|
|
3996
|
+
},
|
|
3997
|
+
attach: {
|
|
3998
|
+
changed: attachDiff.changed,
|
|
3999
|
+
changeReasons: attachDiff.reasons,
|
|
4000
|
+
detail: attachDiff.changed
|
|
4001
|
+
? `Converged hook/install repair: ${describeOpenClawBrainConvergeChangeReasons(attachDiff.reasons)}`
|
|
4002
|
+
: "Converged hook/install repair confirmed the selected activation root and hook wiring without additional runtime-affecting changes",
|
|
4003
|
+
hookLayout: attachResult.brainFeedback.hookLayout,
|
|
4004
|
+
hookPath: attachResult.brainFeedback.hookPath
|
|
4005
|
+
},
|
|
4006
|
+
restart: {
|
|
4007
|
+
required: restartPlan.required,
|
|
4008
|
+
automatic: restartPlan.automatic,
|
|
4009
|
+
performed: restartPlan.required && restartPlan.automatic && restartError === null,
|
|
4010
|
+
command: restartCapture?.shellCommand ?? null,
|
|
4011
|
+
detail: restartPlan.required
|
|
4012
|
+
? restartPlan.automatic
|
|
4013
|
+
? restartError === null
|
|
4014
|
+
? `Ran automatic gateway restart: ${restartCapture.shellCommand}`
|
|
4015
|
+
: restartError
|
|
4016
|
+
: `${restartPlan.detail} Restart it manually, then rerun status.`
|
|
4017
|
+
: restartPlan.detail,
|
|
4018
|
+
error: restartError
|
|
4019
|
+
},
|
|
4020
|
+
verification: {
|
|
4021
|
+
summaryLine: verificationSnapshot.summaryLine,
|
|
4022
|
+
nextStep: verificationSnapshot.nextStep,
|
|
4023
|
+
displayedStatus: verificationSnapshot.displayedStatus,
|
|
4024
|
+
installState: verificationSnapshot.facts.installState,
|
|
4025
|
+
installLayout: verificationSnapshot.facts.installLayout,
|
|
4026
|
+
loadability: verificationSnapshot.facts.loadability,
|
|
4027
|
+
runtimeLoad: verificationSnapshot.facts.runtimeLoad,
|
|
4028
|
+
loadProof: verificationSnapshot.facts.loadProof,
|
|
4029
|
+
serveState: verificationSnapshot.facts.serveState,
|
|
4030
|
+
routeFnAvailable: verificationSnapshot.facts.routeFnAvailable,
|
|
4031
|
+
awaitingFirstExport: verificationSnapshot.facts.awaitingFirstExport
|
|
4032
|
+
},
|
|
4033
|
+
verdict,
|
|
4034
|
+
underlyingInstall: attachResult
|
|
4035
|
+
};
|
|
4036
|
+
emitInstallConvergeResult(result, parsed);
|
|
4037
|
+
return verdict.verdict === "failed" || verdict.verdict === "manual_action_required" ? 1 : 0;
|
|
3561
4038
|
}
|
|
3562
4039
|
function runAttachCommand(parsed) {
|
|
3563
4040
|
return runProfileHookAttachCommand(parsed);
|
|
@@ -4023,7 +4500,7 @@ function persistWatchTracedLearningBridgeSurface(input) {
|
|
|
4023
4500
|
}
|
|
4024
4501
|
}));
|
|
4025
4502
|
}
|
|
4026
|
-
function runLearnCommand(parsed) {
|
|
4503
|
+
async function runLearnCommand(parsed) {
|
|
4027
4504
|
const learnStatePath = path.join(parsed.activationRoot, "learn-cli-state.json");
|
|
4028
4505
|
const teacherSnapshotPath = resolveAsyncTeacherLiveLoopSnapshotPath(parsed.activationRoot);
|
|
4029
4506
|
function isLearnRuntimeStateLike(value) {
|
|
@@ -4236,6 +4713,7 @@ function runLearnCommand(parsed) {
|
|
|
4236
4713
|
return 0;
|
|
4237
4714
|
}
|
|
4238
4715
|
const learningExport = normalizedEventExport;
|
|
4716
|
+
const resolvedEmbedder = resolveCliEmbedderConfig(undefined, activationRoot);
|
|
4239
4717
|
const serveTimeLearning = resolveServeTimeLearningRuntimeInput(activationRoot);
|
|
4240
4718
|
const learnerResult = drainAlwaysOnLearningRuntime({
|
|
4241
4719
|
packLabel: "learn-cli",
|
|
@@ -4258,7 +4736,11 @@ function runLearnCommand(parsed) {
|
|
|
4258
4736
|
...(serveTimeLearning.baselineState !== undefined ? { baselineState: serveTimeLearning.baselineState } : {}),
|
|
4259
4737
|
activationRoot
|
|
4260
4738
|
});
|
|
4261
|
-
|
|
4739
|
+
let lastMaterialization = learnerResult.materializations.at(-1) ?? null;
|
|
4740
|
+
lastMaterialization = await reindexMaterializationCandidateWithEmbedder(lastMaterialization, resolvedEmbedder.embedder);
|
|
4741
|
+
if (lastMaterialization !== null && learnerResult.materializations.length > 0) {
|
|
4742
|
+
learnerResult.materializations[learnerResult.materializations.length - 1] = lastMaterialization;
|
|
4743
|
+
}
|
|
4262
4744
|
const plan = describeAlwaysOnLearningRuntimeState(learnerResult.state, lastMaterialization);
|
|
4263
4745
|
const learningPath = summarizeLearningPathFromMaterialization(lastMaterialization);
|
|
4264
4746
|
const graphEvolution = lastMaterialization?.candidate.payloads.graph.evolution;
|
|
@@ -4651,14 +5133,9 @@ async function applyWatchMaterialization(activationRoot, snapshot, lastHandledMa
|
|
|
4651
5133
|
failure: null
|
|
4652
5134
|
};
|
|
4653
5135
|
}
|
|
4654
|
-
|
|
4655
|
-
|
|
4656
|
-
|
|
4657
|
-
candidate: await reindexCandidatePackBuildResultWithEmbedder(materialization.candidate, embedder)
|
|
4658
|
-
};
|
|
4659
|
-
if (snapshot?.learner !== undefined && snapshot.learner !== null) {
|
|
4660
|
-
snapshot.learner.lastMaterialization = materialization;
|
|
4661
|
-
}
|
|
5136
|
+
materialization = await reindexMaterializationCandidateWithEmbedder(materialization, embedder);
|
|
5137
|
+
if (snapshot?.learner !== undefined && snapshot.learner !== null) {
|
|
5138
|
+
snapshot.learner.lastMaterialization = materialization;
|
|
4662
5139
|
}
|
|
4663
5140
|
const shortPackId = packId.length > 16 ? packId.slice(0, 16) : packId;
|
|
4664
5141
|
const observedAt = new Date().toISOString();
|
|
@@ -4839,7 +5316,7 @@ function resolveWatchTeacherLabelerConfig(input, activationRoot) {
|
|
|
4839
5316
|
warnings
|
|
4840
5317
|
};
|
|
4841
5318
|
}
|
|
4842
|
-
function
|
|
5319
|
+
function resolveCliEmbedderConfig(input, activationRoot) {
|
|
4843
5320
|
if (input !== undefined) {
|
|
4844
5321
|
return {
|
|
4845
5322
|
embedder: input,
|
|
@@ -5023,7 +5500,7 @@ export async function createWatchCommandRuntime(input) {
|
|
|
5023
5500
|
log(`Scan root: ${shortenPath(scanRoot)}`);
|
|
5024
5501
|
log(`State: cursor=${shortenPath(sessionTailCursorPath)} snapshot=${shortenPath(teacherSnapshotPath)}`);
|
|
5025
5502
|
const resolvedTeacherLabeler = resolveWatchTeacherLabelerConfig(input.teacherLabeler, activationRoot);
|
|
5026
|
-
const resolvedEmbedder =
|
|
5503
|
+
const resolvedEmbedder = resolveCliEmbedderConfig(input.embedder, activationRoot);
|
|
5027
5504
|
const teacherLabeler = resolvedTeacherLabeler.teacherLabeler;
|
|
5028
5505
|
for (const warning of resolvedTeacherLabeler.warnings) {
|
|
5029
5506
|
startupWarnings.push(`teacher_config_warning:${warning}`);
|
|
@@ -5644,7 +6121,12 @@ export function runOperatorCli(argv = process.argv.slice(2)) {
|
|
|
5644
6121
|
return runHistoryCommand(parsed);
|
|
5645
6122
|
}
|
|
5646
6123
|
if (parsed.command === "learn") {
|
|
5647
|
-
|
|
6124
|
+
runLearnCommand(parsed).then((code) => { process.exitCode = code; }, (error) => {
|
|
6125
|
+
console.error("[openclawbrain] learn failed");
|
|
6126
|
+
console.error(error instanceof Error ? error.stack ?? error.message : String(error));
|
|
6127
|
+
process.exitCode = 1;
|
|
6128
|
+
});
|
|
6129
|
+
return 0;
|
|
5648
6130
|
}
|
|
5649
6131
|
if (parsed.command === "watch") {
|
|
5650
6132
|
// Watch is async — bridge to sync CLI entry by scheduling and returning 0.
|