@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.
- package/.openclaw/extensions/agent-wallet/openclaw.plugin.json +1 -1
- package/.openclaw/extensions/agent-wallet/package.json +1 -1
- package/CHANGELOG.md +72 -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 +738 -88
- package/claude-code/plugins/agent-wallet/.claude-plugin/plugin.json +1 -1
- package/claude-code/plugins/agent-wallet/.mcp.json +1 -2
- 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,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
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
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",
|
|
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
|
|
1548
|
+
const currentTarget = existingRuntimePointerTarget(currentPath);
|
|
1549
|
+
const verification = verifyRuntime(installRoot, env);
|
|
1337
1550
|
if (!verification.ok && !verification.skipped) {
|
|
1338
|
-
const
|
|
1339
|
-
const rolledBack = Boolean(
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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
|
|
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
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
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
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
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
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
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
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
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
|
|
2182
|
-
const
|
|
2183
|
-
|
|
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
|
-
|
|
2189
|
-
|
|
2190
|
-
return
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
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
|
-
|
|
2201
|
-
attempted: true,
|
|
2671
|
+
attempted: false,
|
|
2202
2672
|
ok: false,
|
|
2203
|
-
|
|
2204
|
-
|
|
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
|
|
2213
|
-
const
|
|
2214
|
-
if (
|
|
2215
|
-
|
|
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
|
-
|
|
2218
|
-
|
|
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
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
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
|
|