@agentlayer.tech/wallet 0.1.73 → 0.1.75

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.
@@ -380,6 +380,123 @@ function releaseRootFor(version, env = process.env) {
380
380
  return path.join(resolveRuntimeBase(env), "releases", version);
381
381
  }
382
382
 
383
+ function stagingRootFor(version, env = process.env) {
384
+ return path.join(
385
+ resolveRuntimeBase(env),
386
+ "releases",
387
+ `.staging-${version}-${process.pid}-${Date.now()}`,
388
+ );
389
+ }
390
+
391
+ function updateJournalPath(env = process.env) {
392
+ return path.join(resolveRuntimeBase(env), "update-journal.json");
393
+ }
394
+
395
+ function integrationRegistryPath(env = process.env) {
396
+ return path.join(resolveRuntimeBase(env), "integrations.json");
397
+ }
398
+
399
+ function readIntegrationRegistry(env = process.env) {
400
+ const payload = readJsonFile(integrationRegistryPath(env));
401
+ if (!payload || payload.schema_version !== 1 || typeof payload.integrations !== "object") {
402
+ return { schema_version: 1, integrations: {} };
403
+ }
404
+ return payload;
405
+ }
406
+
407
+ function recordManagedIntegration(name, details = {}, env = process.env) {
408
+ const registry = readIntegrationRegistry(env);
409
+ registry.integrations[name] = {
410
+ ...details,
411
+ managed: true,
412
+ installed_version: packageVersion,
413
+ updated_at: new Date().toISOString(),
414
+ };
415
+ registry.updated_at = new Date().toISOString();
416
+ writeJsonFileAtomic(integrationRegistryPath(env), registry);
417
+ return registry.integrations[name];
418
+ }
419
+
420
+ function managedIntegration(name, env = process.env) {
421
+ const entry = readIntegrationRegistry(env).integrations[name];
422
+ return entry && entry.managed === true ? entry : null;
423
+ }
424
+
425
+ function integrationSyncStatus(env = process.env) {
426
+ const active = activeVersion(env);
427
+ const registry = readIntegrationRegistry(env);
428
+ const integrations = Object.entries(registry.integrations)
429
+ .filter(([, entry]) => entry?.managed === true)
430
+ .sort(([left], [right]) => left.localeCompare(right))
431
+ .map(([name, entry]) => {
432
+ const versionInSync = active === null || entry.installed_version === active;
433
+ const registrationOk = entry.registration_ok !== false;
434
+ return {
435
+ name,
436
+ installed_version: entry.installed_version || null,
437
+ active_version: active,
438
+ in_sync: versionInSync && registrationOk,
439
+ registration_ok: registrationOk,
440
+ restart_required: Boolean(entry.restart_required),
441
+ };
442
+ });
443
+ return {
444
+ in_sync: integrations.every((entry) => entry.in_sync),
445
+ integrations,
446
+ };
447
+ }
448
+
449
+ function writeUpdateJournal(state, details = {}, env = process.env) {
450
+ writeJsonFile(updateJournalPath(env), {
451
+ schema_version: 1,
452
+ state,
453
+ version: packageVersion,
454
+ updated_at: new Date().toISOString(),
455
+ ...details,
456
+ });
457
+ }
458
+
459
+ function writeReleaseState(runtimeRoot, state, details = {}) {
460
+ writeJsonFile(path.join(runtimeRoot, ".agent-wallet-release.json"), {
461
+ schema_version: 1,
462
+ version: packageVersion,
463
+ state,
464
+ updated_at: new Date().toISOString(),
465
+ ...details,
466
+ });
467
+ }
468
+
469
+ function failStagingRuntime(stagingRoot, error) {
470
+ if (!stagingRoot || !fs.existsSync(stagingRoot)) return null;
471
+ writeReleaseState(stagingRoot, "failed", { error: String(error || "install failed") });
472
+ const failedRoot = path.join(
473
+ path.dirname(stagingRoot),
474
+ `.failed-${packageVersion}-${Date.now()}`,
475
+ );
476
+ fs.renameSync(stagingRoot, failedRoot);
477
+ return failedRoot;
478
+ }
479
+
480
+ function commitStagedRuntime(stagingRoot, releaseRoot) {
481
+ if (!stagingRoot) return null;
482
+ let replacedRoot = null;
483
+ if (fs.existsSync(releaseRoot)) {
484
+ replacedRoot = uniquePathWithSuffix(
485
+ path.join(path.dirname(releaseRoot), `${path.basename(releaseRoot)}-replaced`),
486
+ );
487
+ fs.renameSync(releaseRoot, replacedRoot);
488
+ }
489
+ try {
490
+ fs.renameSync(stagingRoot, releaseRoot);
491
+ } catch (error) {
492
+ if (replacedRoot && !fs.existsSync(releaseRoot)) {
493
+ fs.renameSync(replacedRoot, releaseRoot);
494
+ }
495
+ throw error;
496
+ }
497
+ return replacedRoot;
498
+ }
499
+
383
500
  function currentRuntimePath(env = process.env) {
384
501
  return path.join(resolveRuntimeBase(env), "current");
385
502
  }
