@agentlayer.tech/wallet 0.1.73 → 0.1.74

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,69 @@ 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 writeUpdateJournal(state, details = {}, env = process.env) {
396
+ writeJsonFile(updateJournalPath(env), {
397
+ schema_version: 1,
398
+ state,
399
+ version: packageVersion,
400
+ updated_at: new Date().toISOString(),
401
+ ...details,
402
+ });
403
+ }
404
+
405
+ function writeReleaseState(runtimeRoot, state, details = {}) {
406
+ writeJsonFile(path.join(runtimeRoot, ".agent-wallet-release.json"), {
407
+ schema_version: 1,
408
+ version: packageVersion,
409
+ state,
410
+ updated_at: new Date().toISOString(),
411
+ ...details,
412
+ });
413
+ }
414
+
415
+ function failStagingRuntime(stagingRoot, error) {
416
+ if (!stagingRoot || !fs.existsSync(stagingRoot)) return null;
417
+ writeReleaseState(stagingRoot, "failed", { error: String(error || "install failed") });
418
+ const failedRoot = path.join(
419
+ path.dirname(stagingRoot),
420
+ `.failed-${packageVersion}-${Date.now()}`,
421
+ );
422
+ fs.renameSync(stagingRoot, failedRoot);
423
+ return failedRoot;
424
+ }
425
+
426
+ function commitStagedRuntime(stagingRoot, releaseRoot) {
427
+ if (!stagingRoot) return null;
428
+ let replacedRoot = null;
429
+ if (fs.existsSync(releaseRoot)) {
430
+ replacedRoot = uniquePathWithSuffix(
431
+ path.join(path.dirname(releaseRoot), `${path.basename(releaseRoot)}-replaced`),
432
+ );
433
+ fs.renameSync(releaseRoot, replacedRoot);
434
+ }
435
+ try {
436
+ fs.renameSync(stagingRoot, releaseRoot);
437
+ } catch (error) {
438
+ if (replacedRoot && !fs.existsSync(releaseRoot)) {
439
+ fs.renameSync(replacedRoot, releaseRoot);
440
+ }
441
+ throw error;
442
+ }
443
+ return replacedRoot;
444
+ }
445
+
383
446
  function currentRuntimePath(env = process.env) {
384
447
  return path.join(resolveRuntimeBase(env), "current");
385
448
  }
