@openclawbrain/cli 0.4.12 → 0.4.14

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
@@ -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";
@@ -15,6 +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
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";
@@ -516,7 +517,7 @@ function operatorCliHelp() {
516
517
  " --help Show this help.",
517
518
  "",
518
519
  "Lifecycle flow:",
519
- " 1. install openclawbrain install — safe first-time default; writes the generated shadow hook or pins an already-installed native package plugin for one OpenClaw home",
520
+ " 1. install openclawbrain install — converge one OpenClaw home: plugin-manager install/update/repair, hook repair, conditional restart, then status verification",
520
521
  " 2. attach openclawbrain attach --openclaw-home <path> [--activation-root <path>] — explicit reattach/manual hook path for known brain data; use install first",
521
522
  " 3. status openclawbrain status --activation-root <path> — answer \"How's the brain?\" for that boundary",
522
523
  " 4. status --detailed openclawbrain status --activation-root <path> --detailed — explain serve path, freshness, backlog, and failure mode",
@@ -1452,6 +1453,182 @@ function buildGatewayRestartCommand(profileId) {
1452
1453
  function buildGatewayStatusCommand(profileId) {
1453
1454
  return `env -i HOME="$HOME" PATH="$PATH" openclaw --profile ${quoteShellArg(profileId)} gateway status`;
1454
1455
  }
1456
+ function buildGatewayRestartArgs(profileId) {
1457
+ return profileId === null ? ["gateway", "restart"] : ["gateway", "restart", "--profile", profileId];
1458
+ }
1459
+ function shellJoin(parts) {
1460
+ return parts
1461
+ .map((part) => {
1462
+ if (/^[A-Za-z0-9_./:@=-]+$/.test(part)) {
1463
+ return part;
1464
+ }
1465
+ return JSON.stringify(part);
1466
+ })
1467
+ .join(" ");
1468
+ }
1469
+ function runCapturedExternalCommand(command, args, options = {}) {
1470
+ const result = spawnSync(command, args, {
1471
+ cwd: options.cwd ?? process.cwd(),
1472
+ env: options.env ?? process.env,
1473
+ encoding: "utf8",
1474
+ stdio: "pipe"
1475
+ });
1476
+ return {
1477
+ command,
1478
+ args,
1479
+ shellCommand: shellJoin([command, ...args]),
1480
+ stdout: result.stdout ?? "",
1481
+ stderr: result.stderr ?? "",
1482
+ exitCode: typeof result.status === "number" ? result.status : null,
1483
+ signal: result.signal ?? null,
1484
+ error: result.error ? toErrorMessage(result.error) : null
1485
+ };
1486
+ }
1487
+ function summarizeCapturedCommandFailure(capture) {
1488
+ const parts = [];
1489
+ if (capture.error !== null) {
1490
+ parts.push(capture.error);
1491
+ }
1492
+ if (capture.stderr.trim().length > 0) {
1493
+ parts.push(capture.stderr.trim());
1494
+ }
1495
+ if (capture.stdout.trim().length > 0) {
1496
+ parts.push(capture.stdout.trim());
1497
+ }
1498
+ if (capture.exitCode !== null) {
1499
+ parts.push(`exitCode=${capture.exitCode}`);
1500
+ }
1501
+ return parts.length === 0 ? "no command output was captured" : parts.join(" | ");
1502
+ }
1503
+ function readTextFileIfExists(filePath) {
1504
+ if (filePath === null || !existsSync(filePath)) {
1505
+ return null;
1506
+ }
1507
+ try {
1508
+ return readFileSync(filePath, "utf8");
1509
+ }
1510
+ catch {
1511
+ return null;
1512
+ }
1513
+ }
1514
+ function readInstallRuntimeFingerprint(openclawHome) {
1515
+ const installedPlugin = findInstalledOpenClawBrainPlugin(openclawHome);
1516
+ const selectedInstall = installedPlugin.selectedInstall;
1517
+ const hook = inspectOpenClawBrainHookStatus(openclawHome);
1518
+ const resolvedActivationRoot = resolveActivationRoot({
1519
+ openclawHome,
1520
+ quiet: true
1521
+ });
1522
+ const { config } = readOpenClawJsonConfig(openclawHome);
1523
+ return {
1524
+ selectedInstall: selectedInstall === null
1525
+ ? null
1526
+ : {
1527
+ extensionDir: selectedInstall.extensionDir,
1528
+ manifestId: selectedInstall.manifestId,
1529
+ installId: selectedInstall.installId,
1530
+ packageName: selectedInstall.packageName,
1531
+ installLayout: selectedInstall.installLayout
1532
+ },
1533
+ installLayout: hook.installLayout,
1534
+ hookPath: hook.hookPath,
1535
+ hookState: hook.installState,
1536
+ loadability: hook.loadability,
1537
+ activationRoot: resolvedActivationRoot.trim().length === 0 ? null : path.resolve(resolvedActivationRoot),
1538
+ loaderSource: readTextFileIfExists(selectedInstall?.loaderEntryPath ?? null),
1539
+ runtimeGuardSource: readTextFileIfExists(selectedInstall?.runtimeGuardPath ?? null),
1540
+ pluginsConfig: JSON.stringify(config.plugins ?? null)
1541
+ };
1542
+ }
1543
+ function runOpenClawBrainConvergePluginStep(openclawHome) {
1544
+ const before = readInstallRuntimeFingerprint(openclawHome);
1545
+ const plan = planOpenClawBrainConvergePluginAction(before);
1546
+ const commandArgs = plan.action === "install"
1547
+ ? ["plugins", "install", plan.packageSpec]
1548
+ : ["plugins", "update", plan.pluginId];
1549
+ const capture = runCapturedExternalCommand("openclaw", commandArgs);
1550
+ if (capture.error !== null || capture.exitCode !== 0) {
1551
+ const hasAuthoritativeNativePlugin = before.selectedInstall !== null && before.installLayout === "native_package_plugin";
1552
+ if (plan.action === "update" && hasAuthoritativeNativePlugin) {
1553
+ return {
1554
+ plan,
1555
+ command: capture.shellCommand,
1556
+ changed: false,
1557
+ changeReasons: [],
1558
+ detail: `Skipped plugin-manager refresh because the existing split-package plugin is already authoritative and \
1559
+ \`${capture.shellCommand}\` failed: ${summarizeCapturedCommandFailure(capture)}`,
1560
+ warning: `plugin-manager refresh skipped after \`${capture.shellCommand}\` failed; keeping the existing authoritative split-package plugin for this converge run`,
1561
+ capture,
1562
+ before,
1563
+ after: before
1564
+ };
1565
+ }
1566
+ throw new Error(`OpenClaw plugin-manager ${plan.action} failed for ${path.resolve(openclawHome)}. Tried \`${capture.shellCommand}\`. Detail: ${summarizeCapturedCommandFailure(capture)}`);
1567
+ }
1568
+ const after = readInstallRuntimeFingerprint(openclawHome);
1569
+ const diff = diffOpenClawBrainConvergeRuntimeFingerprint(before, after);
1570
+ return {
1571
+ plan,
1572
+ command: capture.shellCommand,
1573
+ changed: diff.changed,
1574
+ changeReasons: diff.reasons,
1575
+ detail: diff.changed
1576
+ ? `${plan.action === "install" ? "Installed" : "Refreshed"} plugin-manager state: ${describeOpenClawBrainConvergeChangeReasons(diff.reasons)}`
1577
+ : `${plan.action === "install" ? "Ran install" : "Ran update"} through the OpenClaw plugin manager, but no runtime-affecting plugin delta was detected`,
1578
+ warning: null,
1579
+ capture,
1580
+ before,
1581
+ after
1582
+ };
1583
+ }
1584
+ function inspectInstallConvergeVerification(parsed) {
1585
+ const targetInspection = inspectOpenClawHome(parsed.openclawHome);
1586
+ const operatorInput = {
1587
+ activationRoot: parsed.activationRoot,
1588
+ eventExportPath: null,
1589
+ teacherSnapshotPath: resolveOperatorTeacherSnapshotPath(parsed.activationRoot, null),
1590
+ updatedAt: null,
1591
+ brainAttachmentPolicy: null,
1592
+ openclawHome: parsed.openclawHome,
1593
+ ...(targetInspection.profileId === null ? {} : { profileId: targetInspection.profileId })
1594
+ };
1595
+ const status = describeCurrentProfileBrainStatus(operatorInput);
1596
+ const report = buildOperatorSurfaceReport(operatorInput);
1597
+ const normalizedStatusAndReport = applyAttachmentPolicyTruth(status, report);
1598
+ const installHook = summarizeStatusInstallHook(parsed.openclawHome);
1599
+ const attachmentTruth = summarizeStatusAttachmentTruth({
1600
+ activationRoot: parsed.activationRoot,
1601
+ openclawHome: parsed.openclawHome,
1602
+ status: normalizedStatusAndReport.status
1603
+ });
1604
+ const displayedStatus = summarizeDisplayedStatus(normalizedStatusAndReport.status, installHook);
1605
+ const routeFn = summarizeStatusRouteFn(normalizedStatusAndReport.status, normalizedStatusAndReport.report);
1606
+ return {
1607
+ targetInspection,
1608
+ status: normalizedStatusAndReport.status,
1609
+ report: normalizedStatusAndReport.report,
1610
+ installHook,
1611
+ attachmentTruth,
1612
+ displayedStatus,
1613
+ routeFn,
1614
+ nextStep: buildStatusNextStep(normalizedStatusAndReport.status, normalizedStatusAndReport.report, {
1615
+ openclawHome: parsed.openclawHome,
1616
+ installHook
1617
+ }),
1618
+ summaryLine: `STATUS ${displayedStatus}; hook=${installHook.state}/${installHook.loadability}; runtime=${attachmentTruth.runtimeLoad}; loadProof=${normalizedStatusAndReport.status.hook.loadProof}; serve=${normalizedStatusAndReport.status.brainStatus.serveState}`,
1619
+ facts: {
1620
+ installLayout: normalizedStatusAndReport.status.hook.installLayout ?? installHook.installLayout ?? null,
1621
+ installState: installHook.state,
1622
+ loadability: installHook.loadability,
1623
+ displayedStatus,
1624
+ runtimeLoad: attachmentTruth.runtimeLoad,
1625
+ loadProof: normalizedStatusAndReport.status.hook.loadProof,
1626
+ serveState: normalizedStatusAndReport.status.brainStatus.serveState,
1627
+ routeFnAvailable: routeFn.available,
1628
+ awaitingFirstExport: normalizedStatusAndReport.status.brainStatus.awaitingFirstExport
1629
+ }
1630
+ };
1631
+ }
1455
1632
  function buildInstallCommand(openclawHome) {
1456
1633
  return `openclawbrain install --openclaw-home ${quoteShellArg(openclawHome)}`;
1457
1634
  }
@@ -3238,7 +3415,7 @@ function runHistoryCommand(parsed) {
3238
3415
  }
3239
3416
  return 0;
3240
3417
  }
3241
- function runProfileHookAttachCommand(parsed) {
3418
+ function executeProfileHookAttachCommand(parsed) {
3242
3419
  const steps = [];
3243
3420
  const commandLabel = parsed.command.toUpperCase();
3244
3421
  const isInstall = parsed.command === "install";
@@ -3458,106 +3635,273 @@ function runProfileHookAttachCommand(parsed) {
3458
3635
  ? `Install: kept healthy active pack ${activationPlan.activePackId} in place`
3459
3636
  : `Attach: rewired the profile hook to healthy active pack ${activationPlan.activePackId}`
3460
3637
  ];
3461
- // 9. Print summary
3462
- if (parsed.json) {
3463
- console.log(JSON.stringify({
3464
- command: parsed.command,
3465
- openclawHome: parsed.openclawHome,
3466
- openclawHomeSource: parsed.openclawHomeSource,
3467
- openclawTarget: {
3468
- layout: targetInspection.layout,
3469
- detail: describeOpenClawHomeInspection(targetInspection),
3470
- profileId: targetInspection.profileId,
3471
- profileSource: targetInspection.profileSource,
3472
- configuredProfileIds: targetInspection.configuredProfileIds
3638
+ return {
3639
+ command: parsed.command,
3640
+ commandLabel,
3641
+ openclawHome: parsed.openclawHome,
3642
+ openclawHomeSource: parsed.openclawHomeSource,
3643
+ openclawTarget: {
3644
+ layout: targetInspection.layout,
3645
+ detail: describeOpenClawHomeInspection(targetInspection),
3646
+ profileId: targetInspection.profileId,
3647
+ profileSource: targetInspection.profileSource,
3648
+ configuredProfileIds: targetInspection.configuredProfileIds
3649
+ },
3650
+ activationRoot: parsed.activationRoot,
3651
+ resolvedInputs: {
3652
+ activationRoot: {
3653
+ value: parsed.activationRoot,
3654
+ source: parsed.activationRootSource
3473
3655
  },
3474
- activationRoot: parsed.activationRoot,
3475
- resolvedInputs: {
3476
- activationRoot: {
3477
- value: parsed.activationRoot,
3478
- source: parsed.activationRootSource
3479
- },
3480
- workspaceId: {
3481
- value: parsed.workspaceId,
3482
- source: parsed.workspaceIdSource
3483
- }
3656
+ workspaceId: {
3657
+ value: parsed.workspaceId,
3658
+ source: parsed.workspaceIdSource
3659
+ }
3660
+ },
3661
+ workspaceId: parsed.workspaceId,
3662
+ shared: parsed.shared,
3663
+ embedderProvision: embedderProvision === null
3664
+ ? null
3665
+ : {
3666
+ skipped: parsed.skipEmbedderProvision,
3667
+ source: parsed.skipEmbedderProvisionSource,
3668
+ model: embedderProvision.model,
3669
+ baseUrl: embedderProvision.baseUrl
3484
3670
  },
3485
- workspaceId: parsed.workspaceId,
3486
- shared: parsed.shared,
3487
- embedderProvision: embedderProvision === null
3488
- ? null
3489
- : {
3490
- skipped: parsed.skipEmbedderProvision,
3491
- source: parsed.skipEmbedderProvisionSource,
3492
- model: embedderProvision.model,
3493
- baseUrl: embedderProvision.baseUrl
3494
- },
3495
- providerDefaults: providerDefaults === null
3496
- ? null
3497
- : {
3498
- path: providerDefaults.path,
3499
- teacher: providerDefaults.defaults.teacher === undefined
3500
- ? null
3501
- : {
3502
- provider: providerDefaults.defaults.teacher.provider ?? null,
3503
- model: providerDefaults.defaults.teacher.model ?? null,
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
3671
+ providerDefaults: providerDefaults === null
3672
+ ? null
3673
+ : {
3674
+ path: providerDefaults.path,
3675
+ teacher: providerDefaults.defaults.teacher === undefined
3676
+ ? null
3677
+ : {
3678
+ provider: providerDefaults.defaults.teacher.provider ?? null,
3679
+ model: providerDefaults.defaults.teacher.model ?? null,
3680
+ detectedLocally: providerDefaults.defaults.teacher.detectedLocally ?? false
3681
+ },
3682
+ embedder: providerDefaults.defaults.embedder === undefined
3683
+ ? null
3684
+ : {
3685
+ provider: providerDefaults.defaults.embedder.provider ?? null,
3686
+ model: providerDefaults.defaults.embedder.model ?? null
3687
+ },
3688
+ teacherBaseUrl: providerDefaults.defaults.teacherBaseUrl ?? null,
3689
+ embedderBaseUrl: providerDefaults.defaults.embedderBaseUrl ?? null
3531
3690
  },
3532
- extensionDir,
3533
- lifecycleSummary,
3534
- preflightSummary,
3535
- restartGuidance,
3536
- nextSteps,
3537
- steps
3538
- }, null, 2));
3691
+ pluginConfigRepair,
3692
+ learnerService,
3693
+ brainFeedback: {
3694
+ hookPath: brainFeedback.hookPath,
3695
+ hookLayout: brainFeedback.hookLayout,
3696
+ providerDefaultsPath: brainFeedback.providerDefaultsPath,
3697
+ profile: brainFeedback.profile,
3698
+ attachment: brainFeedback.attachment,
3699
+ restart: brainFeedback.restart,
3700
+ embedder: brainFeedback.embedder,
3701
+ teacher: brainFeedback.teacher,
3702
+ learnerService: brainFeedback.learnerService,
3703
+ startup: brainFeedback.startup,
3704
+ provedNow: brainFeedback.provedNow,
3705
+ notYetProved: brainFeedback.notYetProved,
3706
+ lines: brainFeedback.lines
3707
+ },
3708
+ extensionDir,
3709
+ lifecycleSummary,
3710
+ preflightSummary,
3711
+ restartGuidance,
3712
+ nextSteps,
3713
+ steps
3714
+ };
3715
+ }
3716
+ function emitProfileHookAttachCommandResult(result, parsed) {
3717
+ if (parsed.json) {
3718
+ console.log(JSON.stringify(result, null, 2));
3719
+ return;
3539
3720
  }
3540
- else {
3541
- console.log(`${commandLabel} complete\n`);
3542
- console.log("Brain feedback:");
3543
- for (const line of brainFeedback.lines) {
3544
- console.log(` ${line}`);
3545
- }
3546
- console.log(`Restart: ${restartGuidance}`);
3547
- if (brainFeedback.restart.gatewayStatusCommand !== null) {
3548
- console.log(`Gateway: Confirm OpenClaw after restart: ${brainFeedback.restart.gatewayStatusCommand}`);
3549
- }
3550
- console.log(`Check: ${buildInstallStatusCommand(parsed.activationRoot)}`);
3551
- console.log(`Proof: ${buildProofCommandForOpenClawHome(parsed.openclawHome)}`);
3552
- console.log(`Learner: ${buildLearnerServiceStatusCommand(parsed.activationRoot)}`);
3553
- if (embedderProvision !== null && embedderProvision.state === "skipped") {
3554
- console.log(`Embedder: ${buildInstallEmbedderProvisionCommand(embedderProvision.baseUrl, embedderProvision.model)}`);
3555
- }
3721
+ console.log(`${result.commandLabel} complete\n`);
3722
+ console.log("Brain feedback:");
3723
+ for (const line of result.brainFeedback.lines) {
3724
+ console.log(` ${line}`);
3725
+ }
3726
+ console.log(`Restart: ${result.restartGuidance}`);
3727
+ if (result.brainFeedback.restart.gatewayStatusCommand !== null) {
3728
+ console.log(`Gateway: Confirm OpenClaw after restart: ${result.brainFeedback.restart.gatewayStatusCommand}`);
3556
3729
  }
3730
+ console.log(`Check: ${buildInstallStatusCommand(result.activationRoot)}`);
3731
+ console.log(`Proof: ${buildProofCommandForOpenClawHome(result.openclawHome)}`);
3732
+ console.log(`Learner: ${buildLearnerServiceStatusCommand(result.activationRoot)}`);
3733
+ if (result.embedderProvision !== null && result.embedderProvision.skipped) {
3734
+ console.log(`Embedder: ${buildInstallEmbedderProvisionCommand(result.embedderProvision.baseUrl, result.embedderProvision.model)}`);
3735
+ }
3736
+ }
3737
+ function runProfileHookAttachCommand(parsed) {
3738
+ const result = executeProfileHookAttachCommand(parsed);
3739
+ emitProfileHookAttachCommandResult(result, parsed);
3557
3740
  return 0;
3558
3741
  }
3742
+ function emitInstallConvergeResult(result, parsed) {
3743
+ if (parsed.json) {
3744
+ console.log(JSON.stringify(result, null, 2));
3745
+ return;
3746
+ }
3747
+ const heading = result.verdict.verdict === "failed"
3748
+ ? "INSTALL converge failed"
3749
+ : "INSTALL converge complete";
3750
+ console.log(`${heading}\n`);
3751
+ console.log(`Plugin: ${result.plugin.detail}`);
3752
+ console.log(`Attach: ${result.attach.detail}`);
3753
+ console.log(`Restart: ${result.restart.detail}`);
3754
+ console.log(`Verify: ${result.verification.summaryLine}`);
3755
+ console.log(`Verdict: ${result.verdict.verdict}`);
3756
+ console.log(`Why: ${result.verdict.why}`);
3757
+ if (result.verdict.warnings.length > 0) {
3758
+ console.log(`Warnings: ${result.verdict.warnings.join("; ")}`);
3759
+ }
3760
+ console.log(`Next: ${result.verification.nextStep}`);
3761
+ console.log(`Proof: ${buildProofCommandForOpenClawHome(result.openclawHome)}`);
3762
+ console.log(`Learner: ${buildLearnerServiceStatusCommand(result.activationRoot)}`);
3763
+ }
3559
3764
  function runInstallCommand(parsed) {
3560
- return runProfileHookAttachCommand(parsed);
3765
+ let pluginResult = null;
3766
+ let attachResult = null;
3767
+ try {
3768
+ validateOpenClawHome(parsed.openclawHome);
3769
+ pluginResult = runOpenClawBrainConvergePluginStep(parsed.openclawHome);
3770
+ attachResult = executeProfileHookAttachCommand(parsed);
3771
+ }
3772
+ catch (error) {
3773
+ const verdict = finalizeOpenClawBrainConvergeResult({
3774
+ stepFailure: toErrorMessage(error),
3775
+ verification: null,
3776
+ warnings: []
3777
+ });
3778
+ const failureResult = {
3779
+ command: "install",
3780
+ openclawHome: parsed.openclawHome,
3781
+ activationRoot: parsed.activationRoot,
3782
+ plugin: pluginResult ?? {
3783
+ action: null,
3784
+ command: null,
3785
+ changed: false,
3786
+ changeReasons: [],
3787
+ detail: "plugin-manager converge did not complete"
3788
+ },
3789
+ attach: attachResult ?? {
3790
+ changed: false,
3791
+ changeReasons: [],
3792
+ detail: "under-the-hood install/attach repair did not complete",
3793
+ hookLayout: null,
3794
+ hookPath: null
3795
+ },
3796
+ restart: {
3797
+ required: false,
3798
+ automatic: false,
3799
+ performed: false,
3800
+ command: null,
3801
+ detail: "restart did not run because install failed earlier",
3802
+ error: null
3803
+ },
3804
+ verification: {
3805
+ summaryLine: "verification did not run because install failed earlier",
3806
+ nextStep: "Fix the install failure and rerun openclawbrain install.",
3807
+ displayedStatus: "unknown",
3808
+ installState: "unknown",
3809
+ loadability: "unknown",
3810
+ runtimeLoad: "unknown",
3811
+ loadProof: "unknown",
3812
+ serveState: "unknown",
3813
+ routeFnAvailable: false,
3814
+ awaitingFirstExport: false
3815
+ },
3816
+ verdict
3817
+ };
3818
+ emitInstallConvergeResult(failureResult, parsed);
3819
+ return 1;
3820
+ }
3821
+ const attachDiff = diffOpenClawBrainConvergeRuntimeFingerprint(pluginResult.after, readInstallRuntimeFingerprint(parsed.openclawHome));
3822
+ const changeReasons = [...new Set([...pluginResult.changeReasons, ...attachDiff.reasons])];
3823
+ const restartPlan = buildOpenClawBrainConvergeRestartPlan({
3824
+ profileName: attachResult.brainFeedback.profile.exactProfileName,
3825
+ changeReasons
3826
+ });
3827
+ let restartCapture = null;
3828
+ let restartError = null;
3829
+ if (restartPlan.required && restartPlan.automatic) {
3830
+ restartCapture = runCapturedExternalCommand("openclaw", buildGatewayRestartArgs(attachResult.brainFeedback.profile.exactProfileName));
3831
+ if (restartCapture.error !== null || restartCapture.exitCode !== 0) {
3832
+ restartError = `Automatic restart failed after converge. Tried \`${restartCapture.shellCommand}\`. Detail: ${summarizeCapturedCommandFailure(restartCapture)}`;
3833
+ }
3834
+ }
3835
+ const verificationSnapshot = inspectInstallConvergeVerification(parsed);
3836
+ const verification = classifyOpenClawBrainConvergeVerification({
3837
+ ...verificationSnapshot.facts,
3838
+ restartRequired: restartPlan.required,
3839
+ restartPerformed: restartPlan.required && restartPlan.automatic && restartError === null
3840
+ });
3841
+ const convergeWarnings = [];
3842
+ if (pluginResult.warning) {
3843
+ convergeWarnings.push(pluginResult.warning);
3844
+ }
3845
+ if (restartError !== null) {
3846
+ convergeWarnings.push(restartError);
3847
+ }
3848
+ const verdict = finalizeOpenClawBrainConvergeResult({
3849
+ stepFailure: null,
3850
+ verification,
3851
+ warnings: convergeWarnings
3852
+ });
3853
+ const result = {
3854
+ command: "install",
3855
+ openclawHome: parsed.openclawHome,
3856
+ activationRoot: parsed.activationRoot,
3857
+ plugin: {
3858
+ action: pluginResult.plan.action,
3859
+ command: pluginResult.command,
3860
+ changed: pluginResult.changed,
3861
+ changeReasons: pluginResult.changeReasons,
3862
+ detail: pluginResult.detail
3863
+ },
3864
+ attach: {
3865
+ changed: attachDiff.changed,
3866
+ changeReasons: attachDiff.reasons,
3867
+ detail: attachDiff.changed
3868
+ ? `Converged hook/install repair: ${describeOpenClawBrainConvergeChangeReasons(attachDiff.reasons)}`
3869
+ : "Converged hook/install repair confirmed the selected activation root and hook wiring without additional runtime-affecting changes",
3870
+ hookLayout: attachResult.brainFeedback.hookLayout,
3871
+ hookPath: attachResult.brainFeedback.hookPath
3872
+ },
3873
+ restart: {
3874
+ required: restartPlan.required,
3875
+ automatic: restartPlan.automatic,
3876
+ performed: restartPlan.required && restartPlan.automatic && restartError === null,
3877
+ command: restartCapture?.shellCommand ?? null,
3878
+ detail: restartPlan.required
3879
+ ? restartPlan.automatic
3880
+ ? restartError === null
3881
+ ? `Ran automatic gateway restart: ${restartCapture.shellCommand}`
3882
+ : restartError
3883
+ : `${restartPlan.detail} Restart it manually, then rerun status.`
3884
+ : restartPlan.detail,
3885
+ error: restartError
3886
+ },
3887
+ verification: {
3888
+ summaryLine: verificationSnapshot.summaryLine,
3889
+ nextStep: verificationSnapshot.nextStep,
3890
+ displayedStatus: verificationSnapshot.displayedStatus,
3891
+ installState: verificationSnapshot.facts.installState,
3892
+ installLayout: verificationSnapshot.facts.installLayout,
3893
+ loadability: verificationSnapshot.facts.loadability,
3894
+ runtimeLoad: verificationSnapshot.facts.runtimeLoad,
3895
+ loadProof: verificationSnapshot.facts.loadProof,
3896
+ serveState: verificationSnapshot.facts.serveState,
3897
+ routeFnAvailable: verificationSnapshot.facts.routeFnAvailable,
3898
+ awaitingFirstExport: verificationSnapshot.facts.awaitingFirstExport
3899
+ },
3900
+ verdict,
3901
+ underlyingInstall: attachResult
3902
+ };
3903
+ emitInstallConvergeResult(result, parsed);
3904
+ return verdict.verdict === "failed" || verdict.verdict === "manual_action_required" ? 1 : 0;
3561
3905
  }
3562
3906
  function runAttachCommand(parsed) {
3563
3907
  return runProfileHookAttachCommand(parsed);