@agentlayer.tech/wallet 0.1.74 → 0.1.76
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 +26 -0
- package/VERSION +1 -1
- package/agent-wallet/AGENTS.md +1 -0
- package/agent-wallet/UPGRADE_COMPATIBILITY.md +36 -0
- package/agent-wallet/agent_wallet/__init__.py +1 -1
- package/agent-wallet/agent_wallet/boot_key_recovery.py +10 -1
- package/agent-wallet/openclaw.plugin.json +1 -1
- package/agent-wallet/pyproject.toml +1 -1
- package/bin/lib/boot-key.mjs +185 -0
- package/bin/lib/integrations.mjs +424 -0
- package/bin/lib/update-transaction.mjs +235 -0
- package/bin/openclaw-agent-wallet.mjs +351 -191
- 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 +1 -1
|
@@ -6,6 +6,9 @@ import fs from "node:fs";
|
|
|
6
6
|
import os from "node:os";
|
|
7
7
|
import path from "node:path";
|
|
8
8
|
import { fileURLToPath } from "node:url";
|
|
9
|
+
import { createBootKeyManager } from "./lib/boot-key.mjs";
|
|
10
|
+
import { createHostIntegrationManager, createIntegrationManager } from "./lib/integrations.mjs";
|
|
11
|
+
import { createUpdateTransactionManager } from "./lib/update-transaction.mjs";
|
|
9
12
|
|
|
10
13
|
const cliPath = fileURLToPath(import.meta.url);
|
|
11
14
|
const packageRoot = path.resolve(path.dirname(cliPath), "..");
|
|
@@ -388,20 +391,85 @@ function stagingRootFor(version, env = process.env) {
|
|
|
388
391
|
);
|
|
389
392
|
}
|
|
390
393
|
|
|
391
|
-
function
|
|
392
|
-
return
|
|
394
|
+
function updateTransactions(env = process.env) {
|
|
395
|
+
return createUpdateTransactionManager({
|
|
396
|
+
runtimeBase: resolveRuntimeBase(env),
|
|
397
|
+
packageVersion,
|
|
398
|
+
env,
|
|
399
|
+
});
|
|
393
400
|
}
|
|
394
401
|
|
|
395
|
-
function
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
+
function acquireUpdateLock(env = process.env) {
|
|
403
|
+
return updateTransactions(env).acquireLock();
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
function releaseUpdateLock(lock) {
|
|
407
|
+
return createUpdateTransactionManager({
|
|
408
|
+
runtimeBase: path.dirname(lock.path),
|
|
409
|
+
packageVersion,
|
|
410
|
+
}).releaseLock(lock);
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
function holdUpdateLockForTest(env = process.env) {
|
|
414
|
+
updateTransactions(env).holdLockForTest();
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
function readUpdateJournal(env = process.env) {
|
|
418
|
+
return updateTransactions(env).readJournal();
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
function integrations(env = process.env) {
|
|
422
|
+
return createIntegrationManager({
|
|
423
|
+
runtimeBase: resolveRuntimeBase(env),
|
|
424
|
+
packageVersion,
|
|
425
|
+
activeVersion: () => activeVersion(env),
|
|
426
|
+
});
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
function recordManagedIntegration(name, details = {}, env = process.env) {
|
|
430
|
+
return integrations(env).record(name, details);
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
function integrationSyncStatus(env = process.env) {
|
|
434
|
+
return integrations(env).status();
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
function hostIntegrations(env = process.env) {
|
|
438
|
+
return createHostIntegrationManager({
|
|
439
|
+
env,
|
|
440
|
+
packageRoot,
|
|
441
|
+
registry: integrations(env),
|
|
442
|
+
currentRuntimePath: currentRuntimePath(env),
|
|
443
|
+
openclawHome: resolveOpenclawHome(env),
|
|
444
|
+
hermesHome: resolveHermesHome(env),
|
|
445
|
+
codexHome: resolveCodexHome(env),
|
|
446
|
+
codexPluginRoot: resolveCodexPluginInstallRoot(env),
|
|
447
|
+
codexMarketplacePath: resolveCodexMarketplacePath(env),
|
|
448
|
+
claudeMarketplaceDir: resolveClaudeCodeMarketplaceDir(env),
|
|
449
|
+
claudeMarketplaceName: CLAUDE_CODE_MARKETPLACE_NAME,
|
|
450
|
+
expandHome,
|
|
451
|
+
resolveVenvPython,
|
|
452
|
+
commandPath,
|
|
453
|
+
repairRuntimeSymlink,
|
|
454
|
+
repairHermesEnv,
|
|
455
|
+
ensureCodexMarketplaceEntry,
|
|
456
|
+
ensureClaudeCodeMarketplace,
|
|
457
|
+
pinClaudeCacheCopies,
|
|
402
458
|
});
|
|
403
459
|
}
|
|
404
460
|
|
|
461
|
+
function writeUpdateJournal(state, details = {}, env = process.env) {
|
|
462
|
+
updateTransactions(env).writeJournal(state, details);
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
function recoverInterruptedUpdate(env = process.env) {
|
|
466
|
+
return updateTransactions(env).recover();
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
function updateRecoveryStatus(env = process.env) {
|
|
470
|
+
return updateTransactions(env).status();
|
|
471
|
+
}
|
|
472
|
+
|
|
405
473
|
function writeReleaseState(runtimeRoot, state, details = {}) {
|
|
406
474
|
writeJsonFile(path.join(runtimeRoot, ".agent-wallet-release.json"), {
|
|
407
475
|
schema_version: 1,
|
|
@@ -423,14 +491,14 @@ function failStagingRuntime(stagingRoot, error) {
|
|
|
423
491
|
return failedRoot;
|
|
424
492
|
}
|
|
425
493
|
|
|
426
|
-
function commitStagedRuntime(stagingRoot, releaseRoot) {
|
|
494
|
+
function commitStagedRuntime(stagingRoot, releaseRoot, replacedRoot = null, env = process.env) {
|
|
427
495
|
if (!stagingRoot) return null;
|
|
428
|
-
let replacedRoot = null;
|
|
429
496
|
if (fs.existsSync(releaseRoot)) {
|
|
430
|
-
replacedRoot
|
|
497
|
+
replacedRoot ||= uniquePathWithSuffix(
|
|
431
498
|
path.join(path.dirname(releaseRoot), `${path.basename(releaseRoot)}-replaced`),
|
|
432
499
|
);
|
|
433
500
|
fs.renameSync(releaseRoot, replacedRoot);
|
|
501
|
+
if (env.AGENT_WALLET_TEST_EXIT_AFTER_RELEASE_RENAME === "1") process.exit(86);
|
|
434
502
|
}
|
|
435
503
|
try {
|
|
436
504
|
fs.renameSync(stagingRoot, releaseRoot);
|
|
@@ -944,146 +1012,52 @@ function writeJsonFile(pathname, value) {
|
|
|
944
1012
|
fs.writeFileSync(pathname, `${JSON.stringify(value, null, 2)}\n`);
|
|
945
1013
|
}
|
|
946
1014
|
|
|
947
|
-
function
|
|
948
|
-
const currentRoot = resolvedCurrentRuntimeRoot(env);
|
|
949
|
-
if (!currentRoot) return "";
|
|
950
|
-
return readEnvFile(path.join(currentRoot, "agent-wallet", ".env")).AGENT_WALLET_BOOT_KEY || "";
|
|
951
|
-
}
|
|
952
|
-
|
|
953
|
-
function readTextIfExists(pathname) {
|
|
954
|
-
try {
|
|
955
|
-
return fs.readFileSync(pathname, "utf8");
|
|
956
|
-
} catch (error) {
|
|
957
|
-
if (error?.code === "ENOENT") return "";
|
|
958
|
-
throw error;
|
|
959
|
-
}
|
|
960
|
-
}
|
|
961
|
-
|
|
962
|
-
function writeSecretFile(pathname, value) {
|
|
1015
|
+
function writeJsonFileAtomic(pathname, value, mode = 0o600) {
|
|
963
1016
|
fs.mkdirSync(path.dirname(pathname), { recursive: true });
|
|
964
|
-
|
|
1017
|
+
const tempPath = `${pathname}.tmp-${process.pid}-${Date.now()}`;
|
|
965
1018
|
try {
|
|
966
|
-
fs.
|
|
967
|
-
|
|
968
|
-
|
|
1019
|
+
fs.writeFileSync(tempPath, `${JSON.stringify(value, null, 2)}\n`, { mode });
|
|
1020
|
+
fs.renameSync(tempPath, pathname);
|
|
1021
|
+
fs.chmodSync(pathname, mode);
|
|
1022
|
+
} finally {
|
|
1023
|
+
try {
|
|
1024
|
+
fs.rmSync(tempPath, { force: true });
|
|
1025
|
+
} catch {
|
|
1026
|
+
// ignored
|
|
1027
|
+
}
|
|
969
1028
|
}
|
|
970
1029
|
}
|
|
971
1030
|
|
|
972
|
-
function
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
1031
|
+
function bootKeys(env = process.env) {
|
|
1032
|
+
return createBootKeyManager({
|
|
1033
|
+
runtimeBase: resolveRuntimeBase(env),
|
|
1034
|
+
openclawHome: resolveOpenclawHome(env),
|
|
1035
|
+
currentRuntimeRoot: () => resolvedCurrentRuntimeRoot(env),
|
|
1036
|
+
resolveVenvPython,
|
|
1037
|
+
expandHome,
|
|
1038
|
+
bridgeTimeoutMs: positiveIntEnv(
|
|
1039
|
+
"AGENT_WALLET_KEYSTORE_BRIDGE_TIMEOUT_MS",
|
|
1040
|
+
KEYSTORE_BRIDGE_TIMEOUT_MS,
|
|
1041
|
+
env,
|
|
1042
|
+
),
|
|
1043
|
+
env,
|
|
1044
|
+
});
|
|
980
1045
|
}
|
|
981
1046
|
|
|
982
1047
|
function ensureBootKeyFile(env = process.env) {
|
|
983
|
-
|
|
984
|
-
const keyFile = configuredFile ? path.resolve(expandHome(configuredFile)) : defaultBootKeyFile(env);
|
|
985
|
-
const existing = readTextIfExists(keyFile).trim();
|
|
986
|
-
if (existing) {
|
|
987
|
-
return { path: keyFile, status: "existing" };
|
|
988
|
-
}
|
|
989
|
-
const bootKey = String(env.AGENT_WALLET_BOOT_KEY || "").trim() || resolveBootKeyFromFile(env) || currentBootKey(env);
|
|
990
|
-
if (!bootKey) {
|
|
991
|
-
return { path: keyFile, status: "missing" };
|
|
992
|
-
}
|
|
993
|
-
writeSecretFile(keyFile, bootKey);
|
|
994
|
-
return { path: keyFile, status: "created" };
|
|
995
|
-
}
|
|
996
|
-
|
|
997
|
-
// Read the boot key from the OS keystore via the current runtime's Python
|
|
998
|
-
// (best-effort, "" on any failure). Lets a re-install after the runtime migration
|
|
999
|
-
// — which moves the key into the keystore and deletes every plaintext copy — still
|
|
1000
|
-
// resolve the existing boot key instead of refusing to touch sealed secrets.
|
|
1001
|
-
function readBootKeyFromKeystore(env = process.env) {
|
|
1002
|
-
const runtimeRoot = resolvedCurrentRuntimeRoot(env);
|
|
1003
|
-
if (!runtimeRoot) return "";
|
|
1004
|
-
const py = resolveVenvPython(runtimeRoot);
|
|
1005
|
-
if (!py) return "";
|
|
1006
|
-
try {
|
|
1007
|
-
const res = spawnSync(
|
|
1008
|
-
py,
|
|
1009
|
-
["-c", "from agent_wallet.config import read_boot_key_from_keystore as r; print(r())"],
|
|
1010
|
-
{
|
|
1011
|
-
cwd: path.join(runtimeRoot, "agent-wallet"),
|
|
1012
|
-
encoding: "utf8",
|
|
1013
|
-
timeout: positiveIntEnv("AGENT_WALLET_KEYSTORE_BRIDGE_TIMEOUT_MS", KEYSTORE_BRIDGE_TIMEOUT_MS, env),
|
|
1014
|
-
env: { ...env, OPENCLAW_HOME: resolveOpenclawHome(env) },
|
|
1015
|
-
},
|
|
1016
|
-
);
|
|
1017
|
-
if ((res.status ?? 1) !== 0) return "";
|
|
1018
|
-
return String(res.stdout || "").trim();
|
|
1019
|
-
} catch {
|
|
1020
|
-
return "";
|
|
1021
|
-
}
|
|
1048
|
+
return bootKeys(env).ensureFile();
|
|
1022
1049
|
}
|
|
1023
1050
|
|
|
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
1051
|
function readBootKeyFromRuntimeResolver(env = process.env) {
|
|
1028
|
-
|
|
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
|
-
}
|
|
1052
|
+
return bootKeys(env).resolveFromRuntime();
|
|
1048
1053
|
}
|
|
1049
1054
|
|
|
1050
1055
|
function resolveLegacyInstallerBootKey(env = process.env) {
|
|
1051
|
-
|
|
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" };
|
|
1056
|
+
return bootKeys(env).resolveLegacy();
|
|
1060
1057
|
}
|
|
1061
1058
|
|
|
1062
|
-
// Provision the boot key into the OS keystore via the freshly installed runtime's
|
|
1063
|
-
// Python. Returns true only when the import stored AND verified the key, so the
|
|
1064
|
-
// caller may safely drop the plaintext .env copy. Best-effort: false on any failure
|
|
1065
|
-
// (e.g. no usable keystore), in which case the caller keeps the legacy .env write.
|
|
1066
1059
|
function provisionBootKeyToKeystore(releaseRoot, env, bootKey) {
|
|
1067
|
-
|
|
1068
|
-
if (!key) return false;
|
|
1069
|
-
const py = resolveVenvPython(releaseRoot);
|
|
1070
|
-
if (!py) return false;
|
|
1071
|
-
try {
|
|
1072
|
-
const res = spawnSync(
|
|
1073
|
-
py,
|
|
1074
|
-
["-m", "agent_wallet.openclaw_cli", "boot-key-import", "--key-stdin"],
|
|
1075
|
-
{
|
|
1076
|
-
cwd: path.join(releaseRoot, "agent-wallet"),
|
|
1077
|
-
input: key,
|
|
1078
|
-
encoding: "utf8",
|
|
1079
|
-
timeout: positiveIntEnv("AGENT_WALLET_KEYSTORE_BRIDGE_TIMEOUT_MS", KEYSTORE_BRIDGE_TIMEOUT_MS, env),
|
|
1080
|
-
env: { ...env, OPENCLAW_HOME: resolveOpenclawHome(env) },
|
|
1081
|
-
},
|
|
1082
|
-
);
|
|
1083
|
-
return (res.status ?? 1) === 0;
|
|
1084
|
-
} catch {
|
|
1085
|
-
return false;
|
|
1086
|
-
}
|
|
1060
|
+
return bootKeys(env).provision(releaseRoot, bootKey);
|
|
1087
1061
|
}
|
|
1088
1062
|
|
|
1089
1063
|
function resolveVenvPython(releaseRoot) {
|
|
@@ -1161,6 +1135,10 @@ function verifyRuntime(releaseRoot, env = process.env) {
|
|
|
1161
1135
|
};
|
|
1162
1136
|
}
|
|
1163
1137
|
|
|
1138
|
+
function verifyBootKeyWithRuntime(releaseRoot, env = process.env) {
|
|
1139
|
+
return bootKeys(env).verifyWithRuntime(releaseRoot);
|
|
1140
|
+
}
|
|
1141
|
+
|
|
1164
1142
|
function resolveEditorServerChecks(env = process.env) {
|
|
1165
1143
|
const checks = [];
|
|
1166
1144
|
// Claude Code's launcher (run_mcp.sh) falls back to the runtime codex
|
|
@@ -1301,10 +1279,30 @@ function runDoctor(args = []) {
|
|
|
1301
1279
|
fix: rsync.in_sync === false ? "npm run release:local" : "",
|
|
1302
1280
|
});
|
|
1303
1281
|
|
|
1282
|
+
const integrationSync = integrationSyncStatus(env);
|
|
1283
|
+
checks.push({
|
|
1284
|
+
name: "framework_integrations_in_sync",
|
|
1285
|
+
ok: true,
|
|
1286
|
+
in_sync: integrationSync.in_sync,
|
|
1287
|
+
integrations: integrationSync.integrations,
|
|
1288
|
+
error: "",
|
|
1289
|
+
fix: integrationSync.in_sync ? "" : "wallet update --yes",
|
|
1290
|
+
});
|
|
1291
|
+
|
|
1292
|
+
const recoveryStatus = updateRecoveryStatus(env);
|
|
1293
|
+
checks.push({
|
|
1294
|
+
name: "update_recovery_state",
|
|
1295
|
+
ok: true,
|
|
1296
|
+
...recoveryStatus,
|
|
1297
|
+
error: "",
|
|
1298
|
+
fix: recoveryStatus.needs_recovery ? "wallet install --yes" : "",
|
|
1299
|
+
});
|
|
1300
|
+
|
|
1304
1301
|
const ok = checks.every((c) => c.ok);
|
|
1305
1302
|
console.log(
|
|
1306
1303
|
JSON.stringify(
|
|
1307
1304
|
{
|
|
1305
|
+
schema_version: 1,
|
|
1308
1306
|
ok,
|
|
1309
1307
|
package_name: packageJson.name,
|
|
1310
1308
|
package_version: packageVersion,
|
|
@@ -1324,6 +1322,7 @@ function runDoctor(args = []) {
|
|
|
1324
1322
|
|
|
1325
1323
|
function runStatus(args = []) {
|
|
1326
1324
|
const payload = {
|
|
1325
|
+
schema_version: 1,
|
|
1327
1326
|
ok: true,
|
|
1328
1327
|
package_name: packageJson.name,
|
|
1329
1328
|
package_version: packageVersion,
|
|
@@ -1336,6 +1335,8 @@ function runStatus(args = []) {
|
|
|
1336
1335
|
failed_releases: listFailedReleases(),
|
|
1337
1336
|
update_available: computeUpdateAvailability(),
|
|
1338
1337
|
runtime_in_sync: computeRuntimeInSync(),
|
|
1338
|
+
framework_integrations: integrationSyncStatus(),
|
|
1339
|
+
update_recovery: updateRecoveryStatus(),
|
|
1339
1340
|
};
|
|
1340
1341
|
if (hasFlag(args, "--verbose")) {
|
|
1341
1342
|
payload.verbose = true;
|
|
@@ -1353,19 +1354,18 @@ function buildInstallerEnv(args) {
|
|
|
1353
1354
|
const sealedKeysExist = fs.existsSync(sealedKeysPath);
|
|
1354
1355
|
const dryRun = hasFlag(args, "--dry-run");
|
|
1355
1356
|
let bootKeySource = env.AGENT_WALLET_BOOT_KEY ? "environment" : "none";
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
: resolveLegacyInstallerBootKey(env);
|
|
1364
|
-
const existingBootKey = fallback.key;
|
|
1365
|
-
bootKeySource = fallback.source;
|
|
1366
|
-
if (existingBootKey) {
|
|
1367
|
-
env.AGENT_WALLET_BOOT_KEY = existingBootKey;
|
|
1357
|
+
const runtimeResolution = readBootKeyFromRuntimeResolver(env);
|
|
1358
|
+
if (runtimeResolution.supported) {
|
|
1359
|
+
bootKeySource = runtimeResolution.key ? "runtime_verified" : "runtime_rejected";
|
|
1360
|
+
if (runtimeResolution.key) {
|
|
1361
|
+
env.AGENT_WALLET_BOOT_KEY = runtimeResolution.key;
|
|
1362
|
+
} else {
|
|
1363
|
+
delete env.AGENT_WALLET_BOOT_KEY;
|
|
1368
1364
|
}
|
|
1365
|
+
} else if (!env.AGENT_WALLET_BOOT_KEY) {
|
|
1366
|
+
const fallback = resolveLegacyInstallerBootKey(env);
|
|
1367
|
+
bootKeySource = fallback.source;
|
|
1368
|
+
if (fallback.key) env.AGENT_WALLET_BOOT_KEY = fallback.key;
|
|
1369
1369
|
}
|
|
1370
1370
|
|
|
1371
1371
|
const shouldGenerateSecrets =
|
|
@@ -1395,7 +1395,7 @@ function buildInstallerEnv(args) {
|
|
|
1395
1395
|
return { env, generated, bootKeySource };
|
|
1396
1396
|
}
|
|
1397
1397
|
|
|
1398
|
-
function
|
|
1398
|
+
function runInstallUnlocked(args, { commandName = "install" } = {}) {
|
|
1399
1399
|
if (!fs.existsSync(setupPath)) {
|
|
1400
1400
|
console.error(`Missing bundled setup.sh at ${setupPath}`);
|
|
1401
1401
|
return 1;
|
|
@@ -1409,6 +1409,13 @@ function runInstall(args, { commandName = "install" } = {}) {
|
|
|
1409
1409
|
const previousPath = previousRuntimePath();
|
|
1410
1410
|
const installerArgs = withoutCliOnlyArgs(args);
|
|
1411
1411
|
const dryRun = hasFlag(args, "--dry-run");
|
|
1412
|
+
const recovery = dryRun
|
|
1413
|
+
? { attempted: false, ok: true, reason: "dry run" }
|
|
1414
|
+
: recoverInterruptedUpdate(process.env);
|
|
1415
|
+
if (!recovery.ok) {
|
|
1416
|
+
console.error(`Could not recover interrupted update: ${recovery.reason || recovery.error}`);
|
|
1417
|
+
return 1;
|
|
1418
|
+
}
|
|
1412
1419
|
const stagingRoot = !explicitRuntimeRoot && !dryRun ? stagingRootFor(packageVersion) : null;
|
|
1413
1420
|
const installRoot = stagingRoot || releaseRoot;
|
|
1414
1421
|
|
|
@@ -1429,7 +1436,17 @@ function runInstall(args, { commandName = "install" } = {}) {
|
|
|
1429
1436
|
const { env, generated, bootKeySource } = installerEnv;
|
|
1430
1437
|
if (stagingRoot) {
|
|
1431
1438
|
env.OPENCLAW_INSTALL_FINAL_ROOT = releaseRoot;
|
|
1432
|
-
writeUpdateJournal(
|
|
1439
|
+
writeUpdateJournal(
|
|
1440
|
+
"preparing",
|
|
1441
|
+
{
|
|
1442
|
+
transaction_id: crypto.randomUUID(),
|
|
1443
|
+
staging_root: stagingRoot,
|
|
1444
|
+
release_root: releaseRoot,
|
|
1445
|
+
current_target_before: readLinkOrNull(currentPath),
|
|
1446
|
+
previous_target_before: readLinkOrNull(previousPath),
|
|
1447
|
+
},
|
|
1448
|
+
env,
|
|
1449
|
+
);
|
|
1433
1450
|
}
|
|
1434
1451
|
const result = spawnSync("sh", [setupPath, ...installerArgs], {
|
|
1435
1452
|
cwd: packageRoot,
|
|
@@ -1523,6 +1540,34 @@ function runInstall(args, { commandName = "install" } = {}) {
|
|
|
1523
1540
|
return 1;
|
|
1524
1541
|
}
|
|
1525
1542
|
|
|
1543
|
+
const bootKeyVerification = verifyBootKeyWithRuntime(installRoot, env);
|
|
1544
|
+
if (!bootKeyVerification.ok) {
|
|
1545
|
+
const failedRoot = failStagingRuntime(stagingRoot, bootKeyVerification.error);
|
|
1546
|
+
writeUpdateJournal(
|
|
1547
|
+
"failed",
|
|
1548
|
+
{ failed_runtime: failedRoot, error: bootKeyVerification.error },
|
|
1549
|
+
env,
|
|
1550
|
+
);
|
|
1551
|
+
console.error(
|
|
1552
|
+
JSON.stringify(
|
|
1553
|
+
{
|
|
1554
|
+
ok: false,
|
|
1555
|
+
command: commandName,
|
|
1556
|
+
version: packageVersion,
|
|
1557
|
+
category: "boot_key_rejected",
|
|
1558
|
+
error: bootKeyVerification.error,
|
|
1559
|
+
switched_current: false,
|
|
1560
|
+
failed_runtime: failedRoot,
|
|
1561
|
+
current_runtime_target: readLinkOrNull(currentPath),
|
|
1562
|
+
fix: "Remove stale AGENT_WALLET_BOOT_KEY overrides and retry the update.",
|
|
1563
|
+
},
|
|
1564
|
+
null,
|
|
1565
|
+
2,
|
|
1566
|
+
),
|
|
1567
|
+
);
|
|
1568
|
+
return 1;
|
|
1569
|
+
}
|
|
1570
|
+
|
|
1526
1571
|
if (env.AGENT_WALLET_BOOT_KEY) {
|
|
1527
1572
|
const envPath = path.join(installRoot, "agent-wallet", ".env");
|
|
1528
1573
|
// Prefer the OS keystore: provision the boot key there and keep the plaintext
|
|
@@ -1540,11 +1585,32 @@ function runInstall(args, { commandName = "install" } = {}) {
|
|
|
1540
1585
|
writeReleaseState(installRoot, "verified", {
|
|
1541
1586
|
verification_skipped: Boolean(verification.skipped),
|
|
1542
1587
|
});
|
|
1543
|
-
writeUpdateJournal(
|
|
1588
|
+
writeUpdateJournal(
|
|
1589
|
+
"verified",
|
|
1590
|
+
{ ...readUpdateJournal(env), staging_root: stagingRoot, release_root: releaseRoot },
|
|
1591
|
+
env,
|
|
1592
|
+
);
|
|
1544
1593
|
|
|
1545
1594
|
let replacedRoot = null;
|
|
1546
1595
|
try {
|
|
1547
|
-
replacedRoot =
|
|
1596
|
+
replacedRoot = fs.existsSync(releaseRoot)
|
|
1597
|
+
? uniquePathWithSuffix(
|
|
1598
|
+
path.join(path.dirname(releaseRoot), `${path.basename(releaseRoot)}-replaced`),
|
|
1599
|
+
)
|
|
1600
|
+
: null;
|
|
1601
|
+
writeUpdateJournal(
|
|
1602
|
+
"committing",
|
|
1603
|
+
{
|
|
1604
|
+
...readUpdateJournal(env),
|
|
1605
|
+
staging_root: stagingRoot,
|
|
1606
|
+
release_root: releaseRoot,
|
|
1607
|
+
replaced_root: replacedRoot,
|
|
1608
|
+
current_target_before: readLinkOrNull(currentPath),
|
|
1609
|
+
previous_target_before: readLinkOrNull(previousPath),
|
|
1610
|
+
},
|
|
1611
|
+
env,
|
|
1612
|
+
);
|
|
1613
|
+
replacedRoot = commitStagedRuntime(stagingRoot, releaseRoot, replacedRoot, env);
|
|
1548
1614
|
} catch (error) {
|
|
1549
1615
|
const failedRoot = failStagingRuntime(stagingRoot, error.message);
|
|
1550
1616
|
writeUpdateJournal("failed", { failed_runtime: failedRoot, error: error.message }, env);
|
|
@@ -1560,9 +1626,29 @@ function runInstall(args, { commandName = "install" } = {}) {
|
|
|
1560
1626
|
switchSymlink(previousPath, previousTarget);
|
|
1561
1627
|
}
|
|
1562
1628
|
switchSymlink(currentPath, releaseRoot);
|
|
1563
|
-
writeUpdateJournal(
|
|
1629
|
+
writeUpdateJournal(
|
|
1630
|
+
"committed",
|
|
1631
|
+
{ ...readUpdateJournal(env), release_root: releaseRoot, previous_runtime: previousTarget },
|
|
1632
|
+
env,
|
|
1633
|
+
);
|
|
1634
|
+
|
|
1635
|
+
recordManagedIntegration(
|
|
1636
|
+
"openclaw",
|
|
1637
|
+
{
|
|
1638
|
+
config_path: path.resolve(
|
|
1639
|
+
expandHome(parseFlagValue(args, "--config-path") || path.join(resolveOpenclawHome(env), "openclaw.json")),
|
|
1640
|
+
),
|
|
1641
|
+
extension_path: path.join(currentPath, ".openclaw", "extensions", "agent-wallet"),
|
|
1642
|
+
package_root: path.join(currentPath, "agent-wallet"),
|
|
1643
|
+
},
|
|
1644
|
+
env,
|
|
1645
|
+
);
|
|
1564
1646
|
|
|
1565
1647
|
const integrationRefresh = repairInstalledEditorIntegrations(env);
|
|
1648
|
+
const globalCliRefresh = safelyRefreshIntegration(
|
|
1649
|
+
"global-cli",
|
|
1650
|
+
() => refreshGlobalCliIfNeeded(env),
|
|
1651
|
+
);
|
|
1566
1652
|
|
|
1567
1653
|
const pythonInfo = activePythonRuntimeInfo(env);
|
|
1568
1654
|
const nodeInfo = activeNodeRuntimeInfo(env)
|
|
@@ -1585,7 +1671,9 @@ function runInstall(args, { commandName = "install" } = {}) {
|
|
|
1585
1671
|
boot_key_source: bootKeySource,
|
|
1586
1672
|
staged: Boolean(stagingRoot),
|
|
1587
1673
|
release_state: "verified",
|
|
1674
|
+
recovery,
|
|
1588
1675
|
integration_refresh: integrationRefresh,
|
|
1676
|
+
global_cli_refresh: globalCliRefresh,
|
|
1589
1677
|
},
|
|
1590
1678
|
null,
|
|
1591
1679
|
2,
|
|
@@ -1594,6 +1682,34 @@ function runInstall(args, { commandName = "install" } = {}) {
|
|
|
1594
1682
|
return 0;
|
|
1595
1683
|
}
|
|
1596
1684
|
|
|
1685
|
+
function runInstall(args, options = {}) {
|
|
1686
|
+
if (hasFlag(args, "--dry-run")) return runInstallUnlocked(args, options);
|
|
1687
|
+
const lock = acquireUpdateLock(process.env);
|
|
1688
|
+
if (!lock.ok) {
|
|
1689
|
+
console.error(
|
|
1690
|
+
JSON.stringify(
|
|
1691
|
+
{
|
|
1692
|
+
ok: false,
|
|
1693
|
+
category: "update_locked",
|
|
1694
|
+
error: "Another wallet install or update is already running.",
|
|
1695
|
+
lock_path: lock.path,
|
|
1696
|
+
lock_owner: lock.owner,
|
|
1697
|
+
fix: "Wait for the active update to finish, then retry.",
|
|
1698
|
+
},
|
|
1699
|
+
null,
|
|
1700
|
+
2,
|
|
1701
|
+
),
|
|
1702
|
+
);
|
|
1703
|
+
return 1;
|
|
1704
|
+
}
|
|
1705
|
+
try {
|
|
1706
|
+
holdUpdateLockForTest(process.env);
|
|
1707
|
+
return runInstallUnlocked(args, options);
|
|
1708
|
+
} finally {
|
|
1709
|
+
releaseUpdateLock(lock);
|
|
1710
|
+
}
|
|
1711
|
+
}
|
|
1712
|
+
|
|
1597
1713
|
function resolveUpdatePackageSpec(env = process.env) {
|
|
1598
1714
|
const explicit = String(env[UPDATE_PACKAGE_SPEC_ENV] || "").trim();
|
|
1599
1715
|
if (explicit) return explicit;
|
|
@@ -2001,6 +2117,14 @@ function runHermesInstall(args) {
|
|
|
2001
2117
|
}
|
|
2002
2118
|
}
|
|
2003
2119
|
|
|
2120
|
+
recordManagedIntegration("hermes", {
|
|
2121
|
+
hermes_home: hermesHome,
|
|
2122
|
+
plugin_target: pluginTarget,
|
|
2123
|
+
env_path: hermesEnvPath,
|
|
2124
|
+
restart_required: true,
|
|
2125
|
+
...(enable.skipped ? {} : { registration_ok: enable.ok }),
|
|
2126
|
+
});
|
|
2127
|
+
|
|
2004
2128
|
console.log(
|
|
2005
2129
|
JSON.stringify(
|
|
2006
2130
|
{
|
|
@@ -2086,6 +2210,15 @@ function runCodexInstall(args) {
|
|
|
2086
2210
|
}
|
|
2087
2211
|
}
|
|
2088
2212
|
|
|
2213
|
+
recordManagedIntegration("codex", {
|
|
2214
|
+
codex_home: codexHome,
|
|
2215
|
+
plugin_target: pluginTarget,
|
|
2216
|
+
marketplace_path: marketplace.marketplace_path,
|
|
2217
|
+
marketplace_name: marketplace.marketplace_name,
|
|
2218
|
+
restart_required: true,
|
|
2219
|
+
...(add.skipped ? {} : { registration_ok: add.ok }),
|
|
2220
|
+
});
|
|
2221
|
+
|
|
2089
2222
|
console.log(
|
|
2090
2223
|
JSON.stringify(
|
|
2091
2224
|
{
|
|
@@ -2281,6 +2414,16 @@ function runClaudeCodeInstall(args) {
|
|
|
2281
2414
|
}
|
|
2282
2415
|
}
|
|
2283
2416
|
|
|
2417
|
+
recordManagedIntegration("claude-code", {
|
|
2418
|
+
marketplace_dir: marketplaceDir,
|
|
2419
|
+
plugin_target: pluginLink,
|
|
2420
|
+
cache_root: path.resolve(
|
|
2421
|
+
expandHome(process.env.AGENT_WALLET_CLAUDE_CODE_CACHE_ROOT || "~/.claude/plugins/cache"),
|
|
2422
|
+
),
|
|
2423
|
+
restart_required: true,
|
|
2424
|
+
...(enable.skipped ? {} : { registration_ok: enable.ok }),
|
|
2425
|
+
});
|
|
2426
|
+
|
|
2284
2427
|
const ok = enable.skipped || enable.ok;
|
|
2285
2428
|
const pinnedCache = pinClaudeCacheCopies();
|
|
2286
2429
|
const pluginDirFlagFull = `claude --plugin-dir ${pluginSource}`;
|
|
@@ -2328,7 +2471,7 @@ function pathEntryExists(pathname) {
|
|
|
2328
2471
|
}
|
|
2329
2472
|
}
|
|
2330
2473
|
|
|
2331
|
-
function repairRuntimeSymlink(name, linkPath, desiredTarget, env = process.env) {
|
|
2474
|
+
function repairRuntimeSymlink(name, linkPath, desiredTarget, env = process.env, { allowExternal = false } = {}) {
|
|
2332
2475
|
let rawTarget;
|
|
2333
2476
|
try {
|
|
2334
2477
|
const stat = fs.lstatSync(linkPath);
|
|
@@ -2348,7 +2491,7 @@ function repairRuntimeSymlink(name, linkPath, desiredTarget, env = process.env)
|
|
|
2348
2491
|
if (absoluteTarget === path.resolve(desiredTarget) || absoluteTarget.startsWith(`${logicalCurrent}${path.sep}`)) {
|
|
2349
2492
|
return { name, attempted: true, ok: true, repaired: false, reason: "already current" };
|
|
2350
2493
|
}
|
|
2351
|
-
if (!runtimeReleasePath(absoluteTarget, env)) {
|
|
2494
|
+
if (!allowExternal && !runtimeReleasePath(absoluteTarget, env)) {
|
|
2352
2495
|
return { name, attempted: false, ok: true, repaired: false, reason: "external target preserved" };
|
|
2353
2496
|
}
|
|
2354
2497
|
if (!fs.existsSync(desiredTarget)) {
|
|
@@ -2358,8 +2501,7 @@ function repairRuntimeSymlink(name, linkPath, desiredTarget, env = process.env)
|
|
|
2358
2501
|
return { name, attempted: true, ok: true, repaired: true, target: desiredTarget };
|
|
2359
2502
|
}
|
|
2360
2503
|
|
|
2361
|
-
function repairHermesEnv(env = process.env) {
|
|
2362
|
-
const envPath = path.join(resolveHermesHome(env), ".env");
|
|
2504
|
+
function repairHermesEnv(envPath, env = process.env) {
|
|
2363
2505
|
const existing = readEnvFile(envPath);
|
|
2364
2506
|
const repaired = [];
|
|
2365
2507
|
const currentPackage = path.join(currentRuntimePath(env), "agent-wallet");
|
|
@@ -2379,45 +2521,63 @@ function repairHermesEnv(env = process.env) {
|
|
|
2379
2521
|
return repaired;
|
|
2380
2522
|
}
|
|
2381
2523
|
|
|
2382
|
-
function
|
|
2383
|
-
const
|
|
2384
|
-
|
|
2385
|
-
const
|
|
2386
|
-
if (
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
results.push(result);
|
|
2524
|
+
function globalNpmPackageInfo(env = process.env) {
|
|
2525
|
+
const npmBin = commandPath("npm");
|
|
2526
|
+
if (!npmBin) return null;
|
|
2527
|
+
const rootResult = spawnSync(npmBin, ["root", "--global"], { encoding: "utf8", env });
|
|
2528
|
+
if (rootResult.status !== 0) return null;
|
|
2529
|
+
const packageRoot = path.join(rootResult.stdout.trim(), ...packageJson.name.split("/"));
|
|
2530
|
+
try {
|
|
2531
|
+
const manifest = readJsonFile(path.join(packageRoot, "package.json"));
|
|
2532
|
+
if (manifest?.name !== packageJson.name) return null;
|
|
2533
|
+
return { npm_bin: npmBin, package_root: packageRoot, version: manifest.version || null };
|
|
2534
|
+
} catch {
|
|
2535
|
+
return null;
|
|
2395
2536
|
}
|
|
2537
|
+
}
|
|
2396
2538
|
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
"codex",
|
|
2402
|
-
codexTarget,
|
|
2403
|
-
path.join(currentRoot, "codex", "plugins", "agent-wallet"),
|
|
2404
|
-
env,
|
|
2405
|
-
),
|
|
2406
|
-
);
|
|
2539
|
+
function refreshGlobalCliIfNeeded(env = process.env) {
|
|
2540
|
+
const installed = globalNpmPackageInfo(env);
|
|
2541
|
+
if (!installed) {
|
|
2542
|
+
return { attempted: false, ok: true, reason: "global package is not installed" };
|
|
2407
2543
|
}
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
if (pathEntryExists(claudeTarget)) {
|
|
2411
|
-
const result = repairRuntimeSymlink(
|
|
2412
|
-
"claude-code",
|
|
2413
|
-
claudeTarget,
|
|
2414
|
-
path.join(currentRoot, "claude-code", "plugins", "agent-wallet"),
|
|
2415
|
-
env,
|
|
2416
|
-
);
|
|
2417
|
-
result.cache_pins = pinClaudeCacheCopies(env);
|
|
2418
|
-
results.push(result);
|
|
2544
|
+
if (installed.version === packageVersion) {
|
|
2545
|
+
return { attempted: false, ok: true, reason: "already current", version: packageVersion };
|
|
2419
2546
|
}
|
|
2420
|
-
|
|
2547
|
+
const fromNpmCache = path.resolve(packageRoot).split(path.sep).includes("_npx");
|
|
2548
|
+
const forced = env.AGENT_WALLET_FORCE_GLOBAL_CLI_REFRESH === "1";
|
|
2549
|
+
if (!fromNpmCache && !forced) {
|
|
2550
|
+
return {
|
|
2551
|
+
attempted: false,
|
|
2552
|
+
ok: false,
|
|
2553
|
+
reason: "installer is not running from npm exec",
|
|
2554
|
+
installed_version: installed.version,
|
|
2555
|
+
target_version: packageVersion,
|
|
2556
|
+
fix: `npm install --global ${packageJson.name}@${packageVersion}`,
|
|
2557
|
+
};
|
|
2558
|
+
}
|
|
2559
|
+
const packageSpec = `${packageJson.name}@${packageVersion}`;
|
|
2560
|
+
const result = spawnSync(
|
|
2561
|
+
installed.npm_bin,
|
|
2562
|
+
["install", "--global", "--no-audit", "--no-fund", packageSpec],
|
|
2563
|
+
{ encoding: "utf8", env },
|
|
2564
|
+
);
|
|
2565
|
+
return {
|
|
2566
|
+
attempted: true,
|
|
2567
|
+
ok: result.status === 0,
|
|
2568
|
+
previous_version: installed.version,
|
|
2569
|
+
target_version: packageVersion,
|
|
2570
|
+
error: result.status === 0 ? "" : (result.stderr || result.stdout || "").trim(),
|
|
2571
|
+
fix: result.status === 0 ? "" : `npm install --global ${packageSpec}`,
|
|
2572
|
+
};
|
|
2573
|
+
}
|
|
2574
|
+
|
|
2575
|
+
function safelyRefreshIntegration(name, callback) {
|
|
2576
|
+
return integrations().safelyRefresh(name, callback);
|
|
2577
|
+
}
|
|
2578
|
+
|
|
2579
|
+
function repairInstalledEditorIntegrations(env = process.env) {
|
|
2580
|
+
return hostIntegrations(env).refreshAll();
|
|
2421
2581
|
}
|
|
2422
2582
|
|
|
2423
2583
|
const args = process.argv.slice(2);
|