@agentlayer.tech/wallet 0.1.72 → 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.
- package/.openclaw/extensions/agent-wallet/openclaw.plugin.json +1 -1
- package/.openclaw/extensions/agent-wallet/package.json +1 -1
- package/CHANGELOG.md +58 -0
- package/RELEASING.md +23 -4
- package/VERSION +1 -1
- package/agent-wallet/agent_wallet/__init__.py +1 -1
- package/agent-wallet/agent_wallet/boot_key_migration.py +10 -2
- package/agent-wallet/agent_wallet/boot_key_recovery.py +6 -4
- package/agent-wallet/agent_wallet/config.py +125 -23
- package/agent-wallet/agent_wallet/encrypted_storage.py +85 -0
- package/agent-wallet/agent_wallet/evm_user_wallets.py +116 -23
- package/agent-wallet/agent_wallet/keystore.py +82 -2
- package/agent-wallet/agent_wallet/sealed_keys.py +19 -18
- package/agent-wallet/agent_wallet/user_wallets.py +6 -5
- package/agent-wallet/openclaw.plugin.json +1 -1
- package/agent-wallet/pyproject.toml +1 -1
- package/agent-wallet/scripts/bootstrap_openclaw_evm.py +8 -3
- package/agent-wallet/scripts/install_agent_wallet.py +4 -1
- package/bin/openclaw-agent-wallet.mjs +291 -93
- package/claude-code/plugins/agent-wallet/.claude-plugin/plugin.json +1 -1
- package/codex/plugins/agent-wallet/.codex-plugin/plugin.json +1 -1
- package/hermes/plugins/agent_wallet/plugin.yaml +1 -1
- package/package.json +2 -1
- package/wdk-btc-wallet/package.json +1 -1
- package/wdk-evm-wallet/package.json +3 -2
- package/wdk-evm-wallet/src/config.js +24 -0
- package/wdk-evm-wallet/src/server.js +2 -0
|
@@ -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,12 +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
|
-
const
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
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;
|
|
1245
1366
|
if (existingBootKey) {
|
|
1246
1367
|
env.AGENT_WALLET_BOOT_KEY = existingBootKey;
|
|
1247
1368
|
}
|
|
@@ -1261,6 +1382,7 @@ function buildInstallerEnv(args) {
|
|
|
1261
1382
|
if (!sealedKeysExist && shouldGenerateSecrets && !env.AGENT_WALLET_BOOT_KEY) {
|
|
1262
1383
|
generated.AGENT_WALLET_BOOT_KEY = token();
|
|
1263
1384
|
env.AGENT_WALLET_BOOT_KEY = generated.AGENT_WALLET_BOOT_KEY;
|
|
1385
|
+
bootKeySource = "generated";
|
|
1264
1386
|
}
|
|
1265
1387
|
if (!sealedKeysExist && shouldGenerateSecrets && !env.AGENT_WALLET_MASTER_KEY) {
|
|
1266
1388
|
generated.AGENT_WALLET_MASTER_KEY = token();
|
|
@@ -1270,7 +1392,7 @@ function buildInstallerEnv(args) {
|
|
|
1270
1392
|
generated.AGENT_WALLET_APPROVAL_SECRET = token();
|
|
1271
1393
|
env.AGENT_WALLET_APPROVAL_SECRET = generated.AGENT_WALLET_APPROVAL_SECRET;
|
|
1272
1394
|
}
|
|
1273
|
-
return { env, generated };
|
|
1395
|
+
return { env, generated, bootKeySource };
|
|
1274
1396
|
}
|
|
1275
1397
|
|
|
1276
1398
|
function runInstall(args, { commandName = "install" } = {}) {
|
|
@@ -1287,9 +1409,11 @@ function runInstall(args, { commandName = "install" } = {}) {
|
|
|
1287
1409
|
const previousPath = previousRuntimePath();
|
|
1288
1410
|
const installerArgs = withoutCliOnlyArgs(args);
|
|
1289
1411
|
const dryRun = hasFlag(args, "--dry-run");
|
|
1412
|
+
const stagingRoot = !explicitRuntimeRoot && !dryRun ? stagingRootFor(packageVersion) : null;
|
|
1413
|
+
const installRoot = stagingRoot || releaseRoot;
|
|
1290
1414
|
|
|
1291
1415
|
if (!hasFlag(installerArgs, "--runtime-root")) {
|
|
1292
|
-
installerArgs.push("--runtime-root",
|
|
1416
|
+
installerArgs.push("--runtime-root", installRoot);
|
|
1293
1417
|
}
|
|
1294
1418
|
if (!hasFlag(installerArgs, "--install-from-runtime")) {
|
|
1295
1419
|
installerArgs.push("--install-from-runtime");
|
|
@@ -1302,7 +1426,11 @@ function runInstall(args, { commandName = "install" } = {}) {
|
|
|
1302
1426
|
console.error(error.message);
|
|
1303
1427
|
return 1;
|
|
1304
1428
|
}
|
|
1305
|
-
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
|
+
}
|
|
1306
1434
|
const result = spawnSync("sh", [setupPath, ...installerArgs], {
|
|
1307
1435
|
cwd: packageRoot,
|
|
1308
1436
|
stdio: "inherit",
|
|
@@ -1310,10 +1438,22 @@ function runInstall(args, { commandName = "install" } = {}) {
|
|
|
1310
1438
|
});
|
|
1311
1439
|
|
|
1312
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
|
+
}
|
|
1313
1445
|
console.error(result.error.message);
|
|
1314
1446
|
return 1;
|
|
1315
1447
|
}
|
|
1316
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
|
+
}
|
|
1317
1457
|
return result.status ?? 1;
|
|
1318
1458
|
}
|
|
1319
1459
|
|
|
@@ -1321,34 +1461,21 @@ function runInstall(args, { commandName = "install" } = {}) {
|
|
|
1321
1461
|
return 0;
|
|
1322
1462
|
}
|
|
1323
1463
|
|
|
1324
|
-
const currentTarget = existingRuntimePointerTarget(currentPath);
|
|
1325
|
-
if (currentTarget) {
|
|
1326
|
-
switchSymlink(previousPath, currentTarget);
|
|
1327
|
-
}
|
|
1328
|
-
switchSymlink(currentPath, releaseRoot);
|
|
1329
|
-
|
|
1330
1464
|
// Installs that pass --skip-python-setup may have no venv, so this handshake
|
|
1331
1465
|
// would fail and trigger a spurious rollback; such flows must set
|
|
1332
1466
|
// AGENT_WALLET_VERIFY_DISABLE=1 (verifyRuntime then skips).
|
|
1333
|
-
const
|
|
1467
|
+
const currentTarget = existingRuntimePointerTarget(currentPath);
|
|
1468
|
+
const verification = verifyRuntime(installRoot, env);
|
|
1334
1469
|
if (!verification.ok && !verification.skipped) {
|
|
1335
|
-
const
|
|
1336
|
-
const rolledBack = Boolean(
|
|
1337
|
-
if (rolledBack) {
|
|
1338
|
-
switchSymlink(currentPath, rollbackTarget);
|
|
1339
|
-
}
|
|
1470
|
+
const failedRoot = failStagingRuntime(stagingRoot, verification.error);
|
|
1471
|
+
const rolledBack = Boolean(currentTarget);
|
|
1340
1472
|
const previousVersion = rolledBack
|
|
1341
|
-
? path.basename(
|
|
1473
|
+
? path.basename(currentTarget)
|
|
1342
1474
|
: null;
|
|
1343
1475
|
|
|
1344
1476
|
let human;
|
|
1345
1477
|
let fix;
|
|
1346
1478
|
if (!rolledBack) {
|
|
1347
|
-
// First install / no good fallback — leave no active runtime rather than a
|
|
1348
|
-
// broken one. Deliberately do NOT point `previous` at the broken release,
|
|
1349
|
-
// so a later `rollback` cannot reactivate it; the release stays under
|
|
1350
|
-
// releases/<version> for inspection via `install --version`.
|
|
1351
|
-
removeRuntimePointer(currentPath);
|
|
1352
1479
|
human =
|
|
1353
1480
|
verification.category === "broken_release"
|
|
1354
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.`
|
|
@@ -1377,7 +1504,9 @@ function runInstall(args, { commandName = "install" } = {}) {
|
|
|
1377
1504
|
category: verification.category || "unknown",
|
|
1378
1505
|
error: `runtime verification failed: ${verification.error}`,
|
|
1379
1506
|
rolled_back: rolledBack,
|
|
1507
|
+
switched_current: false,
|
|
1380
1508
|
kept_version: previousVersion,
|
|
1509
|
+
failed_runtime: failedRoot,
|
|
1381
1510
|
current_runtime_target: readLinkOrNull(currentPath),
|
|
1382
1511
|
message: human,
|
|
1383
1512
|
fix,
|
|
@@ -1386,15 +1515,20 @@ function runInstall(args, { commandName = "install" } = {}) {
|
|
|
1386
1515
|
2,
|
|
1387
1516
|
),
|
|
1388
1517
|
);
|
|
1518
|
+
writeUpdateJournal(
|
|
1519
|
+
"failed",
|
|
1520
|
+
{ failed_runtime: failedRoot, error: verification.error, kept_version: previousVersion },
|
|
1521
|
+
env,
|
|
1522
|
+
);
|
|
1389
1523
|
return 1;
|
|
1390
1524
|
}
|
|
1391
1525
|
|
|
1392
1526
|
if (env.AGENT_WALLET_BOOT_KEY) {
|
|
1393
|
-
const envPath = path.join(
|
|
1527
|
+
const envPath = path.join(installRoot, "agent-wallet", ".env");
|
|
1394
1528
|
// Prefer the OS keystore: provision the boot key there and keep the plaintext
|
|
1395
1529
|
// out of the release .env entirely. Only fall back to the legacy .env write when
|
|
1396
1530
|
// no keystore round-trip is possible, so the runtime can still resolve the key.
|
|
1397
|
-
if (provisionBootKeyToKeystore(
|
|
1531
|
+
if (provisionBootKeyToKeystore(installRoot, env, env.AGENT_WALLET_BOOT_KEY)) {
|
|
1398
1532
|
envFileUnset(envPath, ["AGENT_WALLET_BOOT_KEY"]);
|
|
1399
1533
|
} else {
|
|
1400
1534
|
envFileSet(envPath, {
|
|
@@ -1403,7 +1537,32 @@ function runInstall(args, { commandName = "install" } = {}) {
|
|
|
1403
1537
|
}
|
|
1404
1538
|
}
|
|
1405
1539
|
|
|
1406
|
-
|
|
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);
|
|
1407
1566
|
|
|
1408
1567
|
const pythonInfo = activePythonRuntimeInfo(env);
|
|
1409
1568
|
const nodeInfo = activeNodeRuntimeInfo(env)
|
|
@@ -1423,6 +1582,9 @@ function runInstall(args, { commandName = "install" } = {}) {
|
|
|
1423
1582
|
current_runtime: currentPath,
|
|
1424
1583
|
previous_runtime: readLinkOrNull(previousPath),
|
|
1425
1584
|
generated_runtime_secrets: Object.keys(generated),
|
|
1585
|
+
boot_key_source: bootKeySource,
|
|
1586
|
+
staged: Boolean(stagingRoot),
|
|
1587
|
+
release_state: "verified",
|
|
1426
1588
|
integration_refresh: integrationRefresh,
|
|
1427
1589
|
},
|
|
1428
1590
|
null,
|
|
@@ -1864,7 +2026,7 @@ function runHermesInstall(args) {
|
|
|
1864
2026
|
function runCodexInstall(args) {
|
|
1865
2027
|
const codexHome = resolveCodexHome();
|
|
1866
2028
|
const pluginSource = resolveCodexPluginSource();
|
|
1867
|
-
const pinnedEnv =
|
|
2029
|
+
const pinnedEnv = { pinned: false, reason: "plugin source is immutable" };
|
|
1868
2030
|
const pluginRoot = resolveCodexPluginInstallRoot();
|
|
1869
2031
|
const pluginTarget = path.join(pluginRoot, "agent-wallet");
|
|
1870
2032
|
const marketplacePath = resolveCodexMarketplacePath();
|
|
@@ -1986,10 +2148,6 @@ function pinHomeIntoMcpFile(mcpPath, env = process.env) {
|
|
|
1986
2148
|
return { pinned: true, openclaw_home: home, path: mcpPath };
|
|
1987
2149
|
}
|
|
1988
2150
|
|
|
1989
|
-
function pinEditorMcpEnv(pluginSource, env = process.env) {
|
|
1990
|
-
return pinHomeIntoMcpFile(path.join(pluginSource, ".mcp.json"), env);
|
|
1991
|
-
}
|
|
1992
|
-
|
|
1993
2151
|
// Claude Code copies the plugin into a version-keyed cache and reads THAT copy,
|
|
1994
2152
|
// so the bundle pin alone is ineffective once a cache exists. Pin every cached
|
|
1995
2153
|
// copy too. Cache root is overridable for tests.
|
|
@@ -2062,8 +2220,7 @@ function ensureClaudeCodeMarketplace(marketplaceDir, pluginSource, force) {
|
|
|
2062
2220
|
|
|
2063
2221
|
function runClaudeCodeInstall(args) {
|
|
2064
2222
|
const pluginSource = resolveClaudeCodePluginSource();
|
|
2065
|
-
const pinnedEnv =
|
|
2066
|
-
const pinnedCache = pinClaudeCacheCopies();
|
|
2223
|
+
const pinnedEnv = { pinned: false, reason: "plugin source is immutable" };
|
|
2067
2224
|
const force = hasFlag(args, "--force");
|
|
2068
2225
|
const skipEnable = hasFlag(args, "--skip-enable");
|
|
2069
2226
|
const claudeBin = commandPath("claude");
|
|
@@ -2125,6 +2282,7 @@ function runClaudeCodeInstall(args) {
|
|
|
2125
2282
|
}
|
|
2126
2283
|
|
|
2127
2284
|
const ok = enable.skipped || enable.ok;
|
|
2285
|
+
const pinnedCache = pinClaudeCacheCopies();
|
|
2128
2286
|
const pluginDirFlagFull = `claude --plugin-dir ${pluginSource}`;
|
|
2129
2287
|
console.log(
|
|
2130
2288
|
JSON.stringify(
|
|
@@ -2149,75 +2307,115 @@ function runClaudeCodeInstall(args) {
|
|
|
2149
2307
|
return enable.skipped || enable.ok ? 0 : 1;
|
|
2150
2308
|
}
|
|
2151
2309
|
|
|
2152
|
-
function
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
}
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
return (
|
|
2162
|
-
fs.existsSync(path.join(resolveCodexPluginInstallRoot(env), "agent-wallet")) ||
|
|
2163
|
-
fs.existsSync(resolveCodexMarketplacePath(env))
|
|
2164
|
-
);
|
|
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
|
+
}
|
|
2165
2319
|
}
|
|
2166
2320
|
|
|
2167
|
-
function
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
)
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
);
|
|
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
|
+
}
|
|
2176
2329
|
}
|
|
2177
2330
|
|
|
2178
|
-
function
|
|
2179
|
-
|
|
2180
|
-
const lines = [];
|
|
2181
|
-
console.log = (...items) => {
|
|
2182
|
-
lines.push(items.join(" "));
|
|
2183
|
-
};
|
|
2331
|
+
function repairRuntimeSymlink(name, linkPath, desiredTarget, env = process.env) {
|
|
2332
|
+
let rawTarget;
|
|
2184
2333
|
try {
|
|
2185
|
-
const
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
ok: code === 0,
|
|
2191
|
-
code,
|
|
2192
|
-
payload: output ? extractTrailingJson(output) : null,
|
|
2193
|
-
error: code === 0 ? "" : output.trim(),
|
|
2194
|
-
};
|
|
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);
|
|
2195
2339
|
} catch (error) {
|
|
2196
|
-
|
|
2197
|
-
name,
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
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" };
|
|
2206
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 };
|
|
2207
2359
|
}
|
|
2208
2360
|
|
|
2209
|
-
function
|
|
2210
|
-
const
|
|
2211
|
-
|
|
2212
|
-
|
|
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");
|
|
2213
2378
|
}
|
|
2214
|
-
|
|
2215
|
-
|
|
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);
|
|
2216
2395
|
}
|
|
2217
|
-
|
|
2396
|
+
|
|
2397
|
+
const codexTarget = path.join(resolveCodexPluginInstallRoot(env), "agent-wallet");
|
|
2398
|
+
if (pathEntryExists(codexTarget)) {
|
|
2218
2399
|
results.push(
|
|
2219
|
-
|
|
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,
|
|
2220
2416
|
);
|
|
2417
|
+
result.cache_pins = pinClaudeCacheCopies(env);
|
|
2418
|
+
results.push(result);
|
|
2221
2419
|
}
|
|
2222
2420
|
return results;
|
|
2223
2421
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent-wallet",
|
|
3
3
|
"displayName": "Agent Wallet",
|
|
4
|
-
"version": "0.1.
|
|
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"
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agentlayer.tech/wallet",
|
|
3
|
-
"version": "0.1.
|
|
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-evm-wallet",
|
|
3
|
-
"version": "0.1.
|
|
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",
|
|
@@ -150,6 +150,27 @@ function ensureLocalAuthToken(tokenPath, configuredToken = "") {
|
|
|
150
150
|
return generated;
|
|
151
151
|
}
|
|
152
152
|
|
|
153
|
+
function ensureInstanceId(instanceIdPath, configuredInstanceId = "") {
|
|
154
|
+
const direct = String(configuredInstanceId ?? "").trim();
|
|
155
|
+
if (direct) {
|
|
156
|
+
return direct;
|
|
157
|
+
}
|
|
158
|
+
fs.mkdirSync(path.dirname(instanceIdPath), { recursive: true, mode: 0o700 });
|
|
159
|
+
try {
|
|
160
|
+
const existing = fs.readFileSync(instanceIdPath, "utf8").trim();
|
|
161
|
+
if (existing) {
|
|
162
|
+
fs.chmodSync(instanceIdPath, 0o600);
|
|
163
|
+
return existing;
|
|
164
|
+
}
|
|
165
|
+
} catch {
|
|
166
|
+
// Generate a stable install identity below.
|
|
167
|
+
}
|
|
168
|
+
const generated = crypto.randomUUID();
|
|
169
|
+
fs.writeFileSync(instanceIdPath, generated + "\n", { encoding: "utf8", mode: 0o600 });
|
|
170
|
+
fs.chmodSync(instanceIdPath, 0o600);
|
|
171
|
+
return generated;
|
|
172
|
+
}
|
|
173
|
+
|
|
153
174
|
function normalizeNetworkKey(value) {
|
|
154
175
|
const normalized = String(value ?? "").trim().toLowerCase();
|
|
155
176
|
const aliases = {
|
|
@@ -201,6 +222,8 @@ export function loadConfig(env = process.env) {
|
|
|
201
222
|
const authTokenPath =
|
|
202
223
|
String(env.WDK_EVM_LOCAL_TOKEN_PATH ?? "").trim() ||
|
|
203
224
|
path.join(openClawHome, "wdk-evm-wallet", "local-auth-token");
|
|
225
|
+
const instanceIdPath =
|
|
226
|
+
String(env.WDK_EVM_INSTANCE_ID_PATH ?? "").trim() || path.join(dataDir, "instance-id");
|
|
204
227
|
const providerMode = parseProviderMode(env.WDK_EVM_RPC_PROVIDER_MODE, "gateway");
|
|
205
228
|
const providerGatewayUrl =
|
|
206
229
|
String(env.PROVIDER_GATEWAY_URL ?? "").trim() || DEFAULT_PROVIDER_GATEWAY_URL;
|
|
@@ -294,6 +317,7 @@ export function loadConfig(env = process.env) {
|
|
|
294
317
|
network,
|
|
295
318
|
openClawHome,
|
|
296
319
|
dataDir,
|
|
320
|
+
instanceId: ensureInstanceId(instanceIdPath, env.WDK_EVM_INSTANCE_ID),
|
|
297
321
|
authRequired: true,
|
|
298
322
|
authTokenPath,
|
|
299
323
|
authToken: ensureLocalAuthToken(authTokenPath, env.WDK_EVM_LOCAL_TOKEN),
|