@@ -507,7 +570,21 @@ function listReleases(env = process.env) {
507
570
  try {
508
571
  return fs
509
572
  .readdirSync(releasesDir, { withFileTypes: true })
510
- .filter((entry) => entry.isDirectory())
573
+ .filter((entry) => entry.isDirectory() && !entry.name.startsWith("."))
574
+ .map((entry) => entry.name)
575
+ .sort();
576
+ } catch (error) {
577
+ if (error?.code === "ENOENT") return [];
578
+ throw error;
579
+ }
580
+ }
581
+
582
+ function listFailedReleases(env = process.env) {
583
+ const releasesDir = path.join(resolveRuntimeBase(env), "releases");
584
+ try {
585
+ return fs
586
+ .readdirSync(releasesDir, { withFileTypes: true })
587
+ .filter((entry) => entry.isDirectory() && entry.name.startsWith(".failed-"))
511
588
  .map((entry) => entry.name)
512
589
  .sort();
513
590
  } catch (error) {
@@ -944,6 +1021,44 @@ function readBootKeyFromKeystore(env = process.env) {
944
1021
  }
945
1022
  }
946
1023
 
1024
+ // New runtimes own boot-key precedence and verify candidates against
1025
+ // sealed_keys.json. An import failure means the active runtime predates this
1026
+ // bridge, so callers may use the legacy JS fallback below.
1027
+ function readBootKeyFromRuntimeResolver(env = process.env) {
1028
+ const runtimeRoot = resolvedCurrentRuntimeRoot(env);
1029
+ if (!runtimeRoot) return { supported: false, key: "" };
1030
+ const py = resolveVenvPython(runtimeRoot);
1031
+ if (!py) return { supported: false, key: "" };
1032
+ try {
1033
+ const res = spawnSync(
1034
+ py,
1035
+ ["-c", "from agent_wallet.config import resolve_boot_key_for_installer as r; print(r())"],
1036
+ {
1037
+ cwd: path.join(runtimeRoot, "agent-wallet"),
1038
+ encoding: "utf8",
1039
+ timeout: positiveIntEnv("AGENT_WALLET_KEYSTORE_BRIDGE_TIMEOUT_MS", KEYSTORE_BRIDGE_TIMEOUT_MS, env),
1040
+ env: { ...env, OPENCLAW_HOME: resolveOpenclawHome(env) },
1041
+ },
1042
+ );
1043
+ if ((res.status ?? 1) !== 0) return { supported: false, key: "" };
1044
+ return { supported: true, key: String(res.stdout || "").trim() };
1045
+ } catch {
1046
+ return { supported: false, key: "" };
1047
+ }
1048
+ }
1049
+
1050
+ function resolveLegacyInstallerBootKey(env = process.env) {
1051
+ for (const [source, key] of [
1052
+ ["legacy_keystore", readBootKeyFromKeystore(env)],
1053
+ ["current_runtime_env", currentBootKey(env)],
1054
+ ["configured_file", resolveBootKeyFromFile(env)],
1055
+ ["default_file", readTextIfExists(defaultBootKeyFile(env)).trim()],
1056
+ ]) {
1057
+ if (key) return { key, source };
1058
+ }
1059
+ return { key: "", source: "none" };
1060
+ }
1061
+
947
1062
  // Provision the boot key into the OS keystore via the freshly installed runtime's
948
1063
  // Python. Returns true only when the import stored AND verified the key, so the
949
1064
  // caller may safely drop the plaintext .env copy. Best-effort: false on any failure
@@ -1218,6 +1333,7 @@ function runStatus(args = []) {
1218
1333
  previous_runtime: readLinkOrNull(previousRuntimePath()),
1219
1334
  active_version: activeVersion(),
1220
1335
  available_releases: listReleases(),
1336
+ failed_releases: listFailedReleases(),
1221
1337
  update_available: computeUpdateAvailability(),
1222
1338
  runtime_in_sync: computeRuntimeInSync(),
1223
1339
  };
@@ -1236,15 +1352,17 @@ function buildInstallerEnv(args) {
1236
1352
  const sealedKeysPath = path.join(resolveOpenclawHome(env), "sealed_keys.json");
1237
1353
  const sealedKeysExist = fs.existsSync(sealedKeysPath);
1238
1354
  const dryRun = hasFlag(args, "--dry-run");
1355
+ let bootKeySource = env.AGENT_WALLET_BOOT_KEY ? "environment" : "none";
1239
1356
  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);
1357
+ const runtimeResolution = readBootKeyFromRuntimeResolver(env);
1358
+ const fallback = runtimeResolution.supported
1359
+ ? {
1360
+ key: runtimeResolution.key,
1361
+ source: runtimeResolution.key ? "runtime_verified" : "runtime_rejected",
1362
+ }
1363
+ : resolveLegacyInstallerBootKey(env);
1364
+ const existingBootKey = fallback.key;
1365
+ bootKeySource = fallback.source;
1248
1366
  if (existingBootKey) {
1249
1367
  env.AGENT_WALLET_BOOT_KEY = existingBootKey;
1250
1368
  }
@@ -1264,6 +1382,7 @@ function buildInstallerEnv(args) {
1264
1382
  if (!sealedKeysExist && shouldGenerateSecrets && !env.AGENT_WALLET_BOOT_KEY) {
1265
1383
  generated.AGENT_WALLET_BOOT_KEY = token();
1266
1384
  env.AGENT_WALLET_BOOT_KEY = generated.AGENT_WALLET_BOOT_KEY;
1385
+ bootKeySource = "generated";
1267
1386
  }
1268
1387
  if (!sealedKeysExist && shouldGenerateSecrets && !env.AGENT_WALLET_MASTER_KEY) {
1269
1388
  generated.AGENT_WALLET_MASTER_KEY = token();
@@ -1273,7 +1392,7 @@ function buildInstallerEnv(args) {
1273
1392
  generated.AGENT_WALLET_APPROVAL_SECRET = token();
1274
1393
  env.AGENT_WALLET_APPROVAL_SECRET = generated.AGENT_WALLET_APPROVAL_SECRET;
1275
1394
  }
1276
- return { env, generated };
1395
+ return { env, generated, bootKeySource };
1277
1396
  }
1278
1397
 
1279
1398
  function runInstall(args, { commandName = "install" } = {}) {
@@ -1290,9 +1409,11 @@ function runInstall(args, { commandName = "install" } = {}) {
1290
1409
  const previousPath = previousRuntimePath();
1291
1410
  const installerArgs = withoutCliOnlyArgs(args);
1292
1411
  const dryRun = hasFlag(args, "--dry-run");
1412
+ const stagingRoot = !explicitRuntimeRoot && !dryRun ? stagingRootFor(packageVersion) : null;
1413
+ const installRoot = stagingRoot || releaseRoot;
1293
1414
 
1294
1415
  if (!hasFlag(installerArgs, "--runtime-root")) {
1295
- installerArgs.push("--runtime-root", releaseRoot);
1416
+ installerArgs.push("--runtime-root", installRoot);
1296
1417
  }
1297
1418
  if (!hasFlag(installerArgs, "--install-from-runtime")) {
1298
1419
  installerArgs.push("--install-from-runtime");
@@ -1305,7 +1426,11 @@ function runInstall(args, { commandName = "install" } = {}) {
1305
1426
  console.error(error.message);
1306
1427
  return 1;
1307
1428
  }
1308
- const { env, generated } = installerEnv;
1429
+ const { env, generated, bootKeySource } = installerEnv;
1430
+ if (stagingRoot) {
1431
+ env.OPENCLAW_INSTALL_FINAL_ROOT = releaseRoot;
1432
+ writeUpdateJournal("preparing", { staging_root: stagingRoot, release_root: releaseRoot }, env);
1433
+ }
1309
1434
  const result = spawnSync("sh", [setupPath, ...installerArgs], {
1310
1435
  cwd: packageRoot,
1311
1436
  stdio: "inherit",
@@ -1313,10 +1438,22 @@ function runInstall(args, { commandName = "install" } = {}) {
1313
1438
  });
1314
1439
 
1315
1440
  if (result.error) {
1441
+ const failedRoot = failStagingRuntime(stagingRoot, result.error.message);
1442
+ if (!dryRun) {
1443
+ writeUpdateJournal("failed", { failed_runtime: failedRoot, error: result.error.message }, env);
1444
+ }
1316
1445
  console.error(result.error.message);
1317
1446
  return 1;
1318
1447
  }
1319
1448
  if ((result.status ?? 1) !== 0) {
1449
+ const failedRoot = failStagingRuntime(stagingRoot, `installer exited with ${result.status ?? 1}`);
1450
+ if (!dryRun) {
1451
+ writeUpdateJournal(
1452
+ "failed",
1453
+ { failed_runtime: failedRoot, error: `installer exited with ${result.status ?? 1}` },
1454
+ env,
1455
+ );
1456
+ }
1320
1457
  return result.status ?? 1;
1321
1458
  }
1322
1459
 
@@ -1324,34 +1461,21 @@ function runInstall(args, { commandName = "install" } = {}) {
1324
1461
  return 0;
1325
1462
  }
1326
1463
 
1327
- const currentTarget = existingRuntimePointerTarget(currentPath);
1328
- if (currentTarget) {
1329
- switchSymlink(previousPath, currentTarget);
1330
- }
1331
- switchSymlink(currentPath, releaseRoot);
1332
-
1333
1464
  // Installs that pass --skip-python-setup may have no venv, so this handshake
1334
1465
  // would fail and trigger a spurious rollback; such flows must set
1335
1466
  // AGENT_WALLET_VERIFY_DISABLE=1 (verifyRuntime then skips).
1336
- const verification = verifyRuntime(releaseRoot, env);
1467
+ const currentTarget = existingRuntimePointerTarget(currentPath);
1468
+ const verification = verifyRuntime(installRoot, env);
1337
1469
  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
- }
1470
+ const failedRoot = failStagingRuntime(stagingRoot, verification.error);
1471
+ const rolledBack = Boolean(currentTarget);
1343
1472
  const previousVersion = rolledBack
1344
- ? path.basename(path.resolve(path.dirname(currentPath), rollbackTarget))
1473
+ ? path.basename(currentTarget)
1345
1474
  : null;
1346
1475
 
1347
1476
  let human;
1348
1477
  let fix;
1349
1478
  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
1479
  human =
1356
1480
  verification.category === "broken_release"
1357
1481
  ? `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 +1504,9 @@ function runInstall(args, { commandName = "install" } = {}) {
1380
1504
  category: verification.category || "unknown",
1381
1505
  error: `runtime verification failed: ${verification.error}`,
1382
1506
  rolled_back: rolledBack,
1507
+ switched_current: false,
1383
1508
  kept_version: previousVersion,
1509
+ failed_runtime: failedRoot,
1384
1510
  current_runtime_target: readLinkOrNull(currentPath),
1385
1511
  message: human,
1386
1512
  fix,
@@ -1389,15 +1515,20 @@ function runInstall(args, { commandName = "install" } = {}) {
1389
1515
  2,
1390
1516
  ),
1391
1517
  );
1518
+ writeUpdateJournal(
1519
+ "failed",
1520
+ { failed_runtime: failedRoot, error: verification.error, kept_version: previousVersion },
1521
+ env,
1522
+ );
1392
1523
  return 1;
1393
1524
  }
1394
1525
 
1395
1526
  if (env.AGENT_WALLET_BOOT_KEY) {
1396
- const envPath = path.join(releaseRoot, "agent-wallet", ".env");
1527
+ const envPath = path.join(installRoot, "agent-wallet", ".env");
1397
1528
  // Prefer the OS keystore: provision the boot key there and keep the plaintext
1398
1529
  // out of the release .env entirely. Only fall back to the legacy .env write when
1399
1530
  // no keystore round-trip is possible, so the runtime can still resolve the key.
1400
- if (provisionBootKeyToKeystore(releaseRoot, env, env.AGENT_WALLET_BOOT_KEY)) {
1531
+ if (provisionBootKeyToKeystore(installRoot, env, env.AGENT_WALLET_BOOT_KEY)) {
1401
1532
  envFileUnset(envPath, ["AGENT_WALLET_BOOT_KEY"]);
1402
1533
  } else {
1403
1534
  envFileSet(envPath, {
@@ -1406,7 +1537,32 @@ function runInstall(args, { commandName = "install" } = {}) {
1406
1537
  }
1407
1538
  }
1408
1539
 
1409
- const integrationRefresh = refreshInstalledEditorIntegrations(env);
1540
+ writeReleaseState(installRoot, "verified", {
1541
+ verification_skipped: Boolean(verification.skipped),
1542
+ });
1543
+ writeUpdateJournal("verified", { staging_root: stagingRoot, release_root: releaseRoot }, env);
1544
+
1545
+ let replacedRoot = null;
1546
+ try {
1547
+ replacedRoot = commitStagedRuntime(stagingRoot, releaseRoot);
1548
+ } catch (error) {
1549
+ const failedRoot = failStagingRuntime(stagingRoot, error.message);
1550
+ writeUpdateJournal("failed", { failed_runtime: failedRoot, error: error.message }, env);
1551
+ console.error(`Could not commit staged runtime: ${error.message}`);
1552
+ return 1;
1553
+ }
1554
+
1555
+ const previousTarget =
1556
+ currentTarget && path.resolve(currentTarget) === path.resolve(releaseRoot) && replacedRoot
1557
+ ? replacedRoot
1558
+ : currentTarget;
1559
+ if (previousTarget) {
1560
+ switchSymlink(previousPath, previousTarget);
1561
+ }
1562
+ switchSymlink(currentPath, releaseRoot);
1563
+ writeUpdateJournal("committed", { release_root: releaseRoot, previous_runtime: previousTarget }, env);
1564
+
1565
+ const integrationRefresh = repairInstalledEditorIntegrations(env);
1410
1566
 
1411
1567
  const pythonInfo = activePythonRuntimeInfo(env);
1412
1568
  const nodeInfo = activeNodeRuntimeInfo(env)
@@ -1426,6 +1582,9 @@ function runInstall(args, { commandName = "install" } = {}) {
1426
1582
  current_runtime: currentPath,
1427
1583
  previous_runtime: readLinkOrNull(previousPath),
1428
1584
  generated_runtime_secrets: Object.keys(generated),
1585
+ boot_key_source: bootKeySource,
1586
+ staged: Boolean(stagingRoot),
1587
+ release_state: "verified",
1429
1588
  integration_refresh: integrationRefresh,
1430
1589
  },
1431
1590
  null,
@@ -1867,7 +2026,7 @@ function runHermesInstall(args) {
1867
2026
  function runCodexInstall(args) {
1868
2027
  const codexHome = resolveCodexHome();
1869
2028
  const pluginSource = resolveCodexPluginSource();
1870
- const pinnedEnv = pinEditorMcpEnv(pluginSource);
2029
+ const pinnedEnv = { pinned: false, reason: "plugin source is immutable" };
1871
2030
  const pluginRoot = resolveCodexPluginInstallRoot();
1872
2031
  const pluginTarget = path.join(pluginRoot, "agent-wallet");
1873
2032
  const marketplacePath = resolveCodexMarketplacePath();
@@ -1989,10 +2148,6 @@ function pinHomeIntoMcpFile(mcpPath, env = process.env) {
1989
2148
  return { pinned: true, openclaw_home: home, path: mcpPath };
1990
2149
  }
1991
2150
 
1992
- function pinEditorMcpEnv(pluginSource, env = process.env) {
1993
- return pinHomeIntoMcpFile(path.join(pluginSource, ".mcp.json"), env);
1994
- }
1995
-
1996
2151
  // Claude Code copies the plugin into a version-keyed cache and reads THAT copy,
1997
2152
  // so the bundle pin alone is ineffective once a cache exists. Pin every cached
1998
2153
  // copy too. Cache root is overridable for tests.
@@ -2065,8 +2220,7 @@ function ensureClaudeCodeMarketplace(marketplaceDir, pluginSource, force) {
2065
2220
 
2066
2221
  function runClaudeCodeInstall(args) {
2067
2222
  const pluginSource = resolveClaudeCodePluginSource();
2068
- const pinnedEnv = pinEditorMcpEnv(pluginSource);
2069
- const pinnedCache = pinClaudeCacheCopies();
2223
+ const pinnedEnv = { pinned: false, reason: "plugin source is immutable" };
2070
2224
  const force = hasFlag(args, "--force");
2071
2225
  const skipEnable = hasFlag(args, "--skip-enable");
2072
2226
  const claudeBin = commandPath("claude");
@@ -2128,6 +2282,7 @@ function runClaudeCodeInstall(args) {
2128
2282
  }
2129
2283
 
2130
2284
  const ok = enable.skipped || enable.ok;
2285
+ const pinnedCache = pinClaudeCacheCopies();
2131
2286
  const pluginDirFlagFull = `claude --plugin-dir ${pluginSource}`;
2132
2287
  console.log(
2133
2288
  JSON.stringify(
@@ -2152,75 +2307,115 @@ function runClaudeCodeInstall(args) {
2152
2307
  return enable.skipped || enable.ok ? 0 : 1;
2153
2308
  }
2154
2309
 
2155
- function hermesInstallPresent(env = process.env) {
2156
- const hermesHome = resolveHermesHome(env);
2157
- return (
2158
- fs.existsSync(path.join(hermesHome, "plugins", "agent_wallet")) ||
2159
- fs.existsSync(path.join(hermesHome, ".env"))
2160
- );
2161
- }
2162
-
2163
- function codexInstallPresent(env = process.env) {
2164
- return (
2165
- fs.existsSync(path.join(resolveCodexPluginInstallRoot(env), "agent-wallet")) ||
2166
- fs.existsSync(resolveCodexMarketplacePath(env))
2167
- );
2310
+ function runtimeReleasePath(value, env = process.env) {
2311
+ if (!value) return false;
2312
+ try {
2313
+ const releasesRoot = path.join(resolveRuntimeBase(env), "releases");
2314
+ const relative = path.relative(releasesRoot, path.resolve(expandHome(String(value))));
2315
+ return Boolean(relative) && relative !== ".." && !relative.startsWith(`..${path.sep}`);
2316
+ } catch {
2317
+ return false;
2318
+ }
2168
2319
  }
2169
2320
 
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"),
2174
- );
2175
- return (
2176
- fs.existsSync(path.join(marketplaceDir, "plugins", "agent-wallet")) ||
2177
- fs.existsSync(path.join(cacheRoot, CLAUDE_CODE_MARKETPLACE_NAME, "agent-wallet"))
2178
- );
2321
+ function pathEntryExists(pathname) {
2322
+ try {
2323
+ fs.lstatSync(pathname);
2324
+ return true;
2325
+ } catch (error) {
2326
+ if (error?.code === "ENOENT") return false;
2327
+ throw error;
2328
+ }
2179
2329
  }
2180
2330
 
2181
- function captureInstallerRefresh(name, runFn, args) {
2182
- const originalLog = console.log;
2183
- const lines = [];
2184
- console.log = (...items) => {
2185
- lines.push(items.join(" "));
2186
- };
2331
+ function repairRuntimeSymlink(name, linkPath, desiredTarget, env = process.env) {
2332
+ let rawTarget;
2187
2333
  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
- };
2334
+ const stat = fs.lstatSync(linkPath);
2335
+ if (!stat.isSymbolicLink()) {
2336
+ return { name, attempted: false, ok: true, repaired: false, reason: "not a symlink" };
2337
+ }
2338
+ rawTarget = fs.readlinkSync(linkPath);
2198
2339
  } catch (error) {
2199
- return {
2200
- name,
2201
- attempted: true,
2202
- ok: false,
2203
- code: 1,
2204
- payload: null,
2205
- error: error?.message || String(error),
2206
- };
2207
- } finally {
2208
- console.log = originalLog;
2340
+ if (error?.code === "ENOENT") {
2341
+ return { name, attempted: false, ok: true, repaired: false, reason: "not installed" };
2342
+ }
2343
+ return { name, attempted: true, ok: false, repaired: false, error: error.message };
2344
+ }
2345
+
2346
+ const absoluteTarget = path.resolve(path.dirname(linkPath), rawTarget);
2347
+ const logicalCurrent = currentRuntimePath(env);
2348
+ if (absoluteTarget === path.resolve(desiredTarget) || absoluteTarget.startsWith(`${logicalCurrent}${path.sep}`)) {
2349
+ return { name, attempted: true, ok: true, repaired: false, reason: "already current" };
2350
+ }
2351
+ if (!runtimeReleasePath(absoluteTarget, env)) {
2352
+ return { name, attempted: false, ok: true, repaired: false, reason: "external target preserved" };
2209
2353
  }
2354
+ if (!fs.existsSync(desiredTarget)) {
2355
+ return { name, attempted: true, ok: false, repaired: false, error: `missing ${desiredTarget}` };
2356
+ }
2357
+ switchSymlink(linkPath, desiredTarget);
2358
+ return { name, attempted: true, ok: true, repaired: true, target: desiredTarget };
2210
2359
  }
2211
2360
 
2212
- function refreshInstalledEditorIntegrations(env = process.env) {
2213
- const results = [];
2214
- if (hermesInstallPresent(env)) {
2215
- results.push(captureInstallerRefresh("hermes", runHermesInstall, ["--yes", "--force", "--skip-enable"]));
2361
+ function repairHermesEnv(env = process.env) {
2362
+ const envPath = path.join(resolveHermesHome(env), ".env");
2363
+ const existing = readEnvFile(envPath);
2364
+ const repaired = [];
2365
+ const currentPackage = path.join(currentRuntimePath(env), "agent-wallet");
2366
+ if (runtimeReleasePath(existing.AGENT_WALLET_PACKAGE_ROOT, env)) {
2367
+ envFileSet(envPath, { AGENT_WALLET_PACKAGE_ROOT: currentPackage });
2368
+ repaired.push("AGENT_WALLET_PACKAGE_ROOT");
2369
+ }
2370
+ if (runtimeReleasePath(existing.AGENT_WALLET_PYTHON, env)) {
2371
+ const currentPython = resolveVenvPython(currentRuntimePath(env));
2372
+ if (currentPython) {
2373
+ envFileSet(envPath, { AGENT_WALLET_PYTHON: currentPython });
2374
+ } else {
2375
+ envFileUnset(envPath, ["AGENT_WALLET_PYTHON"]);
2376
+ }
2377
+ repaired.push("AGENT_WALLET_PYTHON");
2216
2378
  }
2217
- if (codexInstallPresent(env)) {
2218
- results.push(captureInstallerRefresh("codex", runCodexInstall, ["--yes", "--force", "--skip-enable"]));
2379
+ return repaired;
2380
+ }
2381
+
2382
+ function repairInstalledEditorIntegrations(env = process.env) {
2383
+ const results = [];
2384
+ const currentRoot = currentRuntimePath(env);
2385
+ const hermesTarget = path.join(resolveHermesHome(env), "plugins", "agent_wallet");
2386
+ if (pathEntryExists(hermesTarget) || fs.existsSync(path.join(resolveHermesHome(env), ".env"))) {
2387
+ const result = repairRuntimeSymlink(
2388
+ "hermes",
2389
+ hermesTarget,
2390
+ path.join(currentRoot, "hermes", "plugins", "agent_wallet"),
2391
+ env,
2392
+ );
2393
+ result.env_repaired = repairHermesEnv(env);
2394
+ results.push(result);
2219
2395
  }
2220
- if (claudeCodeInstallPresent(env)) {
2396
+
2397
+ const codexTarget = path.join(resolveCodexPluginInstallRoot(env), "agent-wallet");
2398
+ if (pathEntryExists(codexTarget)) {
2221
2399
  results.push(
2222
- captureInstallerRefresh("claude-code", runClaudeCodeInstall, ["--yes", "--force", "--skip-enable"]),
2400
+ repairRuntimeSymlink(
2401
+ "codex",
2402
+ codexTarget,
2403
+ path.join(currentRoot, "codex", "plugins", "agent-wallet"),
2404
+ env,
2405
+ ),
2406
+ );
2407
+ }
2408
+
2409
+ const claudeTarget = path.join(resolveClaudeCodeMarketplaceDir(env), "plugins", "agent-wallet");
2410
+ if (pathEntryExists(claudeTarget)) {
2411
+ const result = repairRuntimeSymlink(
2412
+ "claude-code",
2413
+ claudeTarget,
2414
+ path.join(currentRoot, "claude-code", "plugins", "agent-wallet"),
2415
+ env,
2223
2416
  );
2417
+ result.cache_pins = pinClaudeCacheCopies(env);
2418
+ results.push(result);
2224
2419
  }
2225
2420
  return results;
2226
2421
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "agent-wallet",
3
3
  "displayName": "Agent Wallet",
4
- "version": "0.1.73",
4
+ "version": "0.1.74",
5
5
  "description": "Claude Code bridge for the existing AgentLayer wallet runtime. Connects to Solana, Bitcoin, and EVM wallets without creating a new one.",
6
6
  "author": {
7
7
  "name": "AgentLayer"
@@ -7,8 +7,7 @@
7
7
  ],
8
8
  "env": {
9
9
  "FASTMCP_SHOW_SERVER_BANNER": "false",
10
- "FASTMCP_LOG_LEVEL": "ERROR",
11
- "OPENCLAW_HOME": "/tmp/openclaw-cli-install-keystore-precedence-smoke"
10
+ "FASTMCP_LOG_LEVEL": "ERROR"
12
11
  }
13
12
  }
14
13
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-wallet",
3
- "version": "0.1.73",
3
+ "version": "0.1.74",
4
4
  "description": "Codex plugin bridge for the AgentLayer wallet runtime.",
5
5
  "author": {
6
6
  "name": "AgentLayer"
@@ -1,5 +1,5 @@
1
1
  name: agent-wallet
2
- version: 0.1.73
2
+ version: 0.1.74
3
3
  description: Thin Hermes Agent bridge to the existing AgentLayer/OpenClaw wallet backend
4
4
  provides_tools:
5
5
  - agent_wallet_tools
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentlayer.tech/wallet",
3
- "version": "0.1.73",
3
+ "version": "0.1.74",
4
4
  "description": "NPM installer for the OpenClaw Agent Wallet local runtime.",
5
5
  "type": "module",
6
6
  "repository": {
@@ -19,6 +19,7 @@
19
19
  "build:openclaw-plugins": "node scripts/manage_openclaw_plugin_packages.mjs build",
20
20
  "check:openclaw-plugins": "node scripts/manage_openclaw_plugin_packages.mjs check",
21
21
  "check:release-version": "node scripts/check_release_version.mjs",
22
+ "test:release-promotion": "node --test scripts/validate_npm_promotion.test.mjs",
22
23
  "version:sync": "node scripts/sync_version.mjs",
23
24
  "release:local": "node scripts/release_local.mjs",
24
25
  "test:npm-installer": "python3 agent-wallet/tests/smoke_npm_installer.py",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wdk-btc-wallet",
3
- "version": "0.1.73",
3
+ "version": "0.1.74",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "description": "Separate BTC-only wallet service built on Tether WDK.",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wdk-evm-wallet",
3
- "version": "0.1.73",
3
+ "version": "0.1.74",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "description": "Separate EVM wallet service built on Tether WDK.",
@@ -14,7 +14,8 @@
14
14
  "test:morpho-runtime": "node --test --test-concurrency=1 tests/smoke_morpho_runtime.mjs",
15
15
  "test:lido-runtime": "node --test --test-concurrency=1 tests/smoke_lido_runtime.mjs",
16
16
  "test:uniswap-runtime": "node --test --test-concurrency=1 tests/smoke_uniswap_runtime.mjs",
17
- "test:unit": "node --test tests/unit_uniswap_helpers.mjs"
17
+ "test:unit": "node --test tests/unit_uniswap_helpers.mjs",
18
+ "test:identity": "node --test tests/unit_instance_identity.mjs"
18
19
  },
19
20
  "dependencies": {
20
21
  "@morpho-org/morpho-sdk": "^3.2.0",