@@ -507,7 +624,21 @@ function listReleases(env = process.env) {
507
624
  try {
508
625
  return fs
509
626
  .readdirSync(releasesDir, { withFileTypes: true })
510
- .filter((entry) => entry.isDirectory())
627
+ .filter((entry) => entry.isDirectory() && !entry.name.startsWith("."))
628
+ .map((entry) => entry.name)
629
+ .sort();
630
+ } catch (error) {
631
+ if (error?.code === "ENOENT") return [];
632
+ throw error;
633
+ }
634
+ }
635
+
636
+ function listFailedReleases(env = process.env) {
637
+ const releasesDir = path.join(resolveRuntimeBase(env), "releases");
638
+ try {
639
+ return fs
640
+ .readdirSync(releasesDir, { withFileTypes: true })
641
+ .filter((entry) => entry.isDirectory() && entry.name.startsWith(".failed-"))
511
642
  .map((entry) => entry.name)
512
643
  .sort();
513
644
  } catch (error) {
@@ -867,6 +998,22 @@ function writeJsonFile(pathname, value) {
867
998
  fs.writeFileSync(pathname, `${JSON.stringify(value, null, 2)}\n`);
868
999
  }
869
1000
 
1001
+ function writeJsonFileAtomic(pathname, value, mode = 0o600) {
1002
+ fs.mkdirSync(path.dirname(pathname), { recursive: true });
1003
+ const tempPath = `${pathname}.tmp-${process.pid}-${Date.now()}`;
1004
+ try {
1005
+ fs.writeFileSync(tempPath, `${JSON.stringify(value, null, 2)}\n`, { mode });
1006
+ fs.renameSync(tempPath, pathname);
1007
+ fs.chmodSync(pathname, mode);
1008
+ } finally {
1009
+ try {
1010
+ fs.rmSync(tempPath, { force: true });
1011
+ } catch {
1012
+ // ignored
1013
+ }
1014
+ }
1015
+ }
1016
+
870
1017
  function currentBootKey(env = process.env) {
871
1018
  const currentRoot = resolvedCurrentRuntimeRoot(env);
872
1019
  if (!currentRoot) return "";
@@ -944,6 +1091,44 @@ function readBootKeyFromKeystore(env = process.env) {
944
1091
  }
945
1092
  }
946
1093
 
1094
+ // New runtimes own boot-key precedence and verify candidates against
1095
+ // sealed_keys.json. An import failure means the active runtime predates this
1096
+ // bridge, so callers may use the legacy JS fallback below.
1097
+ function readBootKeyFromRuntimeResolver(env = process.env) {
1098
+ const runtimeRoot = resolvedCurrentRuntimeRoot(env);
1099
+ if (!runtimeRoot) return { supported: false, key: "" };
1100
+ const py = resolveVenvPython(runtimeRoot);
1101
+ if (!py) return { supported: false, key: "" };
1102
+ try {
1103
+ const res = spawnSync(
1104
+ py,
1105
+ ["-c", "from agent_wallet.config import resolve_boot_key_for_installer as r; print(r())"],
1106
+ {
1107
+ cwd: path.join(runtimeRoot, "agent-wallet"),
1108
+ encoding: "utf8",
1109
+ timeout: positiveIntEnv("AGENT_WALLET_KEYSTORE_BRIDGE_TIMEOUT_MS", KEYSTORE_BRIDGE_TIMEOUT_MS, env),
1110
+ env: { ...env, OPENCLAW_HOME: resolveOpenclawHome(env) },
1111
+ },
1112
+ );
1113
+ if ((res.status ?? 1) !== 0) return { supported: false, key: "" };
1114
+ return { supported: true, key: String(res.stdout || "").trim() };
1115
+ } catch {
1116
+ return { supported: false, key: "" };
1117
+ }
1118
+ }
1119
+
1120
+ function resolveLegacyInstallerBootKey(env = process.env) {
1121
+ for (const [source, key] of [
1122
+ ["legacy_keystore", readBootKeyFromKeystore(env)],
1123
+ ["current_runtime_env", currentBootKey(env)],
1124
+ ["configured_file", resolveBootKeyFromFile(env)],
1125
+ ["default_file", readTextIfExists(defaultBootKeyFile(env)).trim()],
1126
+ ]) {
1127
+ if (key) return { key, source };
1128
+ }
1129
+ return { key: "", source: "none" };
1130
+ }
1131
+
947
1132
  // Provision the boot key into the OS keystore via the freshly installed runtime's
948
1133
  // Python. Returns true only when the import stored AND verified the key, so the
949
1134
  // caller may safely drop the plaintext .env copy. Best-effort: false on any failure
@@ -1186,6 +1371,16 @@ function runDoctor(args = []) {
1186
1371
  fix: rsync.in_sync === false ? "npm run release:local" : "",
1187
1372
  });
1188
1373
 
1374
+ const integrationSync = integrationSyncStatus(env);
1375
+ checks.push({
1376
+ name: "framework_integrations_in_sync",
1377
+ ok: true,
1378
+ in_sync: integrationSync.in_sync,
1379
+ integrations: integrationSync.integrations,
1380
+ error: "",
1381
+ fix: integrationSync.in_sync ? "" : "wallet update --yes",
1382
+ });
1383
+
1189
1384
  const ok = checks.every((c) => c.ok);
1190
1385
  console.log(
1191
1386
  JSON.stringify(
@@ -1218,8 +1413,10 @@ function runStatus(args = []) {
1218
1413
  previous_runtime: readLinkOrNull(previousRuntimePath()),
1219
1414
  active_version: activeVersion(),
1220
1415
  available_releases: listReleases(),
1416
+ failed_releases: listFailedReleases(),
1221
1417
  update_available: computeUpdateAvailability(),
1222
1418
  runtime_in_sync: computeRuntimeInSync(),
1419
+ framework_integrations: integrationSyncStatus(),
1223
1420
  };
1224
1421
  if (hasFlag(args, "--verbose")) {
1225
1422
  payload.verbose = true;
@@ -1236,15 +1433,17 @@ function buildInstallerEnv(args) {
1236
1433
  const sealedKeysPath = path.join(resolveOpenclawHome(env), "sealed_keys.json");
1237
1434
  const sealedKeysExist = fs.existsSync(sealedKeysPath);
1238
1435
  const dryRun = hasFlag(args, "--dry-run");
1436
+ let bootKeySource = env.AGENT_WALLET_BOOT_KEY ? "environment" : "none";
1239
1437
  if (!env.AGENT_WALLET_BOOT_KEY) {
1240
- // Keep installer/update boot-key resolution aligned with the runtime:
1241
- // explicit env first, then keystore, then plaintext fallback paths. A stale
1242
- // boot-key file must not override a good keystore key and break updates.
1243
- const existingBootKey =
1244
- readBootKeyFromKeystore(env) ||
1245
- resolveBootKeyFromFile(env) ||
1246
- readTextIfExists(defaultBootKeyFile(env)).trim() ||
1247
- currentBootKey(env);
1438
+ const runtimeResolution = readBootKeyFromRuntimeResolver(env);
1439
+ const fallback = runtimeResolution.supported
1440
+ ? {
1441
+ key: runtimeResolution.key,
1442
+ source: runtimeResolution.key ? "runtime_verified" : "runtime_rejected",
1443
+ }
1444
+ : resolveLegacyInstallerBootKey(env);
1445
+ const existingBootKey = fallback.key;
1446
+ bootKeySource = fallback.source;
1248
1447
  if (existingBootKey) {
1249
1448
  env.AGENT_WALLET_BOOT_KEY = existingBootKey;
1250
1449
  }
@@ -1264,6 +1463,7 @@ function buildInstallerEnv(args) {
1264
1463
  if (!sealedKeysExist && shouldGenerateSecrets && !env.AGENT_WALLET_BOOT_KEY) {
1265
1464
  generated.AGENT_WALLET_BOOT_KEY = token();
1266
1465
  env.AGENT_WALLET_BOOT_KEY = generated.AGENT_WALLET_BOOT_KEY;
1466
+ bootKeySource = "generated";
1267
1467
  }
1268
1468
  if (!sealedKeysExist && shouldGenerateSecrets && !env.AGENT_WALLET_MASTER_KEY) {
1269
1469
  generated.AGENT_WALLET_MASTER_KEY = token();
@@ -1273,7 +1473,7 @@ function buildInstallerEnv(args) {
1273
1473
  generated.AGENT_WALLET_APPROVAL_SECRET = token();
1274
1474
  env.AGENT_WALLET_APPROVAL_SECRET = generated.AGENT_WALLET_APPROVAL_SECRET;
1275
1475
  }
1276
- return { env, generated };
1476
+ return { env, generated, bootKeySource };
1277
1477
  }
1278
1478
 
1279
1479
  function runInstall(args, { commandName = "install" } = {}) {
@@ -1290,9 +1490,11 @@ function runInstall(args, { commandName = "install" } = {}) {
1290
1490
  const previousPath = previousRuntimePath();
1291
1491
  const installerArgs = withoutCliOnlyArgs(args);
1292
1492
  const dryRun = hasFlag(args, "--dry-run");
1493
+ const stagingRoot = !explicitRuntimeRoot && !dryRun ? stagingRootFor(packageVersion) : null;
1494
+ const installRoot = stagingRoot || releaseRoot;
1293
1495
 
1294
1496
  if (!hasFlag(installerArgs, "--runtime-root")) {
1295
- installerArgs.push("--runtime-root", releaseRoot);
1497
+ installerArgs.push("--runtime-root", installRoot);
1296
1498
  }
1297
1499
  if (!hasFlag(installerArgs, "--install-from-runtime")) {
1298
1500
  installerArgs.push("--install-from-runtime");
@@ -1305,7 +1507,11 @@ function runInstall(args, { commandName = "install" } = {}) {
1305
1507
  console.error(error.message);
1306
1508
  return 1;
1307
1509
  }
1308
- const { env, generated } = installerEnv;
1510
+ const { env, generated, bootKeySource } = installerEnv;
1511
+ if (stagingRoot) {
1512
+ env.OPENCLAW_INSTALL_FINAL_ROOT = releaseRoot;
1513
+ writeUpdateJournal("preparing", { staging_root: stagingRoot, release_root: releaseRoot }, env);
1514
+ }
1309
1515
  const result = spawnSync("sh", [setupPath, ...installerArgs], {
1310
1516
  cwd: packageRoot,
1311
1517
  stdio: "inherit",
@@ -1313,10 +1519,22 @@ function runInstall(args, { commandName = "install" } = {}) {
1313
1519
  });
1314
1520
 
1315
1521
  if (result.error) {
1522
+ const failedRoot = failStagingRuntime(stagingRoot, result.error.message);
1523
+ if (!dryRun) {
1524
+ writeUpdateJournal("failed", { failed_runtime: failedRoot, error: result.error.message }, env);
1525
+ }
1316
1526
  console.error(result.error.message);
1317
1527
  return 1;
1318
1528
  }
1319
1529
  if ((result.status ?? 1) !== 0) {
1530
+ const failedRoot = failStagingRuntime(stagingRoot, `installer exited with ${result.status ?? 1}`);
1531
+ if (!dryRun) {
1532
+ writeUpdateJournal(
1533
+ "failed",
1534
+ { failed_runtime: failedRoot, error: `installer exited with ${result.status ?? 1}` },
1535
+ env,
1536
+ );
1537
+ }
1320
1538
  return result.status ?? 1;
1321
1539
  }
1322
1540
 
@@ -1324,34 +1542,21 @@ function runInstall(args, { commandName = "install" } = {}) {
1324
1542
  return 0;
1325
1543
  }
1326
1544
 
1327
- const currentTarget = existingRuntimePointerTarget(currentPath);
1328
- if (currentTarget) {
1329
- switchSymlink(previousPath, currentTarget);
1330
- }
1331
- switchSymlink(currentPath, releaseRoot);
1332
-
1333
1545
  // Installs that pass --skip-python-setup may have no venv, so this handshake
1334
1546
  // would fail and trigger a spurious rollback; such flows must set
1335
1547
  // AGENT_WALLET_VERIFY_DISABLE=1 (verifyRuntime then skips).
1336
- const verification = verifyRuntime(releaseRoot, env);
1548
+ const currentTarget = existingRuntimePointerTarget(currentPath);
1549
+ const verification = verifyRuntime(installRoot, env);
1337
1550
  if (!verification.ok && !verification.skipped) {
1338
- const rollbackTarget = currentTarget; // pre-switch target captured before the switch, if any
1339
- const rolledBack = Boolean(rollbackTarget);
1340
- if (rolledBack) {
1341
- switchSymlink(currentPath, rollbackTarget);
1342
- }
1551
+ const failedRoot = failStagingRuntime(stagingRoot, verification.error);
1552
+ const rolledBack = Boolean(currentTarget);
1343
1553
  const previousVersion = rolledBack
1344
- ? path.basename(path.resolve(path.dirname(currentPath), rollbackTarget))
1554
+ ? path.basename(currentTarget)
1345
1555
  : null;
1346
1556
 
1347
1557
  let human;
1348
1558
  let fix;
1349
1559
  if (!rolledBack) {
1350
- // First install / no good fallback — leave no active runtime rather than a
1351
- // broken one. Deliberately do NOT point `previous` at the broken release,
1352
- // so a later `rollback` cannot reactivate it; the release stays under
1353
- // releases/<version> for inspection via `install --version`.
1354
- removeRuntimePointer(currentPath);
1355
1560
  human =
1356
1561
  verification.category === "broken_release"
1357
1562
  ? `Release ${packageVersion} is broken and there is no previous working version to fall back to. Nothing is active. This is a bad release — please report it; a patched version will follow.`
@@ -1380,7 +1585,9 @@ function runInstall(args, { commandName = "install" } = {}) {
1380
1585
  category: verification.category || "unknown",
1381
1586
  error: `runtime verification failed: ${verification.error}`,
1382
1587
  rolled_back: rolledBack,
1588
+ switched_current: false,
1383
1589
  kept_version: previousVersion,
1590
+ failed_runtime: failedRoot,
1384
1591
  current_runtime_target: readLinkOrNull(currentPath),
1385
1592
  message: human,
1386
1593
  fix,
@@ -1389,15 +1596,20 @@ function runInstall(args, { commandName = "install" } = {}) {
1389
1596
  2,
1390
1597
  ),
1391
1598
  );
1599
+ writeUpdateJournal(
1600
+ "failed",
1601
+ { failed_runtime: failedRoot, error: verification.error, kept_version: previousVersion },
1602
+ env,
1603
+ );
1392
1604
  return 1;
1393
1605
  }
1394
1606
 
1395
1607
  if (env.AGENT_WALLET_BOOT_KEY) {
1396
- const envPath = path.join(releaseRoot, "agent-wallet", ".env");
1608
+ const envPath = path.join(installRoot, "agent-wallet", ".env");
1397
1609
  // Prefer the OS keystore: provision the boot key there and keep the plaintext
1398
1610
  // out of the release .env entirely. Only fall back to the legacy .env write when
1399
1611
  // no keystore round-trip is possible, so the runtime can still resolve the key.
1400
- if (provisionBootKeyToKeystore(releaseRoot, env, env.AGENT_WALLET_BOOT_KEY)) {
1612
+ if (provisionBootKeyToKeystore(installRoot, env, env.AGENT_WALLET_BOOT_KEY)) {
1401
1613
  envFileUnset(envPath, ["AGENT_WALLET_BOOT_KEY"]);
1402
1614
  } else {
1403
1615
  envFileSet(envPath, {
@@ -1406,7 +1618,45 @@ function runInstall(args, { commandName = "install" } = {}) {
1406
1618
  }
1407
1619
  }
1408
1620
 
1409
- const integrationRefresh = refreshInstalledEditorIntegrations(env);
1621
+ writeReleaseState(installRoot, "verified", {
1622
+ verification_skipped: Boolean(verification.skipped),
1623
+ });
1624
+ writeUpdateJournal("verified", { staging_root: stagingRoot, release_root: releaseRoot }, env);
1625
+
1626
+ let replacedRoot = null;
1627
+ try {
1628
+ replacedRoot = commitStagedRuntime(stagingRoot, releaseRoot);
1629
+ } catch (error) {
1630
+ const failedRoot = failStagingRuntime(stagingRoot, error.message);
1631
+ writeUpdateJournal("failed", { failed_runtime: failedRoot, error: error.message }, env);
1632
+ console.error(`Could not commit staged runtime: ${error.message}`);
1633
+ return 1;
1634
+ }
1635
+
1636
+ const previousTarget =
1637
+ currentTarget && path.resolve(currentTarget) === path.resolve(releaseRoot) && replacedRoot
1638
+ ? replacedRoot
1639
+ : currentTarget;
1640
+ if (previousTarget) {
1641
+ switchSymlink(previousPath, previousTarget);
1642
+ }
1643
+ switchSymlink(currentPath, releaseRoot);
1644
+ writeUpdateJournal("committed", { release_root: releaseRoot, previous_runtime: previousTarget }, env);
1645
+
1646
+ recordManagedIntegration(
1647
+ "openclaw",
1648
+ {
1649
+ config_path: path.resolve(
1650
+ expandHome(parseFlagValue(args, "--config-path") || path.join(resolveOpenclawHome(env), "openclaw.json")),
1651
+ ),
1652
+ extension_path: path.join(currentPath, ".openclaw", "extensions", "agent-wallet"),
1653
+ package_root: path.join(currentPath, "agent-wallet"),
1654
+ },
1655
+ env,
1656
+ );
1657
+
1658
+ const integrationRefresh = repairInstalledEditorIntegrations(env);
1659
+ const globalCliRefresh = refreshGlobalCliIfNeeded(env);
1410
1660
 
1411
1661
  const pythonInfo = activePythonRuntimeInfo(env);
1412
1662
  const nodeInfo = activeNodeRuntimeInfo(env)
@@ -1426,7 +1676,11 @@ function runInstall(args, { commandName = "install" } = {}) {
1426
1676
  current_runtime: currentPath,
1427
1677
  previous_runtime: readLinkOrNull(previousPath),
1428
1678
  generated_runtime_secrets: Object.keys(generated),
1679
+ boot_key_source: bootKeySource,
1680
+ staged: Boolean(stagingRoot),
1681
+ release_state: "verified",
1429
1682
  integration_refresh: integrationRefresh,
1683
+ global_cli_refresh: globalCliRefresh,
1430
1684
  },
1431
1685
  null,
1432
1686
  2,
@@ -1842,6 +2096,14 @@ function runHermesInstall(args) {
1842
2096
  }
1843
2097
  }
1844
2098
 
2099
+ recordManagedIntegration("hermes", {
2100
+ hermes_home: hermesHome,
2101
+ plugin_target: pluginTarget,
2102
+ env_path: hermesEnvPath,
2103
+ restart_required: true,
2104
+ ...(enable.skipped ? {} : { registration_ok: enable.ok }),
2105
+ });
2106
+
1845
2107
  console.log(
1846
2108
  JSON.stringify(
1847
2109
  {
@@ -1867,7 +2129,7 @@ function runHermesInstall(args) {
1867
2129
  function runCodexInstall(args) {
1868
2130
  const codexHome = resolveCodexHome();
1869
2131
  const pluginSource = resolveCodexPluginSource();
1870
- const pinnedEnv = pinEditorMcpEnv(pluginSource);
2132
+ const pinnedEnv = { pinned: false, reason: "plugin source is immutable" };
1871
2133
  const pluginRoot = resolveCodexPluginInstallRoot();
1872
2134
  const pluginTarget = path.join(pluginRoot, "agent-wallet");
1873
2135
  const marketplacePath = resolveCodexMarketplacePath();
@@ -1927,6 +2189,15 @@ function runCodexInstall(args) {
1927
2189
  }
1928
2190
  }
1929
2191
 
2192
+ recordManagedIntegration("codex", {
2193
+ codex_home: codexHome,
2194
+ plugin_target: pluginTarget,
2195
+ marketplace_path: marketplace.marketplace_path,
2196
+ marketplace_name: marketplace.marketplace_name,
2197
+ restart_required: true,
2198
+ ...(add.skipped ? {} : { registration_ok: add.ok }),
2199
+ });
2200
+
1930
2201
  console.log(
1931
2202
  JSON.stringify(
1932
2203
  {
@@ -1989,10 +2260,6 @@ function pinHomeIntoMcpFile(mcpPath, env = process.env) {
1989
2260
  return { pinned: true, openclaw_home: home, path: mcpPath };
1990
2261
  }
1991
2262
 
1992
- function pinEditorMcpEnv(pluginSource, env = process.env) {
1993
- return pinHomeIntoMcpFile(path.join(pluginSource, ".mcp.json"), env);
1994
- }
1995
-
1996
2263
  // Claude Code copies the plugin into a version-keyed cache and reads THAT copy,
1997
2264
  // so the bundle pin alone is ineffective once a cache exists. Pin every cached
1998
2265
  // copy too. Cache root is overridable for tests.
@@ -2065,8 +2332,7 @@ function ensureClaudeCodeMarketplace(marketplaceDir, pluginSource, force) {
2065
2332
 
2066
2333
  function runClaudeCodeInstall(args) {
2067
2334
  const pluginSource = resolveClaudeCodePluginSource();
2068
- const pinnedEnv = pinEditorMcpEnv(pluginSource);
2069
- const pinnedCache = pinClaudeCacheCopies();
2335
+ const pinnedEnv = { pinned: false, reason: "plugin source is immutable" };
2070
2336
  const force = hasFlag(args, "--force");
2071
2337
  const skipEnable = hasFlag(args, "--skip-enable");
2072
2338
  const claudeBin = commandPath("claude");
@@ -2127,7 +2393,18 @@ function runClaudeCodeInstall(args) {
2127
2393
  }
2128
2394
  }
2129
2395
 
2396
+ recordManagedIntegration("claude-code", {
2397
+ marketplace_dir: marketplaceDir,
2398
+ plugin_target: pluginLink,
2399
+ cache_root: path.resolve(
2400
+ expandHome(process.env.AGENT_WALLET_CLAUDE_CODE_CACHE_ROOT || "~/.claude/plugins/cache"),
2401
+ ),
2402
+ restart_required: true,
2403
+ ...(enable.skipped ? {} : { registration_ok: enable.ok }),
2404
+ });
2405
+
2130
2406
  const ok = enable.skipped || enable.ok;
2407
+ const pinnedCache = pinClaudeCacheCopies();
2131
2408
  const pluginDirFlagFull = `claude --plugin-dir ${pluginSource}`;
2132
2409
  console.log(
2133
2410
  JSON.stringify(
@@ -2152,76 +2429,449 @@ function runClaudeCodeInstall(args) {
2152
2429
  return enable.skipped || enable.ok ? 0 : 1;
2153
2430
  }
2154
2431
 
2155
- function hermesInstallPresent(env = process.env) {
2432
+ function runtimeReleasePath(value, env = process.env) {
2433
+ if (!value) return false;
2434
+ try {
2435
+ const releasesRoot = path.join(resolveRuntimeBase(env), "releases");
2436
+ const relative = path.relative(releasesRoot, path.resolve(expandHome(String(value))));
2437
+ return Boolean(relative) && relative !== ".." && !relative.startsWith(`..${path.sep}`);
2438
+ } catch {
2439
+ return false;
2440
+ }
2441
+ }
2442
+
2443
+ function pathEntryExists(pathname) {
2444
+ try {
2445
+ fs.lstatSync(pathname);
2446
+ return true;
2447
+ } catch (error) {
2448
+ if (error?.code === "ENOENT") return false;
2449
+ throw error;
2450
+ }
2451
+ }
2452
+
2453
+ function repairRuntimeSymlink(name, linkPath, desiredTarget, env = process.env, { allowExternal = false } = {}) {
2454
+ let rawTarget;
2455
+ try {
2456
+ const stat = fs.lstatSync(linkPath);
2457
+ if (!stat.isSymbolicLink()) {
2458
+ return { name, attempted: false, ok: true, repaired: false, reason: "not a symlink" };
2459
+ }
2460
+ rawTarget = fs.readlinkSync(linkPath);
2461
+ } catch (error) {
2462
+ if (error?.code === "ENOENT") {
2463
+ return { name, attempted: false, ok: true, repaired: false, reason: "not installed" };
2464
+ }
2465
+ return { name, attempted: true, ok: false, repaired: false, error: error.message };
2466
+ }
2467
+
2468
+ const absoluteTarget = path.resolve(path.dirname(linkPath), rawTarget);
2469
+ const logicalCurrent = currentRuntimePath(env);
2470
+ if (absoluteTarget === path.resolve(desiredTarget) || absoluteTarget.startsWith(`${logicalCurrent}${path.sep}`)) {
2471
+ return { name, attempted: true, ok: true, repaired: false, reason: "already current" };
2472
+ }
2473
+ if (!allowExternal && !runtimeReleasePath(absoluteTarget, env)) {
2474
+ return { name, attempted: false, ok: true, repaired: false, reason: "external target preserved" };
2475
+ }
2476
+ if (!fs.existsSync(desiredTarget)) {
2477
+ return { name, attempted: true, ok: false, repaired: false, error: `missing ${desiredTarget}` };
2478
+ }
2479
+ switchSymlink(linkPath, desiredTarget);
2480
+ return { name, attempted: true, ok: true, repaired: true, target: desiredTarget };
2481
+ }
2482
+
2483
+ function repairHermesEnv(envPath, env = process.env) {
2484
+ const existing = readEnvFile(envPath);
2485
+ const repaired = [];
2486
+ const currentPackage = path.join(currentRuntimePath(env), "agent-wallet");
2487
+ if (runtimeReleasePath(existing.AGENT_WALLET_PACKAGE_ROOT, env)) {
2488
+ envFileSet(envPath, { AGENT_WALLET_PACKAGE_ROOT: currentPackage });
2489
+ repaired.push("AGENT_WALLET_PACKAGE_ROOT");
2490
+ }
2491
+ if (runtimeReleasePath(existing.AGENT_WALLET_PYTHON, env)) {
2492
+ const currentPython = resolveVenvPython(currentRuntimePath(env));
2493
+ if (currentPython) {
2494
+ envFileSet(envPath, { AGENT_WALLET_PYTHON: currentPython });
2495
+ } else {
2496
+ envFileUnset(envPath, ["AGENT_WALLET_PYTHON"]);
2497
+ }
2498
+ repaired.push("AGENT_WALLET_PYTHON");
2499
+ }
2500
+ return repaired;
2501
+ }
2502
+
2503
+ function legacyHermesIntegration(env = process.env) {
2156
2504
  const hermesHome = resolveHermesHome(env);
2157
- return (
2158
- fs.existsSync(path.join(hermesHome, "plugins", "agent_wallet")) ||
2159
- fs.existsSync(path.join(hermesHome, ".env"))
2505
+ const pluginTarget = path.join(hermesHome, "plugins", "agent_wallet");
2506
+ let target;
2507
+ try {
2508
+ if (!fs.lstatSync(pluginTarget).isSymbolicLink()) return null;
2509
+ target = path.resolve(path.dirname(pluginTarget), fs.readlinkSync(pluginTarget));
2510
+ } catch {
2511
+ return null;
2512
+ }
2513
+ const manifest = readTextIfExists(path.join(target, "plugin.yaml"));
2514
+ if (!/^name:\s*agent[-_]wallet\s*$/m.test(manifest)) return null;
2515
+ return recordManagedIntegration(
2516
+ "hermes",
2517
+ {
2518
+ hermes_home: hermesHome,
2519
+ plugin_target: pluginTarget,
2520
+ env_path: path.join(hermesHome, ".env"),
2521
+ adopted_legacy_install: true,
2522
+ },
2523
+ env,
2160
2524
  );
2161
2525
  }
2162
2526
 
2163
- function codexInstallPresent(env = process.env) {
2164
- return (
2165
- fs.existsSync(path.join(resolveCodexPluginInstallRoot(env), "agent-wallet")) ||
2166
- fs.existsSync(resolveCodexMarketplacePath(env))
2527
+ function repairOpenclawIntegration(env = process.env) {
2528
+ const entry = managedIntegration("openclaw", env);
2529
+ if (!entry) {
2530
+ return { name: "openclaw", attempted: false, ok: true, repaired: false, reason: "not managed" };
2531
+ }
2532
+ const configPath = path.resolve(
2533
+ expandHome(entry.config_path || path.join(resolveOpenclawHome(env), "openclaw.json")),
2534
+ );
2535
+ let config;
2536
+ try {
2537
+ config = readJsonFile(configPath);
2538
+ } catch (error) {
2539
+ return { name: "openclaw", attempted: true, ok: false, repaired: false, error: error.message };
2540
+ }
2541
+ if (!config || typeof config !== "object") {
2542
+ return { name: "openclaw", attempted: true, ok: false, repaired: false, error: `missing ${configPath}` };
2543
+ }
2544
+
2545
+ const currentRoot = currentRuntimePath(env);
2546
+ const extensionPath = path.join(currentRoot, ".openclaw", "extensions", "agent-wallet");
2547
+ const packageRootPath = path.join(currentRoot, "agent-wallet");
2548
+ const pythonBin = resolveVenvPython(currentRoot);
2549
+ const plugins = config.plugins && typeof config.plugins === "object" ? config.plugins : (config.plugins = {});
2550
+ const load = plugins.load && typeof plugins.load === "object" ? plugins.load : (plugins.load = {});
2551
+ const paths = Array.isArray(load.paths) ? load.paths : [];
2552
+ load.paths = [
2553
+ ...paths.filter((item) => {
2554
+ const value = String(item || "");
2555
+ return !value.replaceAll("\\", "/").endsWith("/.openclaw/extensions/agent-wallet");
2556
+ }),
2557
+ extensionPath,
2558
+ ];
2559
+ const entries = plugins.entries && typeof plugins.entries === "object" ? plugins.entries : (plugins.entries = {});
2560
+ const walletEntry = entries["agent-wallet"];
2561
+ if (!walletEntry || typeof walletEntry !== "object") {
2562
+ return {
2563
+ name: "openclaw",
2564
+ attempted: false,
2565
+ ok: true,
2566
+ repaired: false,
2567
+ reason: "plugin entry not configured",
2568
+ };
2569
+ }
2570
+ walletEntry.enabled = true;
2571
+ walletEntry.config = walletEntry.config && typeof walletEntry.config === "object" ? walletEntry.config : {};
2572
+ walletEntry.config.packageRoot = packageRootPath;
2573
+ if (pythonBin) walletEntry.config.pythonBin = pythonBin;
2574
+ writeJsonFileAtomic(configPath, config);
2575
+ recordManagedIntegration(
2576
+ "openclaw",
2577
+ {
2578
+ config_path: configPath,
2579
+ extension_path: extensionPath,
2580
+ package_root: packageRootPath,
2581
+ restart_required: true,
2582
+ },
2583
+ env,
2167
2584
  );
2585
+ return {
2586
+ name: "openclaw",
2587
+ attempted: true,
2588
+ ok: true,
2589
+ repaired: true,
2590
+ config_path: configPath,
2591
+ restart_required: true,
2592
+ };
2168
2593
  }
2169
2594
 
2170
- function claudeCodeInstallPresent(env = process.env) {
2171
- const marketplaceDir = resolveClaudeCodeMarketplaceDir(env);
2172
- const cacheRoot = path.resolve(
2173
- expandHome(env.AGENT_WALLET_CLAUDE_CODE_CACHE_ROOT || "~/.claude/plugins/cache"),
2595
+ function symlinkManifestMatches(linkPath, manifestRelativePath, expectedName) {
2596
+ let target;
2597
+ try {
2598
+ if (!fs.lstatSync(linkPath).isSymbolicLink()) return false;
2599
+ target = path.resolve(path.dirname(linkPath), fs.readlinkSync(linkPath));
2600
+ } catch {
2601
+ return false;
2602
+ }
2603
+ try {
2604
+ const manifest = readJsonFile(path.join(target, manifestRelativePath));
2605
+ return manifest && manifest.name === expectedName;
2606
+ } catch {
2607
+ return false;
2608
+ }
2609
+ }
2610
+
2611
+ function legacyCodexIntegration(env = process.env) {
2612
+ const marketplacePath = resolveCodexMarketplacePath(env);
2613
+ let marketplace;
2614
+ try {
2615
+ marketplace = readJsonFile(marketplacePath);
2616
+ } catch {
2617
+ return null;
2618
+ }
2619
+ const pluginTarget = path.join(resolveCodexPluginInstallRoot(env), "agent-wallet");
2620
+ const registered = Array.isArray(marketplace?.plugins) && marketplace.plugins.some(
2621
+ (item) => item?.name === "agent-wallet" && item?.source?.source === "local",
2174
2622
  );
2175
- return (
2176
- fs.existsSync(path.join(marketplaceDir, "plugins", "agent-wallet")) ||
2177
- fs.existsSync(path.join(cacheRoot, CLAUDE_CODE_MARKETPLACE_NAME, "agent-wallet"))
2623
+ if (!registered || !symlinkManifestMatches(pluginTarget, ".codex-plugin/plugin.json", "agent-wallet")) {
2624
+ return null;
2625
+ }
2626
+ return recordManagedIntegration(
2627
+ "codex",
2628
+ {
2629
+ codex_home: resolveCodexHome(env),
2630
+ plugin_target: pluginTarget,
2631
+ marketplace_path: marketplacePath,
2632
+ marketplace_name: String(marketplace.name || "local"),
2633
+ adopted_legacy_install: true,
2634
+ },
2635
+ env,
2178
2636
  );
2179
2637
  }
2180
2638
 
2181
- function captureInstallerRefresh(name, runFn, args) {
2182
- const originalLog = console.log;
2183
- const lines = [];
2184
- console.log = (...items) => {
2185
- lines.push(items.join(" "));
2186
- };
2639
+ function legacyClaudeCodeIntegration(env = process.env) {
2640
+ const marketplaceDir = resolveClaudeCodeMarketplaceDir(env);
2641
+ let manifest;
2187
2642
  try {
2188
- const code = runFn(args);
2189
- const output = lines.join("\n");
2190
- return {
2191
- name,
2192
- attempted: true,
2193
- ok: code === 0,
2194
- code,
2195
- payload: output ? extractTrailingJson(output) : null,
2196
- error: code === 0 ? "" : output.trim(),
2197
- };
2198
- } catch (error) {
2643
+ manifest = readJsonFile(path.join(marketplaceDir, ".claude-plugin", "marketplace.json"));
2644
+ } catch {
2645
+ return null;
2646
+ }
2647
+ const pluginTarget = path.join(marketplaceDir, "plugins", "agent-wallet");
2648
+ const registered = manifest?.name === CLAUDE_CODE_MARKETPLACE_NAME &&
2649
+ Array.isArray(manifest.plugins) && manifest.plugins.some((item) => item?.name === "agent-wallet");
2650
+ if (!registered || !symlinkManifestMatches(pluginTarget, ".claude-plugin/plugin.json", "agent-wallet")) {
2651
+ return null;
2652
+ }
2653
+ return recordManagedIntegration(
2654
+ "claude-code",
2655
+ {
2656
+ marketplace_dir: marketplaceDir,
2657
+ plugin_target: pluginTarget,
2658
+ cache_root: path.resolve(
2659
+ expandHome(env.AGENT_WALLET_CLAUDE_CODE_CACHE_ROOT || "~/.claude/plugins/cache"),
2660
+ ),
2661
+ adopted_legacy_install: true,
2662
+ },
2663
+ env,
2664
+ );
2665
+ }
2666
+
2667
+ function runHostRefresh(command, args, env = process.env) {
2668
+ const binary = commandPath(command);
2669
+ if (!binary) {
2199
2670
  return {
2200
- name,
2201
- attempted: true,
2671
+ attempted: false,
2202
2672
  ok: false,
2203
- code: 1,
2204
- payload: null,
2205
- error: error?.message || String(error),
2673
+ error: `${command} CLI not found`,
2674
+ fix: args.join(" "),
2206
2675
  };
2207
- } finally {
2208
- console.log = originalLog;
2209
2676
  }
2677
+ const result = spawnSync(binary, args, { cwd: packageRoot, encoding: "utf8", env });
2678
+ return {
2679
+ attempted: true,
2680
+ ok: result.status === 0,
2681
+ error: result.status === 0 ? "" : (result.stderr || result.stdout || "").trim(),
2682
+ fix: result.status === 0 ? "" : `${command} ${args.join(" ")}`,
2683
+ };
2210
2684
  }
2211
2685
 
2212
- function refreshInstalledEditorIntegrations(env = process.env) {
2213
- const results = [];
2214
- if (hermesInstallPresent(env)) {
2215
- results.push(captureInstallerRefresh("hermes", runHermesInstall, ["--yes", "--force", "--skip-enable"]));
2686
+ function globalNpmPackageInfo(env = process.env) {
2687
+ const npmBin = commandPath("npm");
2688
+ if (!npmBin) return null;
2689
+ const rootResult = spawnSync(npmBin, ["root", "--global"], { encoding: "utf8", env });
2690
+ if (rootResult.status !== 0) return null;
2691
+ const packageRoot = path.join(rootResult.stdout.trim(), ...packageJson.name.split("/"));
2692
+ try {
2693
+ const manifest = readJsonFile(path.join(packageRoot, "package.json"));
2694
+ if (manifest?.name !== packageJson.name) return null;
2695
+ return { npm_bin: npmBin, package_root: packageRoot, version: manifest.version || null };
2696
+ } catch {
2697
+ return null;
2216
2698
  }
2217
- if (codexInstallPresent(env)) {
2218
- results.push(captureInstallerRefresh("codex", runCodexInstall, ["--yes", "--force", "--skip-enable"]));
2699
+ }
2700
+
2701
+ function refreshGlobalCliIfNeeded(env = process.env) {
2702
+ const installed = globalNpmPackageInfo(env);
2703
+ if (!installed) {
2704
+ return { attempted: false, ok: true, reason: "global package is not installed" };
2705
+ }
2706
+ if (installed.version === packageVersion) {
2707
+ return { attempted: false, ok: true, reason: "already current", version: packageVersion };
2219
2708
  }
2220
- if (claudeCodeInstallPresent(env)) {
2221
- results.push(
2222
- captureInstallerRefresh("claude-code", runClaudeCodeInstall, ["--yes", "--force", "--skip-enable"]),
2709
+ const fromNpmCache = path.resolve(packageRoot).split(path.sep).includes("_npx");
2710
+ const forced = env.AGENT_WALLET_FORCE_GLOBAL_CLI_REFRESH === "1";
2711
+ if (!fromNpmCache && !forced) {
2712
+ return {
2713
+ attempted: false,
2714
+ ok: false,
2715
+ reason: "installer is not running from npm exec",
2716
+ installed_version: installed.version,
2717
+ target_version: packageVersion,
2718
+ fix: `npm install --global ${packageJson.name}@${packageVersion}`,
2719
+ };
2720
+ }
2721
+ const packageSpec = `${packageJson.name}@${packageVersion}`;
2722
+ const result = spawnSync(
2723
+ installed.npm_bin,
2724
+ ["install", "--global", "--no-audit", "--no-fund", packageSpec],
2725
+ { encoding: "utf8", env },
2726
+ );
2727
+ return {
2728
+ attempted: true,
2729
+ ok: result.status === 0,
2730
+ previous_version: installed.version,
2731
+ target_version: packageVersion,
2732
+ error: result.status === 0 ? "" : (result.stderr || result.stdout || "").trim(),
2733
+ fix: result.status === 0 ? "" : `npm install --global ${packageSpec}`,
2734
+ };
2735
+ }
2736
+
2737
+ function refreshCodexIntegration(entry, env = process.env) {
2738
+ const pluginTarget = path.resolve(
2739
+ expandHome(entry.plugin_target || path.join(resolveCodexPluginInstallRoot(env), "agent-wallet")),
2740
+ );
2741
+ const marketplacePath = path.resolve(
2742
+ expandHome(entry.marketplace_path || resolveCodexMarketplacePath(env)),
2743
+ );
2744
+ const link = repairRuntimeSymlink(
2745
+ "codex",
2746
+ pluginTarget,
2747
+ path.join(currentRuntimePath(env), "codex", "plugins", "agent-wallet"),
2748
+ env,
2749
+ { allowExternal: true },
2750
+ );
2751
+ if (!link.ok) return link;
2752
+ const marketplace = ensureCodexMarketplaceEntry({ marketplacePath, pluginName: "agent-wallet" });
2753
+ const registration = runHostRefresh(
2754
+ "codex",
2755
+ ["plugin", "add", `agent-wallet@${marketplace.marketplace_name}`],
2756
+ { ...env, CODEX_HOME: entry.codex_home || resolveCodexHome(env) },
2757
+ );
2758
+ recordManagedIntegration(
2759
+ "codex",
2760
+ {
2761
+ ...entry,
2762
+ plugin_target: pluginTarget,
2763
+ marketplace_path: marketplacePath,
2764
+ marketplace_name: marketplace.marketplace_name,
2765
+ registration_ok: registration.ok,
2766
+ restart_required: true,
2767
+ },
2768
+ env,
2769
+ );
2770
+ return {
2771
+ ...link,
2772
+ ok: link.ok && registration.ok,
2773
+ registration,
2774
+ restart_required: true,
2775
+ };
2776
+ }
2777
+
2778
+ function refreshClaudeCodeIntegration(entry, env = process.env) {
2779
+ const marketplaceDir = path.resolve(
2780
+ expandHome(entry.marketplace_dir || resolveClaudeCodeMarketplaceDir(env)),
2781
+ );
2782
+ const cacheRoot = path.resolve(
2783
+ expandHome(entry.cache_root || env.AGENT_WALLET_CLAUDE_CODE_CACHE_ROOT || "~/.claude/plugins/cache"),
2784
+ );
2785
+ const pluginTarget = path.resolve(
2786
+ expandHome(entry.plugin_target || path.join(marketplaceDir, "plugins", "agent-wallet")),
2787
+ );
2788
+ const link = repairRuntimeSymlink(
2789
+ "claude-code",
2790
+ pluginTarget,
2791
+ path.join(currentRuntimePath(env), "claude-code", "plugins", "agent-wallet"),
2792
+ env,
2793
+ { allowExternal: true },
2794
+ );
2795
+ if (!link.ok) return link;
2796
+ ensureClaudeCodeMarketplace(
2797
+ marketplaceDir,
2798
+ path.join(currentRuntimePath(env), "claude-code", "plugins", "agent-wallet"),
2799
+ true,
2800
+ );
2801
+ const marketplaceAdd = runHostRefresh(
2802
+ "claude",
2803
+ ["plugin", "marketplace", "add", marketplaceDir, "--scope", "user"],
2804
+ env,
2805
+ );
2806
+ const registration = marketplaceAdd.ok
2807
+ ? runHostRefresh(
2808
+ "claude",
2809
+ ["plugin", "install", `agent-wallet@${CLAUDE_CODE_MARKETPLACE_NAME}`, "--scope", "user"],
2810
+ env,
2811
+ )
2812
+ : { attempted: false, ok: false, error: "marketplace refresh failed", fix: marketplaceAdd.fix };
2813
+ const cachePins = pinClaudeCacheCopies({
2814
+ ...env,
2815
+ AGENT_WALLET_CLAUDE_CODE_CACHE_ROOT: cacheRoot,
2816
+ });
2817
+ recordManagedIntegration(
2818
+ "claude-code",
2819
+ {
2820
+ ...entry,
2821
+ marketplace_dir: marketplaceDir,
2822
+ plugin_target: pluginTarget,
2823
+ cache_root: cacheRoot,
2824
+ registration_ok: registration.ok,
2825
+ restart_required: true,
2826
+ },
2827
+ env,
2828
+ );
2829
+ return {
2830
+ ...link,
2831
+ ok: link.ok && marketplaceAdd.ok && registration.ok,
2832
+ marketplace_add: marketplaceAdd,
2833
+ registration,
2834
+ cache_pins: cachePins,
2835
+ restart_required: true,
2836
+ };
2837
+ }
2838
+
2839
+ function repairInstalledEditorIntegrations(env = process.env) {
2840
+ const results = [repairOpenclawIntegration(env)];
2841
+ const currentRoot = currentRuntimePath(env);
2842
+ const hermesEntry = managedIntegration("hermes", env) || legacyHermesIntegration(env);
2843
+ if (hermesEntry) {
2844
+ const hermesTarget = path.resolve(expandHome(hermesEntry.plugin_target));
2845
+ const hermesEnvPath = path.resolve(expandHome(hermesEntry.env_path));
2846
+ const result = repairRuntimeSymlink(
2847
+ "hermes",
2848
+ hermesTarget,
2849
+ path.join(currentRoot, "hermes", "plugins", "agent_wallet"),
2850
+ env,
2851
+ { allowExternal: true },
2223
2852
  );
2853
+ result.env_repaired = repairHermesEnv(hermesEnvPath, env);
2854
+ result.restart_required = true;
2855
+ if (result.ok) {
2856
+ recordManagedIntegration(
2857
+ "hermes",
2858
+ {
2859
+ ...hermesEntry,
2860
+ plugin_target: hermesTarget,
2861
+ env_path: hermesEnvPath,
2862
+ restart_required: true,
2863
+ },
2864
+ env,
2865
+ );
2866
+ }
2867
+ results.push(result);
2224
2868
  }
2869
+
2870
+ const codexEntry = managedIntegration("codex", env) || legacyCodexIntegration(env);
2871
+ if (codexEntry) results.push(refreshCodexIntegration(codexEntry, env));
2872
+
2873
+ const claudeEntry = managedIntegration("claude-code", env) || legacyClaudeCodeIntegration(env);
2874
+ if (claudeEntry) results.push(refreshClaudeCodeIntegration(claudeEntry, env));
2225
2875
  return results;
2226
2876
  }
2227
2877