@agentlayer.tech/wallet 0.1.89 → 0.1.91
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/dist/index.js +5 -5
- package/.openclaw/extensions/agent-wallet/index.ts +5 -5
- package/.openclaw/extensions/agent-wallet/openclaw.plugin.json +1 -1
- package/.openclaw/extensions/agent-wallet/package.json +1 -1
- package/CHANGELOG.md +14 -0
- package/README.md +46 -23
- package/VERSION +1 -1
- package/agent-wallet/README.md +26 -1
- package/agent-wallet/UPGRADE_COMPATIBILITY.md +2 -1
- package/agent-wallet/agent_wallet/__init__.py +1 -1
- package/agent-wallet/agent_wallet/autonomous_permissions.py +43 -1
- package/agent-wallet/agent_wallet/openclaw_adapter.py +604 -375
- package/agent-wallet/agent_wallet/providers/x402.py +84 -0
- package/agent-wallet/openclaw.plugin.json +1 -1
- package/agent-wallet/pyproject.toml +1 -1
- package/agent-wallet/scripts/install_agent_wallet.py +197 -11
- package/agent-wallet/scripts/install_openclaw_local_config.py +4 -1
- package/agent-wallet/scripts/install_openclaw_sealed_keys.py +6 -2
- package/bin/lib/host-detection.mjs +236 -0
- package/bin/lib/integrations.mjs +69 -5
- package/bin/openclaw-agent-wallet.mjs +398 -28
- package/claude-code/plugins/agent-wallet/.claude-plugin/plugin.json +1 -1
- package/claude-code/plugins/agent-wallet/README.md +3 -1
- package/claude-code/plugins/agent-wallet/commands/agentlayer-autonomous-approve.md +7 -8
- package/claude-code/plugins/agent-wallet/commands/agentlayer-autonomous-revoke.md +2 -2
- package/claude-code/plugins/agent-wallet/commands/wallet-base.md +38 -15
- package/codex/plugins/agent-wallet/.codex-plugin/plugin.json +1 -1
- package/codex/plugins/agent-wallet/README.md +11 -6
- package/codex/plugins/agent-wallet/skills/wallet-base/SKILL.md +56 -0
- package/hermes/plugins/agent_wallet/plugin.yaml +1 -1
- package/package.json +8 -3
- package/wdk-btc-wallet/package.json +1 -1
- package/wdk-evm-wallet/package.json +1 -1
package/bin/lib/integrations.mjs
CHANGED
|
@@ -81,6 +81,24 @@ export function createIntegrationManager({ runtimeBase, packageVersion, activeVe
|
|
|
81
81
|
return registry.integrations[name];
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
+
function recoverCorruptRegistry() {
|
|
85
|
+
const registry = readRegistry();
|
|
86
|
+
if (!registry.registry_error) {
|
|
87
|
+
return { recovered: false, backup: null };
|
|
88
|
+
}
|
|
89
|
+
const corruptBackup = quarantineCorruptRegistry(registry);
|
|
90
|
+
const recovered = emptyRegistry();
|
|
91
|
+
if (corruptBackup) {
|
|
92
|
+
recovered.recovered_corrupt_registry = path.basename(corruptBackup);
|
|
93
|
+
}
|
|
94
|
+
recovered.updated_at = new Date().toISOString();
|
|
95
|
+
writeJsonAtomic(registryPath, recovered);
|
|
96
|
+
return {
|
|
97
|
+
recovered: true,
|
|
98
|
+
backup: corruptBackup,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
84
102
|
function managed(name) {
|
|
85
103
|
const entry = readRegistry().integrations[name];
|
|
86
104
|
return entry?.managed === true ? entry : null;
|
|
@@ -140,7 +158,16 @@ export function createIntegrationManager({ runtimeBase, packageVersion, activeVe
|
|
|
140
158
|
.filter(Boolean);
|
|
141
159
|
}
|
|
142
160
|
|
|
143
|
-
return {
|
|
161
|
+
return {
|
|
162
|
+
registryPath,
|
|
163
|
+
readRegistry,
|
|
164
|
+
record,
|
|
165
|
+
recoverCorruptRegistry,
|
|
166
|
+
managed,
|
|
167
|
+
status,
|
|
168
|
+
safelyRefresh,
|
|
169
|
+
refreshAll,
|
|
170
|
+
};
|
|
144
171
|
}
|
|
145
172
|
|
|
146
173
|
export function createHostIntegrationManager({
|
|
@@ -190,6 +217,37 @@ export function createHostIntegrationManager({
|
|
|
190
217
|
}
|
|
191
218
|
}
|
|
192
219
|
|
|
220
|
+
function adoptOpenclaw() {
|
|
221
|
+
const configPath = path.join(openclawHome, "openclaw.json");
|
|
222
|
+
let config;
|
|
223
|
+
try {
|
|
224
|
+
config = readJson(configPath);
|
|
225
|
+
} catch {
|
|
226
|
+
return null;
|
|
227
|
+
}
|
|
228
|
+
const walletEntry = config?.plugins?.entries?.["agent-wallet"];
|
|
229
|
+
const packageRoot = walletEntry?.config?.packageRoot;
|
|
230
|
+
const loadPaths = Array.isArray(config?.plugins?.load?.paths)
|
|
231
|
+
? config.plugins.load.paths
|
|
232
|
+
: [];
|
|
233
|
+
const ownedPackage =
|
|
234
|
+
typeof packageRoot === "string" &&
|
|
235
|
+
packageRoot.trim() &&
|
|
236
|
+
runtimeOwnedTarget(packageRoot);
|
|
237
|
+
const ownedExtension = loadPaths.some(
|
|
238
|
+
(item) =>
|
|
239
|
+
String(item || "").replaceAll("\\", "/").endsWith("/.openclaw/extensions/agent-wallet") &&
|
|
240
|
+
runtimeOwnedTarget(String(item)),
|
|
241
|
+
);
|
|
242
|
+
if (!walletEntry || (!ownedPackage && !ownedExtension)) return null;
|
|
243
|
+
return registry.record("openclaw", {
|
|
244
|
+
config_path: configPath,
|
|
245
|
+
extension_path: path.join(currentRuntimePath, ".openclaw", "extensions", "agent-wallet"),
|
|
246
|
+
package_root: path.join(currentRuntimePath, "agent-wallet"),
|
|
247
|
+
adopted_legacy_install: true,
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
|
|
193
251
|
function adoptHermes() {
|
|
194
252
|
const pluginTarget = path.join(hermesHome, "plugins", "agent_wallet");
|
|
195
253
|
const target = symlinkTarget(pluginTarget);
|
|
@@ -268,7 +326,7 @@ export function createHostIntegrationManager({
|
|
|
268
326
|
}
|
|
269
327
|
|
|
270
328
|
function refreshOpenclaw() {
|
|
271
|
-
const entry = registry.managed("openclaw");
|
|
329
|
+
const entry = registry.managed("openclaw") || adoptOpenclaw();
|
|
272
330
|
if (!entry) {
|
|
273
331
|
return { name: "openclaw", attempted: false, ok: true, repaired: false, reason: "not managed" };
|
|
274
332
|
}
|
|
@@ -411,13 +469,19 @@ export function createHostIntegrationManager({
|
|
|
411
469
|
};
|
|
412
470
|
}
|
|
413
471
|
|
|
414
|
-
function refreshAll() {
|
|
415
|
-
|
|
472
|
+
function refreshAll(names = null) {
|
|
473
|
+
const selected = names ? new Set(names) : null;
|
|
474
|
+
const refreshers = {
|
|
416
475
|
openclaw: refreshOpenclaw,
|
|
417
476
|
hermes: refreshHermes,
|
|
418
477
|
codex: refreshCodex,
|
|
419
478
|
"claude-code": refreshClaudeCode,
|
|
420
|
-
}
|
|
479
|
+
};
|
|
480
|
+
return registry.refreshAll(
|
|
481
|
+
Object.fromEntries(
|
|
482
|
+
Object.entries(refreshers).filter(([name]) => !selected || selected.has(name)),
|
|
483
|
+
),
|
|
484
|
+
);
|
|
421
485
|
}
|
|
422
486
|
|
|
423
487
|
return { refreshAll };
|
|
@@ -5,8 +5,14 @@ import crypto from "node:crypto";
|
|
|
5
5
|
import fs from "node:fs";
|
|
6
6
|
import os from "node:os";
|
|
7
7
|
import path from "node:path";
|
|
8
|
+
import readline from "node:readline/promises";
|
|
8
9
|
import { fileURLToPath } from "node:url";
|
|
9
10
|
import { createBootKeyManager } from "./lib/boot-key.mjs";
|
|
11
|
+
import {
|
|
12
|
+
buildInstallPlan,
|
|
13
|
+
detectHosts,
|
|
14
|
+
stripUniversalInstallerArgs,
|
|
15
|
+
} from "./lib/host-detection.mjs";
|
|
10
16
|
import { createHostIntegrationManager, createIntegrationManager } from "./lib/integrations.mjs";
|
|
11
17
|
import { createUpdateTransactionManager } from "./lib/update-transaction.mjs";
|
|
12
18
|
|
|
@@ -33,6 +39,7 @@ function printHelp() {
|
|
|
33
39
|
|
|
34
40
|
Usage:
|
|
35
41
|
openclaw-agent-wallet install [options]
|
|
42
|
+
openclaw-agent-wallet detect [--json]
|
|
36
43
|
openclaw-agent-wallet hermes install [options]
|
|
37
44
|
openclaw-agent-wallet codex install [options]
|
|
38
45
|
openclaw-agent-wallet claude-code install [options]
|
|
@@ -44,12 +51,20 @@ Usage:
|
|
|
44
51
|
|
|
45
52
|
Common install options:
|
|
46
53
|
--yes Generate local runtime secrets when missing.
|
|
54
|
+
--hosts <list> Hosts to install: detected, all, none, or comma-separated names.
|
|
55
|
+
--exclude <list> Detected/managed hosts to leave untouched.
|
|
56
|
+
--runtime-only Install/update the shared runtime without adding host plugins.
|
|
57
|
+
--no-prompt Accept the automatic host selection in interactive terminals.
|
|
47
58
|
--no-auto-secrets Do not generate runtime secrets automatically.
|
|
48
59
|
--backend <backend> solana_local, wdk_btc_local, wdk_evm_local, or none.
|
|
49
60
|
--network <network> devnet, mainnet, base, ethereum, bitcoin, etc.
|
|
61
|
+
--invite <code> Bind a welcome invite to the local Base address.
|
|
50
62
|
|
|
51
63
|
Examples:
|
|
52
64
|
npx @agentlayer.tech/wallet install --yes
|
|
65
|
+
npx @agentlayer.tech/wallet install --yes --hosts codex,claude-code
|
|
66
|
+
npx @agentlayer.tech/wallet detect --json
|
|
67
|
+
npx @agentlayer.tech/wallet install --yes --invite alw_...
|
|
53
68
|
npx @agentlayer.tech/wallet hermes install --yes
|
|
54
69
|
npx @agentlayer.tech/wallet codex install --yes
|
|
55
70
|
npx @agentlayer.tech/wallet claude-code install --yes
|
|
@@ -322,6 +337,32 @@ function runWithCliTelemetry(fn, { startEvent, successEvent, failedEvent, comman
|
|
|
322
337
|
return code;
|
|
323
338
|
}
|
|
324
339
|
|
|
340
|
+
async function runWithCliTelemetryAsync(
|
|
341
|
+
fn,
|
|
342
|
+
{ startEvent, successEvent, failedEvent, commandName, host = "", args = [] },
|
|
343
|
+
) {
|
|
344
|
+
recordCliTelemetry(startEvent, { commandName, host, ok: true, args, flush: false });
|
|
345
|
+
let code;
|
|
346
|
+
try {
|
|
347
|
+
code = await fn();
|
|
348
|
+
} catch (error) {
|
|
349
|
+
recordCliTelemetry(failedEvent, {
|
|
350
|
+
commandName,
|
|
351
|
+
host,
|
|
352
|
+
ok: false,
|
|
353
|
+
args,
|
|
354
|
+
});
|
|
355
|
+
throw error;
|
|
356
|
+
}
|
|
357
|
+
recordCliTelemetry(code === 0 ? successEvent : failedEvent, {
|
|
358
|
+
commandName,
|
|
359
|
+
host,
|
|
360
|
+
ok: code === 0,
|
|
361
|
+
args,
|
|
362
|
+
});
|
|
363
|
+
return code;
|
|
364
|
+
}
|
|
365
|
+
|
|
325
366
|
// Shared with the Python runtime (agent_wallet/update_check.py): the cache lives
|
|
326
367
|
// under OPENCLAW_HOME/agent-wallet-runtime regardless of OPENCLAW_INSTALL_ROOT.
|
|
327
368
|
function updateCheckCachePath(env = process.env) {
|
|
@@ -458,6 +499,134 @@ function hostIntegrations(env = process.env) {
|
|
|
458
499
|
});
|
|
459
500
|
}
|
|
460
501
|
|
|
502
|
+
function managedHostNames(env = process.env) {
|
|
503
|
+
const registry = integrations(env).readRegistry();
|
|
504
|
+
return Object.entries(registry.integrations || {})
|
|
505
|
+
.filter(([, entry]) => entry?.managed === true)
|
|
506
|
+
.map(([name]) => name);
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
function runtimeInstalled(env = process.env) {
|
|
510
|
+
try {
|
|
511
|
+
const stat = fs.lstatSync(currentRuntimePath(env));
|
|
512
|
+
return stat.isSymbolicLink() || stat.isDirectory();
|
|
513
|
+
} catch (error) {
|
|
514
|
+
if (error?.code === "ENOENT") return false;
|
|
515
|
+
throw error;
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
function universalInstallPlan(args, env = process.env) {
|
|
520
|
+
const detections = detectHosts({
|
|
521
|
+
env,
|
|
522
|
+
commandPath,
|
|
523
|
+
});
|
|
524
|
+
const managedHosts = managedHostNames(env);
|
|
525
|
+
const plan = buildInstallPlan({
|
|
526
|
+
args,
|
|
527
|
+
detections: detections.map((entry) => ({
|
|
528
|
+
...entry,
|
|
529
|
+
managed: managedHosts.includes(entry.name),
|
|
530
|
+
})),
|
|
531
|
+
managedHosts,
|
|
532
|
+
runtimeInstalled: runtimeInstalled(env),
|
|
533
|
+
});
|
|
534
|
+
const universalSelectionExplicit =
|
|
535
|
+
hasFlag(args, "--hosts") ||
|
|
536
|
+
hasFlag(args, "--exclude") ||
|
|
537
|
+
hasFlag(args, "--runtime-only") ||
|
|
538
|
+
hasFlag(args, "--managed-only");
|
|
539
|
+
if (
|
|
540
|
+
!plan.runtime_installed_before &&
|
|
541
|
+
!universalSelectionExplicit &&
|
|
542
|
+
hasFlag(args, "--config-path")
|
|
543
|
+
) {
|
|
544
|
+
plan.selected_hosts = ["openclaw"];
|
|
545
|
+
plan.selection_reason = "explicit_openclaw_config";
|
|
546
|
+
}
|
|
547
|
+
return plan;
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
function runDetect(args = [], env = process.env) {
|
|
551
|
+
let plan;
|
|
552
|
+
try {
|
|
553
|
+
plan = universalInstallPlan(args, env);
|
|
554
|
+
} catch (error) {
|
|
555
|
+
console.error(error.message);
|
|
556
|
+
return 2;
|
|
557
|
+
}
|
|
558
|
+
console.log(
|
|
559
|
+
JSON.stringify(
|
|
560
|
+
{
|
|
561
|
+
schema_version: 1,
|
|
562
|
+
ok: true,
|
|
563
|
+
runtime_base: resolveRuntimeBase(env),
|
|
564
|
+
runtime_installed: plan.runtime_installed_before,
|
|
565
|
+
detected_hosts: plan.detected_hosts,
|
|
566
|
+
managed_hosts: plan.managed_hosts,
|
|
567
|
+
default_selected_hosts: plan.selected_hosts,
|
|
568
|
+
selection_reason: plan.selection_reason,
|
|
569
|
+
hosts: plan.detections,
|
|
570
|
+
},
|
|
571
|
+
null,
|
|
572
|
+
2,
|
|
573
|
+
),
|
|
574
|
+
);
|
|
575
|
+
return 0;
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
async function promptForInstallPlan(plan, args) {
|
|
579
|
+
const shouldPrompt =
|
|
580
|
+
!plan.runtime_installed_before &&
|
|
581
|
+
plan.selection_reason === "fresh_install_detected" &&
|
|
582
|
+
!hasFlag(args, "--yes") &&
|
|
583
|
+
!hasFlag(args, "--no-prompt") &&
|
|
584
|
+
process.stdin.isTTY &&
|
|
585
|
+
process.stdout.isTTY;
|
|
586
|
+
if (!shouldPrompt) return plan;
|
|
587
|
+
|
|
588
|
+
const excluded = new Set(plan.excluded_hosts);
|
|
589
|
+
const selected = new Set(plan.selected_hosts);
|
|
590
|
+
const terminal = readline.createInterface({
|
|
591
|
+
input: process.stdin,
|
|
592
|
+
output: process.stdout,
|
|
593
|
+
});
|
|
594
|
+
try {
|
|
595
|
+
console.log("AgentLayer detected these agent frameworks:");
|
|
596
|
+
for (const host of plan.detections.filter((entry) => entry.detected && !excluded.has(entry.name))) {
|
|
597
|
+
const answer = (
|
|
598
|
+
await terminal.question(
|
|
599
|
+
` Install AgentLayer for ${host.display_name}? ${selected.has(host.name) ? "[Y/n]" : "[y/N]"} `,
|
|
600
|
+
)
|
|
601
|
+
).trim().toLowerCase();
|
|
602
|
+
if (answer) {
|
|
603
|
+
if (["y", "yes"].includes(answer)) selected.add(host.name);
|
|
604
|
+
if (["n", "no"].includes(answer)) selected.delete(host.name);
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
} finally {
|
|
608
|
+
terminal.close();
|
|
609
|
+
}
|
|
610
|
+
return {
|
|
611
|
+
...plan,
|
|
612
|
+
selection_reason: "interactive",
|
|
613
|
+
selected_hosts: plan.detections
|
|
614
|
+
.map((entry) => entry.name)
|
|
615
|
+
.filter((name) => selected.has(name)),
|
|
616
|
+
};
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
async function runUniversalInstall(args, options = {}) {
|
|
620
|
+
try {
|
|
621
|
+
const initialPlan = universalInstallPlan(args);
|
|
622
|
+
const installPlan = await promptForInstallPlan(initialPlan, args);
|
|
623
|
+
return runInstall(args, { ...options, installPlan });
|
|
624
|
+
} catch (error) {
|
|
625
|
+
console.error(error.message);
|
|
626
|
+
return 2;
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
|
|
461
630
|
function writeUpdateJournal(state, details = {}, env = process.env) {
|
|
462
631
|
updateTransactions(env).writeJournal(state, details);
|
|
463
632
|
}
|
|
@@ -855,7 +1024,13 @@ function withoutCliOnlyArgs(args) {
|
|
|
855
1024
|
const output = [];
|
|
856
1025
|
for (let index = 0; index < args.length; index += 1) {
|
|
857
1026
|
const value = args[index];
|
|
858
|
-
if (
|
|
1027
|
+
if (
|
|
1028
|
+
value === "--yes" ||
|
|
1029
|
+
value === "--auto-secrets" ||
|
|
1030
|
+
value === "--no-auto-secrets" ||
|
|
1031
|
+
value === "--force" ||
|
|
1032
|
+
value === "--skip-enable"
|
|
1033
|
+
) {
|
|
859
1034
|
continue;
|
|
860
1035
|
}
|
|
861
1036
|
if (value === "--to") {
|
|
@@ -867,7 +1042,7 @@ function withoutCliOnlyArgs(args) {
|
|
|
867
1042
|
}
|
|
868
1043
|
output.push(value);
|
|
869
1044
|
}
|
|
870
|
-
return output;
|
|
1045
|
+
return stripUniversalInstallerArgs(output);
|
|
871
1046
|
}
|
|
872
1047
|
|
|
873
1048
|
function extractTrailingJson(text) {
|
|
@@ -1060,6 +1235,15 @@ function provisionBootKeyToKeystore(releaseRoot, env, bootKey) {
|
|
|
1060
1235
|
return bootKeys(env).provision(releaseRoot, bootKey);
|
|
1061
1236
|
}
|
|
1062
1237
|
|
|
1238
|
+
function printMacOSKeychainNotice(env = process.env) {
|
|
1239
|
+
if (process.platform !== "darwin") return;
|
|
1240
|
+
const preference = String(env.AGENT_WALLET_KEYSTORE_BACKEND || "auto").trim().toLowerCase();
|
|
1241
|
+
if (["plain", "plaintext", "plaintext-file", "file"].includes(preference)) return;
|
|
1242
|
+
console.error(
|
|
1243
|
+
"Security notice: AgentLayer uses your macOS login Keychain for the wallet boot key by default. macOS may ask for Keychain access; approve it only if you initiated this install or update.",
|
|
1244
|
+
);
|
|
1245
|
+
}
|
|
1246
|
+
|
|
1063
1247
|
function resolveVenvPython(releaseRoot) {
|
|
1064
1248
|
const candidates = [
|
|
1065
1249
|
path.join(releaseRoot, "agent-wallet", ".venv", "bin", "python"),
|
|
@@ -1395,7 +1579,158 @@ function buildInstallerEnv(args) {
|
|
|
1395
1579
|
return { env, generated, bootKeySource };
|
|
1396
1580
|
}
|
|
1397
1581
|
|
|
1398
|
-
function
|
|
1582
|
+
function openclawHostCommandArgs(args, env = process.env) {
|
|
1583
|
+
const runtimeRoot = currentRuntimePath(env);
|
|
1584
|
+
const walletRoot = path.join(runtimeRoot, "agent-wallet");
|
|
1585
|
+
const pythonBin = resolveVenvPython(runtimeRoot) || resolveAgentWalletPython(walletRoot);
|
|
1586
|
+
const commandArgs = [
|
|
1587
|
+
path.join(walletRoot, "scripts", "install_openclaw_local_config.py"),
|
|
1588
|
+
"--config-path",
|
|
1589
|
+
path.resolve(
|
|
1590
|
+
expandHome(parseFlagValue(args, "--config-path") || path.join(resolveOpenclawHome(env), "openclaw.json")),
|
|
1591
|
+
),
|
|
1592
|
+
"--plugin-id",
|
|
1593
|
+
parseFlagValue(args, "--plugin-id") || "agent-wallet",
|
|
1594
|
+
"--user-id",
|
|
1595
|
+
parseFlagValue(args, "--user-id") || "",
|
|
1596
|
+
"--backend",
|
|
1597
|
+
parseFlagValue(args, "--backend") || "solana_local",
|
|
1598
|
+
"--network",
|
|
1599
|
+
parseFlagValue(args, "--network") || "mainnet",
|
|
1600
|
+
hasFlag(args, "--sign-only") ? "--sign-only" : "--no-sign-only",
|
|
1601
|
+
"--extension-path",
|
|
1602
|
+
path.join(runtimeRoot, ".openclaw", "extensions", "agent-wallet"),
|
|
1603
|
+
"--package-root",
|
|
1604
|
+
walletRoot,
|
|
1605
|
+
"--python-bin",
|
|
1606
|
+
pythonBin,
|
|
1607
|
+
];
|
|
1608
|
+
for (const [flag, targetFlag] of [
|
|
1609
|
+
["--rpc-url", "--rpc-url"],
|
|
1610
|
+
["--rpc-urls", "--rpc-urls"],
|
|
1611
|
+
["--wdk-evm-service-url", "--wdk-evm-service-url"],
|
|
1612
|
+
]) {
|
|
1613
|
+
const value = parseFlagValue(args, flag);
|
|
1614
|
+
if (value) commandArgs.push(targetFlag, value);
|
|
1615
|
+
}
|
|
1616
|
+
return { pythonBin, commandArgs, configPath: commandArgs[2] };
|
|
1617
|
+
}
|
|
1618
|
+
|
|
1619
|
+
function runOpenclawHostInstall(args, env = process.env) {
|
|
1620
|
+
const { pythonBin, commandArgs, configPath } = openclawHostCommandArgs(args, env);
|
|
1621
|
+
const previousConfig = fs.existsSync(configPath) ? fs.readFileSync(configPath) : null;
|
|
1622
|
+
if (previousConfig === null) {
|
|
1623
|
+
writeJsonFileAtomic(configPath, {
|
|
1624
|
+
plugins: { entries: {} },
|
|
1625
|
+
tools: { alsoAllow: [] },
|
|
1626
|
+
});
|
|
1627
|
+
}
|
|
1628
|
+
const result = spawnSync(pythonBin, commandArgs, {
|
|
1629
|
+
cwd: packageRoot,
|
|
1630
|
+
encoding: "utf8",
|
|
1631
|
+
env,
|
|
1632
|
+
});
|
|
1633
|
+
if (result.error || (result.status ?? 1) !== 0) {
|
|
1634
|
+
if (previousConfig === null) {
|
|
1635
|
+
fs.rmSync(configPath, { force: true });
|
|
1636
|
+
} else {
|
|
1637
|
+
fs.writeFileSync(configPath, previousConfig, { mode: 0o600 });
|
|
1638
|
+
}
|
|
1639
|
+
return {
|
|
1640
|
+
name: "openclaw",
|
|
1641
|
+
attempted: true,
|
|
1642
|
+
ok: false,
|
|
1643
|
+
error: result.error?.message || (result.stderr || result.stdout || "").trim(),
|
|
1644
|
+
rolled_back: true,
|
|
1645
|
+
restart_required: false,
|
|
1646
|
+
};
|
|
1647
|
+
}
|
|
1648
|
+
let details = {};
|
|
1649
|
+
try {
|
|
1650
|
+
details = extractTrailingJson(result.stdout);
|
|
1651
|
+
} catch {
|
|
1652
|
+
details = { config_path: configPath };
|
|
1653
|
+
}
|
|
1654
|
+
recordManagedIntegration(
|
|
1655
|
+
"openclaw",
|
|
1656
|
+
{
|
|
1657
|
+
config_path: configPath,
|
|
1658
|
+
extension_path: path.join(currentRuntimePath(env), ".openclaw", "extensions", "agent-wallet"),
|
|
1659
|
+
package_root: path.join(currentRuntimePath(env), "agent-wallet"),
|
|
1660
|
+
restart_required: true,
|
|
1661
|
+
},
|
|
1662
|
+
env,
|
|
1663
|
+
);
|
|
1664
|
+
return {
|
|
1665
|
+
name: "openclaw",
|
|
1666
|
+
attempted: true,
|
|
1667
|
+
ok: true,
|
|
1668
|
+
...details,
|
|
1669
|
+
restart_required: true,
|
|
1670
|
+
};
|
|
1671
|
+
}
|
|
1672
|
+
|
|
1673
|
+
function runEditorHostInstall(name, args, env = process.env) {
|
|
1674
|
+
const binaryName = name === "claude-code" ? "claude" : name;
|
|
1675
|
+
const hostArgs = [
|
|
1676
|
+
name,
|
|
1677
|
+
"install",
|
|
1678
|
+
...(hasFlag(args, "--force") ? ["--force"] : []),
|
|
1679
|
+
...(!commandPath(binaryName) || hasFlag(args, "--skip-enable") ? ["--skip-enable"] : []),
|
|
1680
|
+
];
|
|
1681
|
+
const result = spawnSync(process.execPath, [cliPath, ...hostArgs], {
|
|
1682
|
+
cwd: packageRoot,
|
|
1683
|
+
encoding: "utf8",
|
|
1684
|
+
env,
|
|
1685
|
+
});
|
|
1686
|
+
let details = {};
|
|
1687
|
+
try {
|
|
1688
|
+
details = extractTrailingJson(result.stdout);
|
|
1689
|
+
} catch {
|
|
1690
|
+
// Preserve the host adapter's error below when it did not emit JSON.
|
|
1691
|
+
}
|
|
1692
|
+
return {
|
|
1693
|
+
name,
|
|
1694
|
+
attempted: true,
|
|
1695
|
+
ok: !result.error && (result.status ?? 1) === 0,
|
|
1696
|
+
...details,
|
|
1697
|
+
error:
|
|
1698
|
+
result.error?.message ||
|
|
1699
|
+
((result.status ?? 1) === 0 ? "" : (result.stderr || result.stdout || "").trim()),
|
|
1700
|
+
};
|
|
1701
|
+
}
|
|
1702
|
+
|
|
1703
|
+
function applyHostInstallPlan(plan, args, env = process.env) {
|
|
1704
|
+
const selected = new Set(plan.selected_hosts);
|
|
1705
|
+
const managed = new Set(plan.managed_hosts);
|
|
1706
|
+
const managedSelected = plan.selected_hosts.filter((name) => managed.has(name));
|
|
1707
|
+
const adoptLegacy =
|
|
1708
|
+
plan.runtime_installed_before &&
|
|
1709
|
+
["managed_only", "existing_runtime_managed_only"].includes(plan.selection_reason);
|
|
1710
|
+
const refreshed = repairInstalledEditorIntegrations(
|
|
1711
|
+
env,
|
|
1712
|
+
adoptLegacy ? null : managedSelected,
|
|
1713
|
+
);
|
|
1714
|
+
const installed = [];
|
|
1715
|
+
|
|
1716
|
+
for (const name of plan.selected_hosts) {
|
|
1717
|
+
if (managed.has(name)) continue;
|
|
1718
|
+
installed.push(
|
|
1719
|
+
name === "openclaw"
|
|
1720
|
+
? runOpenclawHostInstall(args, env)
|
|
1721
|
+
: runEditorHostInstall(name, args, env),
|
|
1722
|
+
);
|
|
1723
|
+
}
|
|
1724
|
+
|
|
1725
|
+
return {
|
|
1726
|
+
ok: [...refreshed, ...installed].every((entry) => entry?.ok !== false),
|
|
1727
|
+
selected_hosts: [...selected],
|
|
1728
|
+
refreshed,
|
|
1729
|
+
installed,
|
|
1730
|
+
};
|
|
1731
|
+
}
|
|
1732
|
+
|
|
1733
|
+
function runInstallUnlocked(args, { commandName = "install", installPlan = null } = {}) {
|
|
1399
1734
|
if (!fs.existsSync(setupPath)) {
|
|
1400
1735
|
console.error(`Missing bundled setup.sh at ${setupPath}`);
|
|
1401
1736
|
return 1;
|
|
@@ -1407,8 +1742,10 @@ function runInstallUnlocked(args, { commandName = "install" } = {}) {
|
|
|
1407
1742
|
: releaseRootFor(packageVersion);
|
|
1408
1743
|
const currentPath = currentRuntimePath();
|
|
1409
1744
|
const previousPath = previousRuntimePath();
|
|
1745
|
+
const hostPlan = installPlan || universalInstallPlan(args);
|
|
1410
1746
|
const installerArgs = withoutCliOnlyArgs(args);
|
|
1411
1747
|
const dryRun = hasFlag(args, "--dry-run");
|
|
1748
|
+
if (!dryRun) printMacOSKeychainNotice(process.env);
|
|
1412
1749
|
const recovery = dryRun
|
|
1413
1750
|
? { attempted: false, ok: true, reason: "dry run" }
|
|
1414
1751
|
: recoverInterruptedUpdate(process.env);
|
|
@@ -1425,6 +1762,9 @@ function runInstallUnlocked(args, { commandName = "install" } = {}) {
|
|
|
1425
1762
|
if (!hasFlag(installerArgs, "--install-from-runtime")) {
|
|
1426
1763
|
installerArgs.push("--install-from-runtime");
|
|
1427
1764
|
}
|
|
1765
|
+
if (!hasFlag(installerArgs, "--configure-openclaw") && !hasFlag(installerArgs, "--no-configure-openclaw")) {
|
|
1766
|
+
installerArgs.push("--no-configure-openclaw");
|
|
1767
|
+
}
|
|
1428
1768
|
|
|
1429
1769
|
let installerEnv;
|
|
1430
1770
|
try {
|
|
@@ -1450,7 +1790,8 @@ function runInstallUnlocked(args, { commandName = "install" } = {}) {
|
|
|
1450
1790
|
}
|
|
1451
1791
|
const result = spawnSync("sh", [setupPath, ...installerArgs], {
|
|
1452
1792
|
cwd: packageRoot,
|
|
1453
|
-
stdio: "inherit",
|
|
1793
|
+
stdio: dryRun ? "pipe" : "inherit",
|
|
1794
|
+
encoding: dryRun ? "utf8" : undefined,
|
|
1454
1795
|
env,
|
|
1455
1796
|
});
|
|
1456
1797
|
|
|
@@ -1463,6 +1804,10 @@ function runInstallUnlocked(args, { commandName = "install" } = {}) {
|
|
|
1463
1804
|
return 1;
|
|
1464
1805
|
}
|
|
1465
1806
|
if ((result.status ?? 1) !== 0) {
|
|
1807
|
+
if (dryRun) {
|
|
1808
|
+
if (result.stderr) process.stderr.write(result.stderr);
|
|
1809
|
+
if (result.stdout) process.stderr.write(result.stdout);
|
|
1810
|
+
}
|
|
1466
1811
|
const failedRoot = failStagingRuntime(stagingRoot, `installer exited with ${result.status ?? 1}`);
|
|
1467
1812
|
if (!dryRun) {
|
|
1468
1813
|
writeUpdateJournal(
|
|
@@ -1475,6 +1820,27 @@ function runInstallUnlocked(args, { commandName = "install" } = {}) {
|
|
|
1475
1820
|
}
|
|
1476
1821
|
|
|
1477
1822
|
if (dryRun) {
|
|
1823
|
+
if (result.stderr) process.stderr.write(result.stderr);
|
|
1824
|
+
let installerPlan;
|
|
1825
|
+
try {
|
|
1826
|
+
installerPlan = extractTrailingJson(result.stdout);
|
|
1827
|
+
} catch (error) {
|
|
1828
|
+
if (result.stdout) process.stderr.write(result.stdout);
|
|
1829
|
+
console.error(error.message);
|
|
1830
|
+
return 1;
|
|
1831
|
+
}
|
|
1832
|
+
console.log(
|
|
1833
|
+
JSON.stringify(
|
|
1834
|
+
{
|
|
1835
|
+
...installerPlan,
|
|
1836
|
+
command: commandName,
|
|
1837
|
+
dry_run: true,
|
|
1838
|
+
host_plan: hostPlan,
|
|
1839
|
+
},
|
|
1840
|
+
null,
|
|
1841
|
+
2,
|
|
1842
|
+
),
|
|
1843
|
+
);
|
|
1478
1844
|
return 0;
|
|
1479
1845
|
}
|
|
1480
1846
|
|
|
@@ -1632,19 +1998,9 @@ function runInstallUnlocked(args, { commandName = "install" } = {}) {
|
|
|
1632
1998
|
env,
|
|
1633
1999
|
);
|
|
1634
2000
|
|
|
1635
|
-
|
|
1636
|
-
|
|
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
|
-
);
|
|
1646
|
-
|
|
1647
|
-
const integrationRefresh = repairInstalledEditorIntegrations(env);
|
|
2001
|
+
const integrationRegistryRecovery = integrations(env).recoverCorruptRegistry();
|
|
2002
|
+
const hostInstallation = applyHostInstallPlan(hostPlan, args, env);
|
|
2003
|
+
const hostInstallFailed = hostInstallation.installed.some((entry) => entry?.ok === false);
|
|
1648
2004
|
const globalCliRefresh = safelyRefreshIntegration(
|
|
1649
2005
|
"global-cli",
|
|
1650
2006
|
() => refreshGlobalCliIfNeeded(env),
|
|
@@ -1661,7 +2017,7 @@ function runInstallUnlocked(args, { commandName = "install" } = {}) {
|
|
|
1661
2017
|
console.error(
|
|
1662
2018
|
JSON.stringify(
|
|
1663
2019
|
{
|
|
1664
|
-
ok:
|
|
2020
|
+
ok: !hostInstallFailed,
|
|
1665
2021
|
command: commandName,
|
|
1666
2022
|
version: packageVersion,
|
|
1667
2023
|
runtime_root: releaseRoot,
|
|
@@ -1672,14 +2028,23 @@ function runInstallUnlocked(args, { commandName = "install" } = {}) {
|
|
|
1672
2028
|
staged: Boolean(stagingRoot),
|
|
1673
2029
|
release_state: "verified",
|
|
1674
2030
|
recovery,
|
|
1675
|
-
|
|
2031
|
+
host_plan: hostPlan,
|
|
2032
|
+
host_installation: hostInstallation,
|
|
2033
|
+
integration_registry_recovery: integrationRegistryRecovery,
|
|
2034
|
+
integration_refresh: hostInstallation.refreshed,
|
|
1676
2035
|
global_cli_refresh: globalCliRefresh,
|
|
2036
|
+
...(hostInstallFailed
|
|
2037
|
+
? {
|
|
2038
|
+
category: "host_install_failed",
|
|
2039
|
+
message: "The shared runtime is active, but one or more selected host plugins failed to install.",
|
|
2040
|
+
}
|
|
2041
|
+
: {}),
|
|
1677
2042
|
},
|
|
1678
2043
|
null,
|
|
1679
2044
|
2,
|
|
1680
2045
|
),
|
|
1681
2046
|
);
|
|
1682
|
-
return 0;
|
|
2047
|
+
return hostInstallFailed ? 1 : 0;
|
|
1683
2048
|
}
|
|
1684
2049
|
|
|
1685
2050
|
function runInstall(args, options = {}) {
|
|
@@ -1717,10 +2082,11 @@ function resolveUpdatePackageSpec(env = process.env) {
|
|
|
1717
2082
|
}
|
|
1718
2083
|
|
|
1719
2084
|
function runDelegatedInstallForUpdate(args, { captureOutput = false } = {}) {
|
|
2085
|
+
const installArgs = hasFlag(args, "--managed-only") ? args : [...args, "--managed-only"];
|
|
1720
2086
|
const localCliPath = String(process.env[UPDATE_CLI_PATH_ENV] || "").trim();
|
|
1721
2087
|
if (localCliPath) {
|
|
1722
2088
|
const meta = resolveCliPackageMeta(localCliPath);
|
|
1723
|
-
const result = spawnSync("node", [localCliPath, "install", ...
|
|
2089
|
+
const result = spawnSync("node", [localCliPath, "install", ...installArgs], {
|
|
1724
2090
|
cwd: packageRoot,
|
|
1725
2091
|
stdio: captureOutput ? "pipe" : "inherit",
|
|
1726
2092
|
encoding: captureOutput ? "utf8" : undefined,
|
|
@@ -1743,7 +2109,7 @@ function runDelegatedInstallForUpdate(args, { captureOutput = false } = {}) {
|
|
|
1743
2109
|
const binCommand = primaryBinCommand();
|
|
1744
2110
|
const result = spawnSync(
|
|
1745
2111
|
npmBin,
|
|
1746
|
-
["exec", "--yes", `--package=${packageSpec}`, binCommand, "--", "install", ...
|
|
2112
|
+
["exec", "--yes", `--package=${packageSpec}`, binCommand, "--", "install", ...installArgs],
|
|
1747
2113
|
{
|
|
1748
2114
|
cwd: packageRoot,
|
|
1749
2115
|
stdio: captureOutput ? "pipe" : "inherit",
|
|
@@ -2576,8 +2942,8 @@ function safelyRefreshIntegration(name, callback) {
|
|
|
2576
2942
|
return integrations().safelyRefresh(name, callback);
|
|
2577
2943
|
}
|
|
2578
2944
|
|
|
2579
|
-
function repairInstalledEditorIntegrations(env = process.env) {
|
|
2580
|
-
return hostIntegrations(env).refreshAll();
|
|
2945
|
+
function repairInstalledEditorIntegrations(env = process.env, names = null) {
|
|
2946
|
+
return hostIntegrations(env).refreshAll(names);
|
|
2581
2947
|
}
|
|
2582
2948
|
|
|
2583
2949
|
const args = process.argv.slice(2);
|
|
@@ -2614,11 +2980,15 @@ if (command === "status") {
|
|
|
2614
2980
|
process.exit(runStatus(args.slice(1)));
|
|
2615
2981
|
}
|
|
2616
2982
|
|
|
2983
|
+
if (command === "detect") {
|
|
2984
|
+
process.exit(runDetect(args.slice(1)));
|
|
2985
|
+
}
|
|
2986
|
+
|
|
2617
2987
|
if (command === "install" || command === "setup") {
|
|
2618
2988
|
const commandArgs = args.slice(1);
|
|
2619
2989
|
process.exit(
|
|
2620
|
-
|
|
2621
|
-
() =>
|
|
2990
|
+
await runWithCliTelemetryAsync(
|
|
2991
|
+
() => runUniversalInstall(commandArgs, { commandName: "install" }),
|
|
2622
2992
|
{
|
|
2623
2993
|
startEvent: "install_start",
|
|
2624
2994
|
successEvent: "install_success",
|
|
@@ -2721,8 +3091,8 @@ if (command === "claude-code") {
|
|
|
2721
3091
|
|
|
2722
3092
|
if (command.startsWith("-")) {
|
|
2723
3093
|
process.exit(
|
|
2724
|
-
|
|
2725
|
-
() =>
|
|
3094
|
+
await runWithCliTelemetryAsync(
|
|
3095
|
+
() => runUniversalInstall(args, { commandName: "install" }),
|
|
2726
3096
|
{
|
|
2727
3097
|
startEvent: "install_start",
|
|
2728
3098
|
successEvent: "install_success",
|