@agentlayer.tech/wallet 0.1.32 → 0.1.34
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 +2 -59
- package/.openclaw/extensions/agent-wallet/index.ts +2 -59
- package/.openclaw/extensions/agent-wallet/openclaw.plugin.json +1 -4
- package/.openclaw/extensions/agent-wallet/package.json +1 -1
- package/CHANGELOG.md +66 -0
- package/README.md +2 -5
- package/RELEASING.md +56 -29
- package/VERSION +1 -0
- package/agent-wallet/.env.example +0 -1
- package/agent-wallet/README.md +0 -8
- package/agent-wallet/agent_wallet/__init__.py +5 -0
- package/agent-wallet/agent_wallet/config.py +0 -1
- package/agent-wallet/agent_wallet/openclaw_adapter.py +25 -324
- package/agent-wallet/agent_wallet/openclaw_cli.py +0 -5
- package/agent-wallet/agent_wallet/providers/bags.py +1 -58
- package/agent-wallet/agent_wallet/providers/jupiter.py +1 -64
- package/agent-wallet/agent_wallet/update_check.py +191 -0
- package/agent-wallet/agent_wallet/wallet_layer/base.py +0 -44
- package/agent-wallet/agent_wallet/wallet_layer/solana.py +0 -236
- package/agent-wallet/openclaw.plugin.json +1 -5
- package/agent-wallet/pyproject.toml +1 -1
- package/agent-wallet/skills/wallet-operator/SKILL.md +2 -5
- package/bin/openclaw-agent-wallet.mjs +419 -33
- package/claude-code/plugins/agent-wallet/.claude-plugin/plugin.json +1 -1
- package/claude-code/plugins/agent-wallet/scripts/run_mcp.sh +14 -1
- package/codex/plugins/agent-wallet/.codex-plugin/plugin.json +1 -1
- package/codex/plugins/agent-wallet/scripts/run_mcp.sh +18 -0
- package/codex/plugins/agent-wallet/server.py +39 -5
- package/hermes/plugins/agent_wallet/plugin.yaml +1 -1
- package/package.json +5 -1
- package/scripts/check_release_version.mjs +50 -20
- package/scripts/version_targets.mjs +60 -0
- package/wdk-btc-wallet/package.json +1 -1
- package/wdk-evm-wallet/README.md +3 -3
- package/wdk-evm-wallet/package.json +1 -1
- package/wdk-evm-wallet/src/wdk_evm_wallet.js +17 -2
|
@@ -54,7 +54,12 @@ After a successful install it switches:
|
|
|
54
54
|
|
|
55
55
|
Wallet files and sealed secrets remain under OPENCLAW_HOME and are not replaced
|
|
56
56
|
by updates. The update command fetches the latest published npm package and
|
|
57
|
-
reuses shared dependency snapshots when possible
|
|
57
|
+
reuses shared dependency snapshots when possible.
|
|
58
|
+
|
|
59
|
+
The runtime checks the npm registry in the background (at most once/day) and
|
|
60
|
+
surfaces a notice when a newer version is published — to the agent via the MCP
|
|
61
|
+
server instructions and to you via 'status' / 'doctor'. The check never blocks
|
|
62
|
+
startup. Disable it with AGENT_WALLET_DISABLE_UPDATE_CHECK=1.`);
|
|
58
63
|
}
|
|
59
64
|
|
|
60
65
|
function primaryBinCommand(pkg = packageJson) {
|
|
@@ -90,6 +95,60 @@ function resolveRuntimeBase(env = process.env) {
|
|
|
90
95
|
return path.join(resolveOpenclawHome(env), "agent-wallet-runtime");
|
|
91
96
|
}
|
|
92
97
|
|
|
98
|
+
function updateCheckDisabled(env = process.env) {
|
|
99
|
+
const raw = String(env.AGENT_WALLET_DISABLE_UPDATE_CHECK || "").trim().toLowerCase();
|
|
100
|
+
return ["1", "true", "yes", "on"].includes(raw);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Shared with the Python runtime (agent_wallet/update_check.py): the cache lives
|
|
104
|
+
// under OPENCLAW_HOME/agent-wallet-runtime regardless of OPENCLAW_INSTALL_ROOT.
|
|
105
|
+
function updateCheckCachePath(env = process.env) {
|
|
106
|
+
return path.join(resolveOpenclawHome(env), "agent-wallet-runtime", "update-check.json");
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function isNewerVersion(latest, current) {
|
|
110
|
+
const parse = (v) =>
|
|
111
|
+
String(v)
|
|
112
|
+
.trim()
|
|
113
|
+
.split(".")
|
|
114
|
+
.map((chunk) => parseInt((chunk.match(/^\d+/) || ["0"])[0], 10) || 0);
|
|
115
|
+
const a = parse(latest);
|
|
116
|
+
const b = parse(current);
|
|
117
|
+
for (let i = 0; i < Math.max(a.length, b.length); i++) {
|
|
118
|
+
const x = a[i] || 0;
|
|
119
|
+
const y = b[i] || 0;
|
|
120
|
+
if (x !== y) return x > y;
|
|
121
|
+
}
|
|
122
|
+
return false;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function computeUpdateAvailability(env = process.env) {
|
|
126
|
+
const current = activeVersion(env) || packageVersion;
|
|
127
|
+
if (updateCheckDisabled(env)) {
|
|
128
|
+
return { available: false, latest: null, current };
|
|
129
|
+
}
|
|
130
|
+
try {
|
|
131
|
+
const cache = readJsonFile(updateCheckCachePath(env)) || {};
|
|
132
|
+
const latest = cache.latest_version || null;
|
|
133
|
+
const available = Boolean(latest && isNewerVersion(latest, current));
|
|
134
|
+
return { available, latest, current };
|
|
135
|
+
} catch {
|
|
136
|
+
// Fail-open: a malformed cache never breaks status/doctor.
|
|
137
|
+
return { available: false, latest: null, current };
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Compare the active installed runtime (what all editors exec via current/)
|
|
142
|
+
// against the version of the CLI being run (the repo/canonical version during
|
|
143
|
+
// local development). A mismatch means a local bump was not reinstalled with
|
|
144
|
+
// release:local. in_sync is null when no runtime is installed yet.
|
|
145
|
+
function computeRuntimeInSync(env = process.env) {
|
|
146
|
+
const active = activeVersion(env);
|
|
147
|
+
const cli = packageVersion;
|
|
148
|
+
const in_sync = active === null ? null : active === cli;
|
|
149
|
+
return { in_sync, active_version: active, cli_version: cli };
|
|
150
|
+
}
|
|
151
|
+
|
|
93
152
|
function resolveHermesHome(env = process.env) {
|
|
94
153
|
return path.resolve(expandHome(env.HERMES_HOME || "~/.hermes"));
|
|
95
154
|
}
|
|
@@ -392,6 +451,17 @@ function switchSymlink(linkPath, targetPath) {
|
|
|
392
451
|
fs.renameSync(tempLink, linkPath);
|
|
393
452
|
}
|
|
394
453
|
|
|
454
|
+
// Remove a runtime pointer symlink (current/previous). recursive+force tolerate
|
|
455
|
+
// a stray directory in that slot; on a symlink this unlinks the pointer only and
|
|
456
|
+
// never deletes the release directory it targets.
|
|
457
|
+
function removeRuntimePointer(pointerPath) {
|
|
458
|
+
try {
|
|
459
|
+
fs.rmSync(pointerPath, { recursive: true, force: true });
|
|
460
|
+
} catch (error) {
|
|
461
|
+
if (error?.code !== "ENOENT") throw error;
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
|
|
395
465
|
function parseFlagValue(args, name) {
|
|
396
466
|
const prefix = `${name}=`;
|
|
397
467
|
for (let index = 0; index < args.length; index += 1) {
|
|
@@ -617,56 +687,240 @@ function ensureBootKeyFile(env = process.env) {
|
|
|
617
687
|
return { path: keyFile, status: "created" };
|
|
618
688
|
}
|
|
619
689
|
|
|
620
|
-
function
|
|
621
|
-
const
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
["OpenClaw extension", path.join(packageRoot, ".openclaw", "extensions", "agent-wallet")],
|
|
625
|
-
["Codex plugin", path.join(packageRoot, "codex", "plugins", "agent-wallet", ".codex-plugin", "plugin.json")],
|
|
626
|
-
["Claude Code plugin", path.join(packageRoot, "claude-code", "plugins", "agent-wallet", ".claude-plugin", "plugin.json")],
|
|
627
|
-
["wdk-btc-wallet", path.join(packageRoot, "wdk-btc-wallet", "package.json")],
|
|
628
|
-
["wdk-evm-wallet", path.join(packageRoot, "wdk-evm-wallet", "package.json")],
|
|
690
|
+
function resolveVenvPython(releaseRoot) {
|
|
691
|
+
const candidates = [
|
|
692
|
+
path.join(releaseRoot, "agent-wallet", ".venv", "bin", "python"),
|
|
693
|
+
path.join(releaseRoot, "agent-wallet", ".runtime-venv", "bin", "python"),
|
|
629
694
|
];
|
|
630
|
-
const
|
|
631
|
-
|
|
632
|
-
|
|
695
|
+
for (const candidate of candidates) {
|
|
696
|
+
if (fs.existsSync(candidate)) return candidate;
|
|
697
|
+
}
|
|
698
|
+
return null;
|
|
699
|
+
}
|
|
633
700
|
|
|
634
|
-
|
|
635
|
-
|
|
701
|
+
// Classify a verification failure so the caller can route the right guidance:
|
|
702
|
+
// broken_release -> our shipped code is bad; user cannot fix, stay on previous.
|
|
703
|
+
// local_env -> user's machine/runtime is fixable (python/venv/corrupt unpack).
|
|
704
|
+
// unknown -> fall back to generic guidance.
|
|
705
|
+
function classifyVerifyError(detail) {
|
|
706
|
+
const text = String(detail || "");
|
|
707
|
+
if (/SyntaxError|IndentationError|ImportError|ModuleNotFoundError|TabError|NameError/.test(text)) {
|
|
708
|
+
return "broken_release";
|
|
636
709
|
}
|
|
637
|
-
if (
|
|
638
|
-
|
|
639
|
-
} else if (!python.version_ok) {
|
|
640
|
-
missing.push(`python>=3.10:selected:${python.version || "unknown"}`);
|
|
641
|
-
} else if (!python.venv_ok) {
|
|
642
|
-
missing.push(`python-venv-ensurepip:selected:${python.version || "unknown"}`);
|
|
710
|
+
if (/ENOENT|python|venv|ensurepip|not found|No such file|Permission denied|spawn/i.test(text)) {
|
|
711
|
+
return "local_env";
|
|
643
712
|
}
|
|
644
|
-
|
|
645
|
-
|
|
713
|
+
return "unknown";
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
function verifyRuntime(releaseRoot, env = process.env) {
|
|
717
|
+
if (String(env.AGENT_WALLET_VERIFY_DISABLE || "") === "1") {
|
|
718
|
+
return { ok: true, skipped: true };
|
|
719
|
+
}
|
|
720
|
+
if (String(env.AGENT_WALLET_VERIFY_FORCE_FAIL || "") === "1") {
|
|
721
|
+
return { ok: false, error: "verify forced to fail (AGENT_WALLET_VERIFY_FORCE_FAIL)", category: "broken_release" };
|
|
722
|
+
}
|
|
723
|
+
const serverPy = path.join(releaseRoot, "codex", "plugins", "agent-wallet", "server.py");
|
|
724
|
+
if (!fs.existsSync(serverPy)) {
|
|
725
|
+
return { ok: false, error: `server.py missing at ${serverPy}`, category: "local_env" };
|
|
646
726
|
}
|
|
727
|
+
const python =
|
|
728
|
+
env.AGENT_WALLET_PYTHON ||
|
|
729
|
+
env.OPENCLAW_AGENT_WALLET_PYTHON ||
|
|
730
|
+
resolveVenvPython(releaseRoot) ||
|
|
731
|
+
commandPath("python3") ||
|
|
732
|
+
"python3";
|
|
733
|
+
const initLine = JSON.stringify({
|
|
734
|
+
jsonrpc: "2.0",
|
|
735
|
+
id: 1,
|
|
736
|
+
method: "initialize",
|
|
737
|
+
params: { protocolVersion: "2024-11-05", capabilities: {}, clientInfo: { name: "verify", version: "0" } },
|
|
738
|
+
});
|
|
739
|
+
const probe = spawnSync(python, [serverPy], {
|
|
740
|
+
input: initLine + "\n",
|
|
741
|
+
encoding: "utf8",
|
|
742
|
+
timeout: Number(env.AGENT_WALLET_VERIFY_TIMEOUT_MS || 25000),
|
|
743
|
+
env: { ...env, FASTMCP_SHOW_SERVER_BANNER: "false", FASTMCP_LOG_LEVEL: "ERROR" },
|
|
744
|
+
});
|
|
745
|
+
if (probe.error) {
|
|
746
|
+
const isTimeout = String(probe.error.message || "").includes("ETIMEDOUT");
|
|
747
|
+
return {
|
|
748
|
+
ok: false,
|
|
749
|
+
error: `handshake ${isTimeout ? "timed out (server did not respond)" : "spawn failed"}: ${probe.error.message}`,
|
|
750
|
+
category: isTimeout ? "broken_release" : "local_env",
|
|
751
|
+
};
|
|
752
|
+
}
|
|
753
|
+
const out = String(probe.stdout || "");
|
|
754
|
+
if (out.includes('"serverInfo"')) {
|
|
755
|
+
return { ok: true };
|
|
756
|
+
}
|
|
757
|
+
const detail = (probe.stderr || out || "").trim().split("\n").slice(-3).join(" ");
|
|
758
|
+
return {
|
|
759
|
+
ok: false,
|
|
760
|
+
error: `MCP initialize handshake failed: ${detail || "no serverInfo in response"}`,
|
|
761
|
+
category: classifyVerifyError(detail),
|
|
762
|
+
};
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
function resolveEditorServerChecks(env = process.env) {
|
|
766
|
+
const checks = [];
|
|
767
|
+
// Claude Code's launcher (run_mcp.sh) falls back to the runtime codex
|
|
768
|
+
// server.py when its own plugin-cache copy lacks one, so a present runtime
|
|
769
|
+
// server.py is exactly what that launcher will exec. We check its presence
|
|
770
|
+
// as the proxy for "Claude can resolve a server" (we do not inspect the
|
|
771
|
+
// version-specific cache copy directly).
|
|
772
|
+
const root = resolvedCurrentRuntimeRoot(env);
|
|
773
|
+
const runtimeCodex = root ? path.join(root, "codex", "plugins", "agent-wallet", "server.py") : null;
|
|
774
|
+
const claudeCacheRoot = expandHome("~/.claude/plugins/cache");
|
|
775
|
+
if (fs.existsSync(claudeCacheRoot)) {
|
|
776
|
+
const reachable = Boolean(runtimeCodex && fs.existsSync(runtimeCodex));
|
|
777
|
+
checks.push({
|
|
778
|
+
name: "editor:claude-code",
|
|
779
|
+
ok: reachable,
|
|
780
|
+
error: reachable ? "" : "Claude cache copy cannot resolve server.py from runtime",
|
|
781
|
+
fix: reachable ? "" : "npx @agentlayer.tech/wallet claude-code install --yes",
|
|
782
|
+
});
|
|
783
|
+
}
|
|
784
|
+
// Codex: plugin symlink target under the codex plugin install root.
|
|
785
|
+
const codexInstallRoot = resolveCodexPluginInstallRoot(env);
|
|
786
|
+
const codexTarget = path.join(codexInstallRoot, "agent-wallet", "server.py");
|
|
787
|
+
if (fs.existsSync(codexInstallRoot)) {
|
|
788
|
+
const ok = fs.existsSync(codexTarget);
|
|
789
|
+
checks.push({
|
|
790
|
+
name: "editor:codex",
|
|
791
|
+
ok,
|
|
792
|
+
error: ok ? "" : `codex plugin server.py missing at ${codexTarget}`,
|
|
793
|
+
fix: ok ? "" : "npx @agentlayer.tech/wallet codex install --yes",
|
|
794
|
+
});
|
|
795
|
+
}
|
|
796
|
+
return checks;
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
function runDoctor(args = []) {
|
|
800
|
+
const deep = hasFlag(args, "--deep");
|
|
801
|
+
const env = process.env;
|
|
802
|
+
const checks = [];
|
|
803
|
+
const fixInstall = "npx @agentlayer.tech/wallet install --yes";
|
|
647
804
|
|
|
805
|
+
for (const command of ["node", "npm"]) {
|
|
806
|
+
const ok = hasCommand(command);
|
|
807
|
+
checks.push({
|
|
808
|
+
name: `command:${command}`,
|
|
809
|
+
ok,
|
|
810
|
+
error: ok ? "" : `${command} not found on PATH`,
|
|
811
|
+
fix: ok ? "" : `install ${command}`,
|
|
812
|
+
});
|
|
813
|
+
}
|
|
814
|
+
const python = selectedPythonProbe();
|
|
815
|
+
const pythonOk = Boolean(python.path && python.version_ok && python.venv_ok);
|
|
816
|
+
checks.push({
|
|
817
|
+
name: "python_version",
|
|
818
|
+
ok: pythonOk,
|
|
819
|
+
error: !python.path ? "python3 not found"
|
|
820
|
+
: !python.version_ok ? `selected python ${python.version} < 3.10`
|
|
821
|
+
: !python.venv_ok ? `python ${python.version} lacks venv/ensurepip` : "",
|
|
822
|
+
fix: pythonOk ? "" : "install python>=3.10 with venv",
|
|
823
|
+
});
|
|
824
|
+
|
|
825
|
+
const currentPath = currentRuntimePath(env);
|
|
826
|
+
const currentRoot = resolvedCurrentRuntimeRoot(env);
|
|
827
|
+
const symlinkOk = Boolean(currentRoot && fs.existsSync(currentRoot));
|
|
828
|
+
checks.push({
|
|
829
|
+
name: "current_symlink",
|
|
830
|
+
ok: symlinkOk,
|
|
831
|
+
target: readLinkOrNull(currentPath),
|
|
832
|
+
error: symlinkOk ? "" : `current does not resolve to an existing release (${currentPath})`,
|
|
833
|
+
fix: symlinkOk ? "" : fixInstall,
|
|
834
|
+
});
|
|
835
|
+
|
|
836
|
+
const venvPython = currentRoot ? resolveVenvPython(currentRoot) : null;
|
|
837
|
+
checks.push({
|
|
838
|
+
name: "runtime_venv_python",
|
|
839
|
+
ok: Boolean(venvPython),
|
|
840
|
+
path: venvPython,
|
|
841
|
+
error: venvPython ? "" : "runtime .runtime-venv/bin/python missing",
|
|
842
|
+
fix: venvPython ? "" : fixInstall,
|
|
843
|
+
});
|
|
844
|
+
|
|
845
|
+
const serverPy = currentRoot
|
|
846
|
+
? path.join(currentRoot, "codex", "plugins", "agent-wallet", "server.py")
|
|
847
|
+
: null;
|
|
848
|
+
const serverExists = Boolean(serverPy && fs.existsSync(serverPy));
|
|
849
|
+
let parseOk = false;
|
|
850
|
+
if (serverExists && venvPython) {
|
|
851
|
+
const compiled = spawnSync(venvPython, ["-m", "py_compile", serverPy], { encoding: "utf8" });
|
|
852
|
+
parseOk = compiled.status === 0;
|
|
853
|
+
}
|
|
854
|
+
checks.push({
|
|
855
|
+
name: "server_py_parses",
|
|
856
|
+
ok: parseOk,
|
|
857
|
+
error: !serverExists ? "runtime codex server.py missing"
|
|
858
|
+
: !venvPython ? "server.py parse skipped (runtime venv missing)"
|
|
859
|
+
: parseOk ? "" : "server.py present but failed to parse",
|
|
860
|
+
fix: parseOk ? "" : fixInstall,
|
|
861
|
+
});
|
|
862
|
+
|
|
863
|
+
if (deep) {
|
|
864
|
+
const verify = currentRoot
|
|
865
|
+
? verifyRuntime(currentRoot, env)
|
|
866
|
+
: { ok: false, error: "no runtime to handshake", category: "local_env" };
|
|
867
|
+
checks.push({
|
|
868
|
+
name: "mcp_initialize_handshake",
|
|
869
|
+
ok: verify.ok,
|
|
870
|
+
error: verify.ok ? "" : verify.error,
|
|
871
|
+
fix: verify.ok ? "" : `${fixInstall} (or: npx @agentlayer.tech/wallet rollback)`,
|
|
872
|
+
});
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
for (const editorCheck of resolveEditorServerChecks(env)) {
|
|
876
|
+
checks.push(editorCheck);
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
// Informational only: never flips doctor's overall ok. Surfaces a newer
|
|
880
|
+
// published version when the background runtime check has cached one.
|
|
881
|
+
const update = computeUpdateAvailability(env);
|
|
882
|
+
checks.push({
|
|
883
|
+
name: "update_available",
|
|
884
|
+
ok: true,
|
|
885
|
+
available: update.available,
|
|
886
|
+
latest: update.latest,
|
|
887
|
+
current: update.current,
|
|
888
|
+
error: "",
|
|
889
|
+
fix: update.available ? "npx @agentlayer.tech/wallet update --yes" : "",
|
|
890
|
+
});
|
|
891
|
+
|
|
892
|
+
// Informational only: flags when the installed runtime lags the CLI/repo
|
|
893
|
+
// version (a local bump that was not reinstalled into the frameworks).
|
|
894
|
+
const rsync = computeRuntimeInSync(env);
|
|
895
|
+
checks.push({
|
|
896
|
+
name: "runtime_in_sync",
|
|
897
|
+
ok: true,
|
|
898
|
+
in_sync: rsync.in_sync,
|
|
899
|
+
active_version: rsync.active_version,
|
|
900
|
+
cli_version: rsync.cli_version,
|
|
901
|
+
error: "",
|
|
902
|
+
fix: rsync.in_sync === false ? "npm run release:local" : "",
|
|
903
|
+
});
|
|
904
|
+
|
|
905
|
+
const ok = checks.every((c) => c.ok);
|
|
648
906
|
console.log(
|
|
649
907
|
JSON.stringify(
|
|
650
908
|
{
|
|
651
|
-
ok
|
|
909
|
+
ok,
|
|
652
910
|
package_name: packageJson.name,
|
|
653
911
|
package_version: packageVersion,
|
|
654
|
-
package_root: packageRoot,
|
|
655
|
-
setup_path: setupPath,
|
|
656
912
|
openclaw_home: resolveOpenclawHome(),
|
|
657
|
-
|
|
658
|
-
current_runtime: currentRuntimePath(),
|
|
913
|
+
current_runtime: currentPath,
|
|
659
914
|
active_version: activeVersion(),
|
|
660
915
|
releases: listReleases(),
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
missing,
|
|
916
|
+
deep,
|
|
917
|
+
checks,
|
|
664
918
|
},
|
|
665
919
|
null,
|
|
666
920
|
2,
|
|
667
921
|
),
|
|
668
922
|
);
|
|
669
|
-
return
|
|
923
|
+
return ok ? 0 : 1;
|
|
670
924
|
}
|
|
671
925
|
|
|
672
926
|
function runStatus(args = []) {
|
|
@@ -680,6 +934,8 @@ function runStatus(args = []) {
|
|
|
680
934
|
previous_runtime: readLinkOrNull(previousRuntimePath()),
|
|
681
935
|
active_version: activeVersion(),
|
|
682
936
|
available_releases: listReleases(),
|
|
937
|
+
update_available: computeUpdateAvailability(),
|
|
938
|
+
runtime_in_sync: computeRuntimeInSync(),
|
|
683
939
|
};
|
|
684
940
|
if (hasFlag(args, "--verbose")) {
|
|
685
941
|
payload.verbose = true;
|
|
@@ -786,6 +1042,68 @@ function runInstall(args, { commandName = "install" } = {}) {
|
|
|
786
1042
|
}
|
|
787
1043
|
switchSymlink(currentPath, releaseRoot);
|
|
788
1044
|
|
|
1045
|
+
// Installs that pass --skip-python-setup may have no venv, so this handshake
|
|
1046
|
+
// would fail and trigger a spurious rollback; such flows must set
|
|
1047
|
+
// AGENT_WALLET_VERIFY_DISABLE=1 (verifyRuntime then skips).
|
|
1048
|
+
const verification = verifyRuntime(releaseRoot, env);
|
|
1049
|
+
if (!verification.ok && !verification.skipped) {
|
|
1050
|
+
const rollbackTarget = currentTarget; // pre-switch target captured before the switch, if any
|
|
1051
|
+
const rolledBack = Boolean(rollbackTarget);
|
|
1052
|
+
if (rolledBack) {
|
|
1053
|
+
switchSymlink(currentPath, rollbackTarget);
|
|
1054
|
+
}
|
|
1055
|
+
const previousVersion = rolledBack
|
|
1056
|
+
? path.basename(path.resolve(path.dirname(currentPath), rollbackTarget))
|
|
1057
|
+
: null;
|
|
1058
|
+
|
|
1059
|
+
let human;
|
|
1060
|
+
let fix;
|
|
1061
|
+
if (!rolledBack) {
|
|
1062
|
+
// First install / no good fallback — leave no active runtime rather than a
|
|
1063
|
+
// broken one. Deliberately do NOT point `previous` at the broken release,
|
|
1064
|
+
// so a later `rollback` cannot reactivate it; the release stays under
|
|
1065
|
+
// releases/<version> for inspection via `install --version`.
|
|
1066
|
+
removeRuntimePointer(currentPath);
|
|
1067
|
+
human =
|
|
1068
|
+
verification.category === "broken_release"
|
|
1069
|
+
? `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.`
|
|
1070
|
+
: `Release ${packageVersion} failed to verify and there is no previous version. Your local environment looks incomplete: ${verification.error}.`;
|
|
1071
|
+
fix =
|
|
1072
|
+
verification.category === "local_env"
|
|
1073
|
+
? "Ensure python>=3.10 with venv is installed, then: npx @agentlayer.tech/wallet install --yes"
|
|
1074
|
+
: "npx @agentlayer.tech/wallet install --version <known-good-version> --yes";
|
|
1075
|
+
} else if (verification.category === "broken_release") {
|
|
1076
|
+
human = `Release ${packageVersion} is broken; kept you on the working version ${previousVersion}. This is on our side — you are safe. Re-run update when a patched release ships.`;
|
|
1077
|
+
fix = "npx @agentlayer.tech/wallet update --yes";
|
|
1078
|
+
} else if (verification.category === "local_env") {
|
|
1079
|
+
human = `Release ${packageVersion} could not start on this machine; kept you on ${previousVersion}. This looks fixable locally: ${verification.error}.`;
|
|
1080
|
+
fix = "Fix python>=3.10/venv, then: npx @agentlayer.tech/wallet install --yes";
|
|
1081
|
+
} else {
|
|
1082
|
+
human = `Release ${packageVersion} failed verification; kept you on ${previousVersion}.`;
|
|
1083
|
+
fix = "npx @agentlayer.tech/wallet doctor --deep (then install --yes once resolved)";
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
console.error(
|
|
1087
|
+
JSON.stringify(
|
|
1088
|
+
{
|
|
1089
|
+
ok: false,
|
|
1090
|
+
command: commandName,
|
|
1091
|
+
version: packageVersion,
|
|
1092
|
+
category: verification.category || "unknown",
|
|
1093
|
+
error: `runtime verification failed: ${verification.error}`,
|
|
1094
|
+
rolled_back: rolledBack,
|
|
1095
|
+
kept_version: previousVersion,
|
|
1096
|
+
current_runtime_target: readLinkOrNull(currentPath),
|
|
1097
|
+
message: human,
|
|
1098
|
+
fix,
|
|
1099
|
+
},
|
|
1100
|
+
null,
|
|
1101
|
+
2,
|
|
1102
|
+
),
|
|
1103
|
+
);
|
|
1104
|
+
return 1;
|
|
1105
|
+
}
|
|
1106
|
+
|
|
789
1107
|
if (env.AGENT_WALLET_BOOT_KEY) {
|
|
790
1108
|
envFileSet(path.join(releaseRoot, "agent-wallet", ".env"), {
|
|
791
1109
|
AGENT_WALLET_BOOT_KEY: env.AGENT_WALLET_BOOT_KEY,
|
|
@@ -986,6 +1304,9 @@ function resolveHermesPluginSource() {
|
|
|
986
1304
|
}
|
|
987
1305
|
|
|
988
1306
|
function resolveCodexPluginSource() {
|
|
1307
|
+
// test/CI override: inject a staged bundle dir
|
|
1308
|
+
const override = String(process.env.AGENT_WALLET_CODEX_PLUGIN_SOURCE || "").trim();
|
|
1309
|
+
if (override) return path.resolve(expandHome(override));
|
|
989
1310
|
const currentRoot = resolvedCurrentRuntimeRoot();
|
|
990
1311
|
const candidates = [];
|
|
991
1312
|
if (currentRoot) {
|
|
@@ -1001,6 +1322,9 @@ function resolveCodexPluginSource() {
|
|
|
1001
1322
|
}
|
|
1002
1323
|
|
|
1003
1324
|
function resolveClaudeCodePluginSource() {
|
|
1325
|
+
// test/CI override: inject a staged bundle dir
|
|
1326
|
+
const override = String(process.env.AGENT_WALLET_CLAUDE_CODE_PLUGIN_SOURCE || "").trim();
|
|
1327
|
+
if (override) return path.resolve(expandHome(override));
|
|
1004
1328
|
const currentRoot = resolvedCurrentRuntimeRoot();
|
|
1005
1329
|
const candidates = [];
|
|
1006
1330
|
if (currentRoot) {
|
|
@@ -1190,6 +1514,7 @@ function runHermesInstall(args) {
|
|
|
1190
1514
|
function runCodexInstall(args) {
|
|
1191
1515
|
const codexHome = resolveCodexHome();
|
|
1192
1516
|
const pluginSource = resolveCodexPluginSource();
|
|
1517
|
+
const pinnedEnv = pinEditorMcpEnv(pluginSource);
|
|
1193
1518
|
const pluginRoot = resolveCodexPluginInstallRoot();
|
|
1194
1519
|
const pluginTarget = path.join(pluginRoot, "agent-wallet");
|
|
1195
1520
|
const marketplacePath = resolveCodexMarketplacePath();
|
|
@@ -1260,6 +1585,7 @@ function runCodexInstall(args) {
|
|
|
1260
1585
|
marketplace_name: marketplace.marketplace_name,
|
|
1261
1586
|
codex_add: add,
|
|
1262
1587
|
restart_required: true,
|
|
1588
|
+
pinned_env: pinnedEnv,
|
|
1263
1589
|
},
|
|
1264
1590
|
null,
|
|
1265
1591
|
2,
|
|
@@ -1276,6 +1602,53 @@ function resolveClaudeCodeMarketplaceDir(env = process.env) {
|
|
|
1276
1602
|
);
|
|
1277
1603
|
}
|
|
1278
1604
|
|
|
1605
|
+
// Pin OPENCLAW_HOME into one .mcp.json so run_mcp.sh uses the install-time home
|
|
1606
|
+
// instead of re-deriving the ~/.openclaw default. We deliberately do NOT pin
|
|
1607
|
+
// AGENT_WALLET_PYTHON: the launcher resolves the venv from OPENCLAW_HOME->current
|
|
1608
|
+
// dynamically, so a pinned python would go stale (and wrongly win) after upgrade.
|
|
1609
|
+
function pinHomeIntoMcpFile(mcpPath, env = process.env) {
|
|
1610
|
+
if (!fs.existsSync(mcpPath)) return { pinned: false, reason: "no .mcp.json", path: mcpPath };
|
|
1611
|
+
let doc;
|
|
1612
|
+
try {
|
|
1613
|
+
doc = JSON.parse(fs.readFileSync(mcpPath, "utf8"));
|
|
1614
|
+
} catch (error) {
|
|
1615
|
+
return { pinned: false, reason: `unreadable .mcp.json: ${error.message}`, path: mcpPath };
|
|
1616
|
+
}
|
|
1617
|
+
const entry = (doc.mcpServers || {})["agent-wallet"];
|
|
1618
|
+
if (!entry) return { pinned: false, reason: "no agent-wallet server entry", path: mcpPath };
|
|
1619
|
+
const home = resolveOpenclawHome(env);
|
|
1620
|
+
entry.env = { ...(entry.env || {}), OPENCLAW_HOME: home };
|
|
1621
|
+
writeJsonFile(mcpPath, doc);
|
|
1622
|
+
return { pinned: true, openclaw_home: home, path: mcpPath };
|
|
1623
|
+
}
|
|
1624
|
+
|
|
1625
|
+
function pinEditorMcpEnv(pluginSource, env = process.env) {
|
|
1626
|
+
return pinHomeIntoMcpFile(path.join(pluginSource, ".mcp.json"), env);
|
|
1627
|
+
}
|
|
1628
|
+
|
|
1629
|
+
// Claude Code copies the plugin into a version-keyed cache and reads THAT copy,
|
|
1630
|
+
// so the bundle pin alone is ineffective once a cache exists. Pin every cached
|
|
1631
|
+
// copy too. Cache root is overridable for tests.
|
|
1632
|
+
function pinClaudeCacheCopies(env = process.env) {
|
|
1633
|
+
const cacheRoot = path.resolve(
|
|
1634
|
+
expandHome(env.AGENT_WALLET_CLAUDE_CODE_CACHE_ROOT || "~/.claude/plugins/cache"),
|
|
1635
|
+
);
|
|
1636
|
+
const pluginCacheDir = path.join(cacheRoot, CLAUDE_CODE_MARKETPLACE_NAME, "agent-wallet");
|
|
1637
|
+
const results = [];
|
|
1638
|
+
if (!fs.existsSync(pluginCacheDir)) return results;
|
|
1639
|
+
let versions;
|
|
1640
|
+
try {
|
|
1641
|
+
versions = fs.readdirSync(pluginCacheDir);
|
|
1642
|
+
} catch {
|
|
1643
|
+
return results;
|
|
1644
|
+
}
|
|
1645
|
+
for (const version of versions) {
|
|
1646
|
+
const mcpPath = path.join(pluginCacheDir, version, ".mcp.json");
|
|
1647
|
+
if (fs.existsSync(mcpPath)) results.push(pinHomeIntoMcpFile(mcpPath, env));
|
|
1648
|
+
}
|
|
1649
|
+
return results;
|
|
1650
|
+
}
|
|
1651
|
+
|
|
1279
1652
|
function ensureClaudeCodeMarketplace(marketplaceDir, pluginSource, force) {
|
|
1280
1653
|
const pluginsDir = path.join(marketplaceDir, "plugins");
|
|
1281
1654
|
const pluginLink = path.join(pluginsDir, "agent-wallet");
|
|
@@ -1325,6 +1698,8 @@ function ensureClaudeCodeMarketplace(marketplaceDir, pluginSource, force) {
|
|
|
1325
1698
|
|
|
1326
1699
|
function runClaudeCodeInstall(args) {
|
|
1327
1700
|
const pluginSource = resolveClaudeCodePluginSource();
|
|
1701
|
+
const pinnedEnv = pinEditorMcpEnv(pluginSource);
|
|
1702
|
+
const pinnedCache = pinClaudeCacheCopies();
|
|
1328
1703
|
const force = hasFlag(args, "--force");
|
|
1329
1704
|
const skipEnable = hasFlag(args, "--skip-enable");
|
|
1330
1705
|
const claudeBin = commandPath("claude");
|
|
@@ -1397,6 +1772,8 @@ function runClaudeCodeInstall(args) {
|
|
|
1397
1772
|
claude_code_install: enable,
|
|
1398
1773
|
manual_load: pluginDirFlagFull,
|
|
1399
1774
|
restart_required: true,
|
|
1775
|
+
pinned_env: pinnedEnv,
|
|
1776
|
+
pinned_cache: pinnedCache,
|
|
1400
1777
|
note: ok
|
|
1401
1778
|
? "Plugin registered. Restart Claude Code to activate."
|
|
1402
1779
|
: `If automatic registration failed, load the plugin with: ${pluginDirFlagFull}`,
|
|
@@ -1422,7 +1799,16 @@ if (command === "--version" || command === "-v" || command === "version") {
|
|
|
1422
1799
|
}
|
|
1423
1800
|
|
|
1424
1801
|
if (command === "doctor") {
|
|
1425
|
-
process.exit(runDoctor());
|
|
1802
|
+
process.exit(runDoctor(args.slice(1)));
|
|
1803
|
+
}
|
|
1804
|
+
|
|
1805
|
+
if (command === "--self-verify") {
|
|
1806
|
+
const releaseRoot = args[1] ? path.resolve(expandHome(args[1])) : resolvedCurrentRuntimeRoot();
|
|
1807
|
+
const result = releaseRoot
|
|
1808
|
+
? verifyRuntime(releaseRoot)
|
|
1809
|
+
: { ok: false, error: "no runtime to verify", category: "local_env" };
|
|
1810
|
+
console.log(JSON.stringify(result));
|
|
1811
|
+
process.exit(result.ok ? 0 : 1);
|
|
1426
1812
|
}
|
|
1427
1813
|
|
|
1428
1814
|
if (command === "status") {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent-wallet",
|
|
3
3
|
"displayName": "Agent Wallet",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.34",
|
|
5
5
|
"description": "Claude Code bridge for the existing AgentLayer wallet runtime. Connects to Solana, Bitcoin, and EVM wallets without creating a new one.",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "AgentLayer"
|
|
@@ -20,7 +20,7 @@ elif [ -f "$CODEX_SERVER" ]; then
|
|
|
20
20
|
elif [ -f "$RUNTIME_CODEX_DIR/server.py" ]; then
|
|
21
21
|
SERVER_PY=$(CDPATH= cd -- "$RUNTIME_CODEX_DIR" && pwd)/server.py
|
|
22
22
|
else
|
|
23
|
-
printf '{"error":"agent-wallet server.py not found.
|
|
23
|
+
printf '{"error":"agent-wallet server.py not found in plugin, codex sibling, or runtime package.","fix":"npx @agentlayer.tech/wallet install --yes"}\n' >&2
|
|
24
24
|
exit 1
|
|
25
25
|
fi
|
|
26
26
|
|
|
@@ -36,4 +36,17 @@ else
|
|
|
36
36
|
PYTHON_BIN=python3
|
|
37
37
|
fi
|
|
38
38
|
|
|
39
|
+
# Fail loudly (not -32000) if the resolved server cannot even be parsed.
|
|
40
|
+
if ! "$PYTHON_BIN" -m py_compile "$SERVER_PY" 2>/dev/null; then
|
|
41
|
+
"$PYTHON_BIN" - "$SERVER_PY" >&2 <<'PY'
|
|
42
|
+
import json, sys
|
|
43
|
+
print(json.dumps({
|
|
44
|
+
"error": "agent-wallet server.py failed to parse — runtime likely broken.",
|
|
45
|
+
"server_py": sys.argv[1],
|
|
46
|
+
"fix": "npx @agentlayer.tech/wallet install --yes (or: npx @agentlayer.tech/wallet rollback)",
|
|
47
|
+
}))
|
|
48
|
+
PY
|
|
49
|
+
exit 1
|
|
50
|
+
fi
|
|
51
|
+
|
|
39
52
|
exec "$PYTHON_BIN" "$SERVER_PY"
|
|
@@ -18,4 +18,22 @@ else
|
|
|
18
18
|
PYTHON_BIN=python3
|
|
19
19
|
fi
|
|
20
20
|
|
|
21
|
+
if [ ! -f "$PLUGIN_ROOT/server.py" ]; then
|
|
22
|
+
printf '{"error":"agent-wallet server.py not found in codex plugin.","fix":"npx @agentlayer.tech/wallet install --yes"}\n' >&2
|
|
23
|
+
exit 1
|
|
24
|
+
fi
|
|
25
|
+
|
|
26
|
+
# Fail loudly (not -32000) if the resolved server cannot even be parsed.
|
|
27
|
+
if ! "$PYTHON_BIN" -m py_compile "$PLUGIN_ROOT/server.py" 2>/dev/null; then
|
|
28
|
+
"$PYTHON_BIN" - "$PLUGIN_ROOT/server.py" >&2 <<'PY'
|
|
29
|
+
import json, sys
|
|
30
|
+
print(json.dumps({
|
|
31
|
+
"error": "agent-wallet server.py failed to parse — runtime likely broken.",
|
|
32
|
+
"server_py": sys.argv[1],
|
|
33
|
+
"fix": "npx @agentlayer.tech/wallet install --yes (or: npx @agentlayer.tech/wallet rollback)",
|
|
34
|
+
}))
|
|
35
|
+
PY
|
|
36
|
+
exit 1
|
|
37
|
+
fi
|
|
38
|
+
|
|
21
39
|
exec "$PYTHON_BIN" "$PLUGIN_ROOT/server.py"
|