@agentlayer.tech/wallet 0.1.30 → 0.1.33
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/README.md +1 -2
- package/.openclaw/extensions/agent-wallet/dist/index.js +6 -340
- package/.openclaw/extensions/agent-wallet/index.ts +6 -340
- package/.openclaw/extensions/agent-wallet/openclaw.plugin.json +0 -43
- package/.openclaw/extensions/agent-wallet/package.json +1 -1
- package/.openclaw/extensions/agent-wallet/skills/wallet-operator/SKILL.md +1 -3
- package/CHANGELOG.md +60 -0
- package/README.md +0 -5
- package/agent-wallet/.env.example +0 -12
- package/agent-wallet/README.md +0 -35
- package/agent-wallet/agent_wallet/btc_user_wallets.py +32 -1
- package/agent-wallet/agent_wallet/config.py +11 -7
- package/agent-wallet/agent_wallet/evm_user_wallets.py +2 -0
- package/agent-wallet/agent_wallet/openclaw_adapter.py +1 -655
- package/agent-wallet/agent_wallet/openclaw_cli.py +0 -7
- package/agent-wallet/agent_wallet/providers/evm_portfolio.py +18 -42
- package/agent-wallet/agent_wallet/providers/jupiter.py +1 -307
- package/agent-wallet/agent_wallet/providers/wdk_btc_local.py +31 -3
- package/agent-wallet/agent_wallet/providers/wdk_evm_local.py +37 -3
- package/agent-wallet/agent_wallet/transaction_policy.py +0 -262
- package/agent-wallet/agent_wallet/wallet_layer/base.py +0 -100
- package/agent-wallet/agent_wallet/wallet_layer/solana.py +1 -1118
- package/agent-wallet/openclaw.plugin.json +0 -4
- package/agent-wallet/pyproject.toml +1 -1
- package/agent-wallet/scripts/install_agent_wallet.py +113 -6
- package/agent-wallet/scripts/install_openclaw_local_config.py +7 -5
- package/agent-wallet/skills/wallet-operator/SKILL.md +1 -5
- package/bin/openclaw-agent-wallet.mjs +434 -68
- package/claude-code/plugins/agent-wallet/scripts/run_mcp.sh +21 -3
- package/codex/plugins/agent-wallet/scripts/run_mcp.sh +18 -0
- package/codex/plugins/agent-wallet/server.py +2 -118
- package/hermes/plugins/agent_wallet/tools.py +1 -1
- package/package.json +1 -1
- package/wdk-btc-wallet/src/local_vault.js +45 -68
- package/wdk-btc-wallet/src/server.js +1 -0
- package/wdk-evm-wallet/README.md +4 -3
- package/wdk-evm-wallet/src/config.js +15 -0
- package/wdk-evm-wallet/src/local_vault.js +45 -68
- package/wdk-evm-wallet/src/server.js +1 -0
- package/agent-wallet/agent_wallet/providers/houdini.py +0 -539
|
@@ -392,6 +392,17 @@ function switchSymlink(linkPath, targetPath) {
|
|
|
392
392
|
fs.renameSync(tempLink, linkPath);
|
|
393
393
|
}
|
|
394
394
|
|
|
395
|
+
// Remove a runtime pointer symlink (current/previous). recursive+force tolerate
|
|
396
|
+
// a stray directory in that slot; on a symlink this unlinks the pointer only and
|
|
397
|
+
// never deletes the release directory it targets.
|
|
398
|
+
function removeRuntimePointer(pointerPath) {
|
|
399
|
+
try {
|
|
400
|
+
fs.rmSync(pointerPath, { recursive: true, force: true });
|
|
401
|
+
} catch (error) {
|
|
402
|
+
if (error?.code !== "ENOENT") throw error;
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
395
406
|
function parseFlagValue(args, name) {
|
|
396
407
|
const prefix = `${name}=`;
|
|
397
408
|
for (let index = 0; index < args.length; index += 1) {
|
|
@@ -617,56 +628,214 @@ function ensureBootKeyFile(env = process.env) {
|
|
|
617
628
|
return { path: keyFile, status: "created" };
|
|
618
629
|
}
|
|
619
630
|
|
|
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")],
|
|
631
|
+
function resolveVenvPython(releaseRoot) {
|
|
632
|
+
const candidates = [
|
|
633
|
+
path.join(releaseRoot, "agent-wallet", ".venv", "bin", "python"),
|
|
634
|
+
path.join(releaseRoot, "agent-wallet", ".runtime-venv", "bin", "python"),
|
|
629
635
|
];
|
|
630
|
-
const
|
|
631
|
-
|
|
632
|
-
|
|
636
|
+
for (const candidate of candidates) {
|
|
637
|
+
if (fs.existsSync(candidate)) return candidate;
|
|
638
|
+
}
|
|
639
|
+
return null;
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
// Classify a verification failure so the caller can route the right guidance:
|
|
643
|
+
// broken_release -> our shipped code is bad; user cannot fix, stay on previous.
|
|
644
|
+
// local_env -> user's machine/runtime is fixable (python/venv/corrupt unpack).
|
|
645
|
+
// unknown -> fall back to generic guidance.
|
|
646
|
+
function classifyVerifyError(detail) {
|
|
647
|
+
const text = String(detail || "");
|
|
648
|
+
if (/SyntaxError|IndentationError|ImportError|ModuleNotFoundError|TabError|NameError/.test(text)) {
|
|
649
|
+
return "broken_release";
|
|
650
|
+
}
|
|
651
|
+
if (/ENOENT|python|venv|ensurepip|not found|No such file|Permission denied|spawn/i.test(text)) {
|
|
652
|
+
return "local_env";
|
|
653
|
+
}
|
|
654
|
+
return "unknown";
|
|
655
|
+
}
|
|
633
656
|
|
|
634
|
-
|
|
635
|
-
|
|
657
|
+
function verifyRuntime(releaseRoot, env = process.env) {
|
|
658
|
+
if (String(env.AGENT_WALLET_VERIFY_DISABLE || "") === "1") {
|
|
659
|
+
return { ok: true, skipped: true };
|
|
636
660
|
}
|
|
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"}`);
|
|
661
|
+
if (String(env.AGENT_WALLET_VERIFY_FORCE_FAIL || "") === "1") {
|
|
662
|
+
return { ok: false, error: "verify forced to fail (AGENT_WALLET_VERIFY_FORCE_FAIL)", category: "broken_release" };
|
|
643
663
|
}
|
|
644
|
-
|
|
645
|
-
|
|
664
|
+
const serverPy = path.join(releaseRoot, "codex", "plugins", "agent-wallet", "server.py");
|
|
665
|
+
if (!fs.existsSync(serverPy)) {
|
|
666
|
+
return { ok: false, error: `server.py missing at ${serverPy}`, category: "local_env" };
|
|
646
667
|
}
|
|
668
|
+
const python =
|
|
669
|
+
env.AGENT_WALLET_PYTHON ||
|
|
670
|
+
env.OPENCLAW_AGENT_WALLET_PYTHON ||
|
|
671
|
+
resolveVenvPython(releaseRoot) ||
|
|
672
|
+
commandPath("python3") ||
|
|
673
|
+
"python3";
|
|
674
|
+
const initLine = JSON.stringify({
|
|
675
|
+
jsonrpc: "2.0",
|
|
676
|
+
id: 1,
|
|
677
|
+
method: "initialize",
|
|
678
|
+
params: { protocolVersion: "2024-11-05", capabilities: {}, clientInfo: { name: "verify", version: "0" } },
|
|
679
|
+
});
|
|
680
|
+
const probe = spawnSync(python, [serverPy], {
|
|
681
|
+
input: initLine + "\n",
|
|
682
|
+
encoding: "utf8",
|
|
683
|
+
timeout: Number(env.AGENT_WALLET_VERIFY_TIMEOUT_MS || 25000),
|
|
684
|
+
env: { ...env, FASTMCP_SHOW_SERVER_BANNER: "false", FASTMCP_LOG_LEVEL: "ERROR" },
|
|
685
|
+
});
|
|
686
|
+
if (probe.error) {
|
|
687
|
+
const isTimeout = String(probe.error.message || "").includes("ETIMEDOUT");
|
|
688
|
+
return {
|
|
689
|
+
ok: false,
|
|
690
|
+
error: `handshake ${isTimeout ? "timed out (server did not respond)" : "spawn failed"}: ${probe.error.message}`,
|
|
691
|
+
category: isTimeout ? "broken_release" : "local_env",
|
|
692
|
+
};
|
|
693
|
+
}
|
|
694
|
+
const out = String(probe.stdout || "");
|
|
695
|
+
if (out.includes('"serverInfo"')) {
|
|
696
|
+
return { ok: true };
|
|
697
|
+
}
|
|
698
|
+
const detail = (probe.stderr || out || "").trim().split("\n").slice(-3).join(" ");
|
|
699
|
+
return {
|
|
700
|
+
ok: false,
|
|
701
|
+
error: `MCP initialize handshake failed: ${detail || "no serverInfo in response"}`,
|
|
702
|
+
category: classifyVerifyError(detail),
|
|
703
|
+
};
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
function resolveEditorServerChecks(env = process.env) {
|
|
707
|
+
const checks = [];
|
|
708
|
+
// Claude Code's launcher (run_mcp.sh) falls back to the runtime codex
|
|
709
|
+
// server.py when its own plugin-cache copy lacks one, so a present runtime
|
|
710
|
+
// server.py is exactly what that launcher will exec. We check its presence
|
|
711
|
+
// as the proxy for "Claude can resolve a server" (we do not inspect the
|
|
712
|
+
// version-specific cache copy directly).
|
|
713
|
+
const root = resolvedCurrentRuntimeRoot(env);
|
|
714
|
+
const runtimeCodex = root ? path.join(root, "codex", "plugins", "agent-wallet", "server.py") : null;
|
|
715
|
+
const claudeCacheRoot = expandHome("~/.claude/plugins/cache");
|
|
716
|
+
if (fs.existsSync(claudeCacheRoot)) {
|
|
717
|
+
const reachable = Boolean(runtimeCodex && fs.existsSync(runtimeCodex));
|
|
718
|
+
checks.push({
|
|
719
|
+
name: "editor:claude-code",
|
|
720
|
+
ok: reachable,
|
|
721
|
+
error: reachable ? "" : "Claude cache copy cannot resolve server.py from runtime",
|
|
722
|
+
fix: reachable ? "" : "npx @agentlayer.tech/wallet claude-code install --yes",
|
|
723
|
+
});
|
|
724
|
+
}
|
|
725
|
+
// Codex: plugin symlink target under the codex plugin install root.
|
|
726
|
+
const codexInstallRoot = resolveCodexPluginInstallRoot(env);
|
|
727
|
+
const codexTarget = path.join(codexInstallRoot, "agent-wallet", "server.py");
|
|
728
|
+
if (fs.existsSync(codexInstallRoot)) {
|
|
729
|
+
const ok = fs.existsSync(codexTarget);
|
|
730
|
+
checks.push({
|
|
731
|
+
name: "editor:codex",
|
|
732
|
+
ok,
|
|
733
|
+
error: ok ? "" : `codex plugin server.py missing at ${codexTarget}`,
|
|
734
|
+
fix: ok ? "" : "npx @agentlayer.tech/wallet codex install --yes",
|
|
735
|
+
});
|
|
736
|
+
}
|
|
737
|
+
return checks;
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
function runDoctor(args = []) {
|
|
741
|
+
const deep = hasFlag(args, "--deep");
|
|
742
|
+
const env = process.env;
|
|
743
|
+
const checks = [];
|
|
744
|
+
const fixInstall = "npx @agentlayer.tech/wallet install --yes";
|
|
647
745
|
|
|
746
|
+
for (const command of ["node", "npm"]) {
|
|
747
|
+
const ok = hasCommand(command);
|
|
748
|
+
checks.push({
|
|
749
|
+
name: `command:${command}`,
|
|
750
|
+
ok,
|
|
751
|
+
error: ok ? "" : `${command} not found on PATH`,
|
|
752
|
+
fix: ok ? "" : `install ${command}`,
|
|
753
|
+
});
|
|
754
|
+
}
|
|
755
|
+
const python = selectedPythonProbe();
|
|
756
|
+
const pythonOk = Boolean(python.path && python.version_ok && python.venv_ok);
|
|
757
|
+
checks.push({
|
|
758
|
+
name: "python_version",
|
|
759
|
+
ok: pythonOk,
|
|
760
|
+
error: !python.path ? "python3 not found"
|
|
761
|
+
: !python.version_ok ? `selected python ${python.version} < 3.10`
|
|
762
|
+
: !python.venv_ok ? `python ${python.version} lacks venv/ensurepip` : "",
|
|
763
|
+
fix: pythonOk ? "" : "install python>=3.10 with venv",
|
|
764
|
+
});
|
|
765
|
+
|
|
766
|
+
const currentPath = currentRuntimePath(env);
|
|
767
|
+
const currentRoot = resolvedCurrentRuntimeRoot(env);
|
|
768
|
+
const symlinkOk = Boolean(currentRoot && fs.existsSync(currentRoot));
|
|
769
|
+
checks.push({
|
|
770
|
+
name: "current_symlink",
|
|
771
|
+
ok: symlinkOk,
|
|
772
|
+
target: readLinkOrNull(currentPath),
|
|
773
|
+
error: symlinkOk ? "" : `current does not resolve to an existing release (${currentPath})`,
|
|
774
|
+
fix: symlinkOk ? "" : fixInstall,
|
|
775
|
+
});
|
|
776
|
+
|
|
777
|
+
const venvPython = currentRoot ? resolveVenvPython(currentRoot) : null;
|
|
778
|
+
checks.push({
|
|
779
|
+
name: "runtime_venv_python",
|
|
780
|
+
ok: Boolean(venvPython),
|
|
781
|
+
path: venvPython,
|
|
782
|
+
error: venvPython ? "" : "runtime .runtime-venv/bin/python missing",
|
|
783
|
+
fix: venvPython ? "" : fixInstall,
|
|
784
|
+
});
|
|
785
|
+
|
|
786
|
+
const serverPy = currentRoot
|
|
787
|
+
? path.join(currentRoot, "codex", "plugins", "agent-wallet", "server.py")
|
|
788
|
+
: null;
|
|
789
|
+
const serverExists = Boolean(serverPy && fs.existsSync(serverPy));
|
|
790
|
+
let parseOk = false;
|
|
791
|
+
if (serverExists && venvPython) {
|
|
792
|
+
const compiled = spawnSync(venvPython, ["-m", "py_compile", serverPy], { encoding: "utf8" });
|
|
793
|
+
parseOk = compiled.status === 0;
|
|
794
|
+
}
|
|
795
|
+
checks.push({
|
|
796
|
+
name: "server_py_parses",
|
|
797
|
+
ok: parseOk,
|
|
798
|
+
error: !serverExists ? "runtime codex server.py missing"
|
|
799
|
+
: !venvPython ? "server.py parse skipped (runtime venv missing)"
|
|
800
|
+
: parseOk ? "" : "server.py present but failed to parse",
|
|
801
|
+
fix: parseOk ? "" : fixInstall,
|
|
802
|
+
});
|
|
803
|
+
|
|
804
|
+
if (deep) {
|
|
805
|
+
const verify = currentRoot
|
|
806
|
+
? verifyRuntime(currentRoot, env)
|
|
807
|
+
: { ok: false, error: "no runtime to handshake", category: "local_env" };
|
|
808
|
+
checks.push({
|
|
809
|
+
name: "mcp_initialize_handshake",
|
|
810
|
+
ok: verify.ok,
|
|
811
|
+
error: verify.ok ? "" : verify.error,
|
|
812
|
+
fix: verify.ok ? "" : `${fixInstall} (or: npx @agentlayer.tech/wallet rollback)`,
|
|
813
|
+
});
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
for (const editorCheck of resolveEditorServerChecks(env)) {
|
|
817
|
+
checks.push(editorCheck);
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
const ok = checks.every((c) => c.ok);
|
|
648
821
|
console.log(
|
|
649
822
|
JSON.stringify(
|
|
650
823
|
{
|
|
651
|
-
ok
|
|
824
|
+
ok,
|
|
652
825
|
package_name: packageJson.name,
|
|
653
826
|
package_version: packageVersion,
|
|
654
|
-
package_root: packageRoot,
|
|
655
|
-
setup_path: setupPath,
|
|
656
827
|
openclaw_home: resolveOpenclawHome(),
|
|
657
|
-
|
|
658
|
-
current_runtime: currentRuntimePath(),
|
|
828
|
+
current_runtime: currentPath,
|
|
659
829
|
active_version: activeVersion(),
|
|
660
830
|
releases: listReleases(),
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
missing,
|
|
831
|
+
deep,
|
|
832
|
+
checks,
|
|
664
833
|
},
|
|
665
834
|
null,
|
|
666
835
|
2,
|
|
667
836
|
),
|
|
668
837
|
);
|
|
669
|
-
return
|
|
838
|
+
return ok ? 0 : 1;
|
|
670
839
|
}
|
|
671
840
|
|
|
672
841
|
function runStatus(args = []) {
|
|
@@ -786,6 +955,68 @@ function runInstall(args, { commandName = "install" } = {}) {
|
|
|
786
955
|
}
|
|
787
956
|
switchSymlink(currentPath, releaseRoot);
|
|
788
957
|
|
|
958
|
+
// Installs that pass --skip-python-setup may have no venv, so this handshake
|
|
959
|
+
// would fail and trigger a spurious rollback; such flows must set
|
|
960
|
+
// AGENT_WALLET_VERIFY_DISABLE=1 (verifyRuntime then skips).
|
|
961
|
+
const verification = verifyRuntime(releaseRoot, env);
|
|
962
|
+
if (!verification.ok && !verification.skipped) {
|
|
963
|
+
const rollbackTarget = currentTarget; // pre-switch target captured before the switch, if any
|
|
964
|
+
const rolledBack = Boolean(rollbackTarget);
|
|
965
|
+
if (rolledBack) {
|
|
966
|
+
switchSymlink(currentPath, rollbackTarget);
|
|
967
|
+
}
|
|
968
|
+
const previousVersion = rolledBack
|
|
969
|
+
? path.basename(path.resolve(path.dirname(currentPath), rollbackTarget))
|
|
970
|
+
: null;
|
|
971
|
+
|
|
972
|
+
let human;
|
|
973
|
+
let fix;
|
|
974
|
+
if (!rolledBack) {
|
|
975
|
+
// First install / no good fallback — leave no active runtime rather than a
|
|
976
|
+
// broken one. Deliberately do NOT point `previous` at the broken release,
|
|
977
|
+
// so a later `rollback` cannot reactivate it; the release stays under
|
|
978
|
+
// releases/<version> for inspection via `install --version`.
|
|
979
|
+
removeRuntimePointer(currentPath);
|
|
980
|
+
human =
|
|
981
|
+
verification.category === "broken_release"
|
|
982
|
+
? `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.`
|
|
983
|
+
: `Release ${packageVersion} failed to verify and there is no previous version. Your local environment looks incomplete: ${verification.error}.`;
|
|
984
|
+
fix =
|
|
985
|
+
verification.category === "local_env"
|
|
986
|
+
? "Ensure python>=3.10 with venv is installed, then: npx @agentlayer.tech/wallet install --yes"
|
|
987
|
+
: "npx @agentlayer.tech/wallet install --version <known-good-version> --yes";
|
|
988
|
+
} else if (verification.category === "broken_release") {
|
|
989
|
+
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.`;
|
|
990
|
+
fix = "npx @agentlayer.tech/wallet update --yes";
|
|
991
|
+
} else if (verification.category === "local_env") {
|
|
992
|
+
human = `Release ${packageVersion} could not start on this machine; kept you on ${previousVersion}. This looks fixable locally: ${verification.error}.`;
|
|
993
|
+
fix = "Fix python>=3.10/venv, then: npx @agentlayer.tech/wallet install --yes";
|
|
994
|
+
} else {
|
|
995
|
+
human = `Release ${packageVersion} failed verification; kept you on ${previousVersion}.`;
|
|
996
|
+
fix = "npx @agentlayer.tech/wallet doctor --deep (then install --yes once resolved)";
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
console.error(
|
|
1000
|
+
JSON.stringify(
|
|
1001
|
+
{
|
|
1002
|
+
ok: false,
|
|
1003
|
+
command: commandName,
|
|
1004
|
+
version: packageVersion,
|
|
1005
|
+
category: verification.category || "unknown",
|
|
1006
|
+
error: `runtime verification failed: ${verification.error}`,
|
|
1007
|
+
rolled_back: rolledBack,
|
|
1008
|
+
kept_version: previousVersion,
|
|
1009
|
+
current_runtime_target: readLinkOrNull(currentPath),
|
|
1010
|
+
message: human,
|
|
1011
|
+
fix,
|
|
1012
|
+
},
|
|
1013
|
+
null,
|
|
1014
|
+
2,
|
|
1015
|
+
),
|
|
1016
|
+
);
|
|
1017
|
+
return 1;
|
|
1018
|
+
}
|
|
1019
|
+
|
|
789
1020
|
if (env.AGENT_WALLET_BOOT_KEY) {
|
|
790
1021
|
envFileSet(path.join(releaseRoot, "agent-wallet", ".env"), {
|
|
791
1022
|
AGENT_WALLET_BOOT_KEY: env.AGENT_WALLET_BOOT_KEY,
|
|
@@ -986,6 +1217,9 @@ function resolveHermesPluginSource() {
|
|
|
986
1217
|
}
|
|
987
1218
|
|
|
988
1219
|
function resolveCodexPluginSource() {
|
|
1220
|
+
// test/CI override: inject a staged bundle dir
|
|
1221
|
+
const override = String(process.env.AGENT_WALLET_CODEX_PLUGIN_SOURCE || "").trim();
|
|
1222
|
+
if (override) return path.resolve(expandHome(override));
|
|
989
1223
|
const currentRoot = resolvedCurrentRuntimeRoot();
|
|
990
1224
|
const candidates = [];
|
|
991
1225
|
if (currentRoot) {
|
|
@@ -1001,6 +1235,9 @@ function resolveCodexPluginSource() {
|
|
|
1001
1235
|
}
|
|
1002
1236
|
|
|
1003
1237
|
function resolveClaudeCodePluginSource() {
|
|
1238
|
+
// test/CI override: inject a staged bundle dir
|
|
1239
|
+
const override = String(process.env.AGENT_WALLET_CLAUDE_CODE_PLUGIN_SOURCE || "").trim();
|
|
1240
|
+
if (override) return path.resolve(expandHome(override));
|
|
1004
1241
|
const currentRoot = resolvedCurrentRuntimeRoot();
|
|
1005
1242
|
const candidates = [];
|
|
1006
1243
|
if (currentRoot) {
|
|
@@ -1190,6 +1427,7 @@ function runHermesInstall(args) {
|
|
|
1190
1427
|
function runCodexInstall(args) {
|
|
1191
1428
|
const codexHome = resolveCodexHome();
|
|
1192
1429
|
const pluginSource = resolveCodexPluginSource();
|
|
1430
|
+
const pinnedEnv = pinEditorMcpEnv(pluginSource);
|
|
1193
1431
|
const pluginRoot = resolveCodexPluginInstallRoot();
|
|
1194
1432
|
const pluginTarget = path.join(pluginRoot, "agent-wallet");
|
|
1195
1433
|
const marketplacePath = resolveCodexMarketplacePath();
|
|
@@ -1260,6 +1498,7 @@ function runCodexInstall(args) {
|
|
|
1260
1498
|
marketplace_name: marketplace.marketplace_name,
|
|
1261
1499
|
codex_add: add,
|
|
1262
1500
|
restart_required: true,
|
|
1501
|
+
pinned_env: pinnedEnv,
|
|
1263
1502
|
},
|
|
1264
1503
|
null,
|
|
1265
1504
|
2,
|
|
@@ -1268,71 +1507,189 @@ function runCodexInstall(args) {
|
|
|
1268
1507
|
return add.skipped || add.ok ? 0 : 1;
|
|
1269
1508
|
}
|
|
1270
1509
|
|
|
1271
|
-
|
|
1272
|
-
const pluginSource = resolveClaudeCodePluginSource();
|
|
1273
|
-
const force = hasFlag(args, "--force");
|
|
1274
|
-
const skipEnable = hasFlag(args, "--skip-enable");
|
|
1275
|
-
const claudeBin = commandPath("claude");
|
|
1510
|
+
const CLAUDE_CODE_MARKETPLACE_NAME = "agentlayer-local";
|
|
1276
1511
|
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1512
|
+
function resolveClaudeCodeMarketplaceDir(env = process.env) {
|
|
1513
|
+
return path.resolve(
|
|
1514
|
+
expandHome(env.AGENT_WALLET_CLAUDE_CODE_MARKETPLACE_DIR || "~/.claude/agentlayer-local"),
|
|
1515
|
+
);
|
|
1516
|
+
}
|
|
1280
1517
|
|
|
1281
|
-
|
|
1518
|
+
// Pin OPENCLAW_HOME into one .mcp.json so run_mcp.sh uses the install-time home
|
|
1519
|
+
// instead of re-deriving the ~/.openclaw default. We deliberately do NOT pin
|
|
1520
|
+
// AGENT_WALLET_PYTHON: the launcher resolves the venv from OPENCLAW_HOME->current
|
|
1521
|
+
// dynamically, so a pinned python would go stale (and wrongly win) after upgrade.
|
|
1522
|
+
function pinHomeIntoMcpFile(mcpPath, env = process.env) {
|
|
1523
|
+
if (!fs.existsSync(mcpPath)) return { pinned: false, reason: "no .mcp.json", path: mcpPath };
|
|
1524
|
+
let doc;
|
|
1282
1525
|
try {
|
|
1283
|
-
|
|
1526
|
+
doc = JSON.parse(fs.readFileSync(mcpPath, "utf8"));
|
|
1527
|
+
} catch (error) {
|
|
1528
|
+
return { pinned: false, reason: `unreadable .mcp.json: ${error.message}`, path: mcpPath };
|
|
1529
|
+
}
|
|
1530
|
+
const entry = (doc.mcpServers || {})["agent-wallet"];
|
|
1531
|
+
if (!entry) return { pinned: false, reason: "no agent-wallet server entry", path: mcpPath };
|
|
1532
|
+
const home = resolveOpenclawHome(env);
|
|
1533
|
+
entry.env = { ...(entry.env || {}), OPENCLAW_HOME: home };
|
|
1534
|
+
writeJsonFile(mcpPath, doc);
|
|
1535
|
+
return { pinned: true, openclaw_home: home, path: mcpPath };
|
|
1536
|
+
}
|
|
1537
|
+
|
|
1538
|
+
function pinEditorMcpEnv(pluginSource, env = process.env) {
|
|
1539
|
+
return pinHomeIntoMcpFile(path.join(pluginSource, ".mcp.json"), env);
|
|
1540
|
+
}
|
|
1541
|
+
|
|
1542
|
+
// Claude Code copies the plugin into a version-keyed cache and reads THAT copy,
|
|
1543
|
+
// so the bundle pin alone is ineffective once a cache exists. Pin every cached
|
|
1544
|
+
// copy too. Cache root is overridable for tests.
|
|
1545
|
+
function pinClaudeCacheCopies(env = process.env) {
|
|
1546
|
+
const cacheRoot = path.resolve(
|
|
1547
|
+
expandHome(env.AGENT_WALLET_CLAUDE_CODE_CACHE_ROOT || "~/.claude/plugins/cache"),
|
|
1548
|
+
);
|
|
1549
|
+
const pluginCacheDir = path.join(cacheRoot, CLAUDE_CODE_MARKETPLACE_NAME, "agent-wallet");
|
|
1550
|
+
const results = [];
|
|
1551
|
+
if (!fs.existsSync(pluginCacheDir)) return results;
|
|
1552
|
+
let versions;
|
|
1553
|
+
try {
|
|
1554
|
+
versions = fs.readdirSync(pluginCacheDir);
|
|
1555
|
+
} catch {
|
|
1556
|
+
return results;
|
|
1557
|
+
}
|
|
1558
|
+
for (const version of versions) {
|
|
1559
|
+
const mcpPath = path.join(pluginCacheDir, version, ".mcp.json");
|
|
1560
|
+
if (fs.existsSync(mcpPath)) results.push(pinHomeIntoMcpFile(mcpPath, env));
|
|
1561
|
+
}
|
|
1562
|
+
return results;
|
|
1563
|
+
}
|
|
1564
|
+
|
|
1565
|
+
function ensureClaudeCodeMarketplace(marketplaceDir, pluginSource, force) {
|
|
1566
|
+
const pluginsDir = path.join(marketplaceDir, "plugins");
|
|
1567
|
+
const pluginLink = path.join(pluginsDir, "agent-wallet");
|
|
1568
|
+
const manifestDir = path.join(marketplaceDir, ".claude-plugin");
|
|
1569
|
+
const manifestPath = path.join(manifestDir, "marketplace.json");
|
|
1570
|
+
|
|
1571
|
+
fs.mkdirSync(pluginsDir, { recursive: true });
|
|
1572
|
+
fs.mkdirSync(manifestDir, { recursive: true });
|
|
1573
|
+
|
|
1574
|
+
// Symlink plugin source into marketplace plugins dir.
|
|
1575
|
+
try {
|
|
1576
|
+
const existing = fs.lstatSync(pluginLink);
|
|
1284
1577
|
if (!existing.isSymbolicLink()) {
|
|
1285
1578
|
if (!force) {
|
|
1286
|
-
throw new Error(
|
|
1579
|
+
throw new Error(
|
|
1580
|
+
`${pluginLink} exists and is not a symlink. Pass --force to replace it.`,
|
|
1581
|
+
);
|
|
1287
1582
|
}
|
|
1288
|
-
fs.rmSync(
|
|
1583
|
+
fs.rmSync(pluginLink, { recursive: true, force: true });
|
|
1289
1584
|
} else {
|
|
1290
|
-
fs.unlinkSync(
|
|
1585
|
+
fs.unlinkSync(pluginLink);
|
|
1291
1586
|
}
|
|
1292
1587
|
} catch (error) {
|
|
1293
1588
|
if (error?.code !== "ENOENT") throw error;
|
|
1294
1589
|
}
|
|
1295
|
-
fs.symlinkSync(pluginSource,
|
|
1590
|
+
fs.symlinkSync(pluginSource, pluginLink, "dir");
|
|
1591
|
+
|
|
1592
|
+
// Write marketplace manifest (Claude Code requires owner + plugins[].source as relative path).
|
|
1593
|
+
const manifest = {
|
|
1594
|
+
name: CLAUDE_CODE_MARKETPLACE_NAME,
|
|
1595
|
+
description: "Local AgentLayer plugins",
|
|
1596
|
+
owner: { name: "AgentLayer" },
|
|
1597
|
+
plugins: [
|
|
1598
|
+
{
|
|
1599
|
+
name: "agent-wallet",
|
|
1600
|
+
displayName: "Agent Wallet",
|
|
1601
|
+
description:
|
|
1602
|
+
"Bridge to the existing local AgentLayer wallet runtime (Solana, Bitcoin, EVM).",
|
|
1603
|
+
category: "development",
|
|
1604
|
+
source: "./plugins/agent-wallet",
|
|
1605
|
+
},
|
|
1606
|
+
],
|
|
1607
|
+
};
|
|
1608
|
+
writeJsonFile(manifestPath, manifest);
|
|
1609
|
+
return { pluginLink, manifestPath };
|
|
1610
|
+
}
|
|
1296
1611
|
|
|
1297
|
-
|
|
1612
|
+
function runClaudeCodeInstall(args) {
|
|
1613
|
+
const pluginSource = resolveClaudeCodePluginSource();
|
|
1614
|
+
const pinnedEnv = pinEditorMcpEnv(pluginSource);
|
|
1615
|
+
const pinnedCache = pinClaudeCacheCopies();
|
|
1616
|
+
const force = hasFlag(args, "--force");
|
|
1617
|
+
const skipEnable = hasFlag(args, "--skip-enable");
|
|
1618
|
+
const claudeBin = commandPath("claude");
|
|
1619
|
+
const marketplaceDir = resolveClaudeCodeMarketplaceDir();
|
|
1620
|
+
|
|
1621
|
+
const { pluginLink } = ensureClaudeCodeMarketplace(marketplaceDir, pluginSource, force);
|
|
1622
|
+
|
|
1623
|
+
const pluginDirFlag = `claude --plugin-dir ${pluginLink}`;
|
|
1624
|
+
|
|
1625
|
+
// Without the Claude CLI we can only set up the files; the user must register manually.
|
|
1626
|
+
let marketplaceAdd = { attempted: false, ok: false, skipped: skipEnable, error: "" };
|
|
1298
1627
|
let enable = { attempted: false, ok: false, skipped: skipEnable, error: "" };
|
|
1628
|
+
|
|
1299
1629
|
if (!skipEnable) {
|
|
1300
1630
|
if (!claudeBin) {
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
error:
|
|
1306
|
-
"Claude Code CLI was not found on PATH. Load the plugin manually with: claude --plugin-dir " +
|
|
1307
|
-
pluginTarget,
|
|
1308
|
-
};
|
|
1631
|
+
const msg =
|
|
1632
|
+
"Claude Code CLI was not found on PATH. Load the plugin manually with: " + pluginDirFlag;
|
|
1633
|
+
marketplaceAdd = { attempted: false, ok: false, skipped: false, error: msg };
|
|
1634
|
+
enable = { attempted: false, ok: false, skipped: false, error: msg };
|
|
1309
1635
|
} else {
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1636
|
+
// Register the local marketplace (idempotent — safe to re-run).
|
|
1637
|
+
const addResult = spawnSync(
|
|
1638
|
+
claudeBin,
|
|
1639
|
+
["plugin", "marketplace", "add", marketplaceDir, "--scope", "user"],
|
|
1640
|
+
{ encoding: "utf8", stdio: "pipe" },
|
|
1641
|
+
);
|
|
1642
|
+
marketplaceAdd = {
|
|
1315
1643
|
attempted: true,
|
|
1316
|
-
ok:
|
|
1644
|
+
ok: addResult.status === 0,
|
|
1317
1645
|
skipped: false,
|
|
1318
|
-
error:
|
|
1646
|
+
error: addResult.status === 0 ? "" : (addResult.stderr || addResult.stdout || "").trim(),
|
|
1319
1647
|
};
|
|
1648
|
+
|
|
1649
|
+
if (marketplaceAdd.ok) {
|
|
1650
|
+
// Install plugin from the now-registered local marketplace.
|
|
1651
|
+
const installResult = spawnSync(
|
|
1652
|
+
claudeBin,
|
|
1653
|
+
["plugin", "install", `agent-wallet@${CLAUDE_CODE_MARKETPLACE_NAME}`, "--scope", "user"],
|
|
1654
|
+
{ encoding: "utf8", stdio: "pipe" },
|
|
1655
|
+
);
|
|
1656
|
+
enable = {
|
|
1657
|
+
attempted: true,
|
|
1658
|
+
ok: installResult.status === 0,
|
|
1659
|
+
skipped: false,
|
|
1660
|
+
error:
|
|
1661
|
+
installResult.status === 0
|
|
1662
|
+
? ""
|
|
1663
|
+
: (installResult.stderr || installResult.stdout || "").trim(),
|
|
1664
|
+
};
|
|
1665
|
+
} else {
|
|
1666
|
+
enable = {
|
|
1667
|
+
attempted: false,
|
|
1668
|
+
ok: false,
|
|
1669
|
+
skipped: false,
|
|
1670
|
+
error: "Skipped plugin install because marketplace registration failed.",
|
|
1671
|
+
};
|
|
1672
|
+
}
|
|
1320
1673
|
}
|
|
1321
1674
|
}
|
|
1322
1675
|
|
|
1323
|
-
const
|
|
1676
|
+
const ok = enable.skipped || enable.ok;
|
|
1677
|
+
const pluginDirFlagFull = `claude --plugin-dir ${pluginSource}`;
|
|
1324
1678
|
console.log(
|
|
1325
1679
|
JSON.stringify(
|
|
1326
1680
|
{
|
|
1327
|
-
ok
|
|
1681
|
+
ok,
|
|
1328
1682
|
plugin_source: pluginSource,
|
|
1329
|
-
|
|
1683
|
+
marketplace_dir: marketplaceDir,
|
|
1684
|
+
marketplace_add: marketplaceAdd,
|
|
1330
1685
|
claude_code_install: enable,
|
|
1331
|
-
manual_load:
|
|
1686
|
+
manual_load: pluginDirFlagFull,
|
|
1332
1687
|
restart_required: true,
|
|
1333
|
-
|
|
1688
|
+
pinned_env: pinnedEnv,
|
|
1689
|
+
pinned_cache: pinnedCache,
|
|
1690
|
+
note: ok
|
|
1334
1691
|
? "Plugin registered. Restart Claude Code to activate."
|
|
1335
|
-
: `If automatic registration failed, load the plugin with: ${
|
|
1692
|
+
: `If automatic registration failed, load the plugin with: ${pluginDirFlagFull}`,
|
|
1336
1693
|
},
|
|
1337
1694
|
null,
|
|
1338
1695
|
2,
|
|
@@ -1355,7 +1712,16 @@ if (command === "--version" || command === "-v" || command === "version") {
|
|
|
1355
1712
|
}
|
|
1356
1713
|
|
|
1357
1714
|
if (command === "doctor") {
|
|
1358
|
-
process.exit(runDoctor());
|
|
1715
|
+
process.exit(runDoctor(args.slice(1)));
|
|
1716
|
+
}
|
|
1717
|
+
|
|
1718
|
+
if (command === "--self-verify") {
|
|
1719
|
+
const releaseRoot = args[1] ? path.resolve(expandHome(args[1])) : resolvedCurrentRuntimeRoot();
|
|
1720
|
+
const result = releaseRoot
|
|
1721
|
+
? verifyRuntime(releaseRoot)
|
|
1722
|
+
: { ok: false, error: "no runtime to verify", category: "local_env" };
|
|
1723
|
+
console.log(JSON.stringify(result));
|
|
1724
|
+
process.exit(result.ok ? 0 : 1);
|
|
1359
1725
|
}
|
|
1360
1726
|
|
|
1361
1727
|
if (command === "status") {
|
|
@@ -6,16 +6,21 @@ PLUGIN_ROOT=$(CDPATH= cd -- "$SCRIPT_DIR/.." && pwd)
|
|
|
6
6
|
OPENCLAW_HOME=${OPENCLAW_HOME:-"$HOME/.openclaw"}
|
|
7
7
|
PACKAGE_ROOT=${AGENT_WALLET_PACKAGE_ROOT:-${OPENCLAW_AGENT_WALLET_PACKAGE_ROOT:-"$OPENCLAW_HOME/agent-wallet-runtime/current/agent-wallet"}}
|
|
8
8
|
|
|
9
|
-
# Resolve server.py
|
|
10
|
-
|
|
9
|
+
# Resolve server.py. When Claude Code copies this plugin into its cache, the
|
|
10
|
+
# relative sibling paths below no longer resolve, so fall back to the codex
|
|
11
|
+
# plugin copy inside the installed runtime package, which is always present.
|
|
11
12
|
LOCAL_SERVER="$PLUGIN_ROOT/server.py"
|
|
13
|
+
CODEX_SERVER="$PLUGIN_ROOT/../../codex/plugins/agent-wallet/server.py"
|
|
14
|
+
RUNTIME_CODEX_DIR="$OPENCLAW_HOME/agent-wallet-runtime/current/codex/plugins/agent-wallet"
|
|
12
15
|
|
|
13
16
|
if [ -f "$LOCAL_SERVER" ]; then
|
|
14
17
|
SERVER_PY="$LOCAL_SERVER"
|
|
15
18
|
elif [ -f "$CODEX_SERVER" ]; then
|
|
16
19
|
SERVER_PY=$(CDPATH= cd -- "$PLUGIN_ROOT/../../codex/plugins/agent-wallet" && pwd)/server.py
|
|
20
|
+
elif [ -f "$RUNTIME_CODEX_DIR/server.py" ]; then
|
|
21
|
+
SERVER_PY=$(CDPATH= cd -- "$RUNTIME_CODEX_DIR" && pwd)/server.py
|
|
17
22
|
else
|
|
18
|
-
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
|
|
19
24
|
exit 1
|
|
20
25
|
fi
|
|
21
26
|
|
|
@@ -31,4 +36,17 @@ else
|
|
|
31
36
|
PYTHON_BIN=python3
|
|
32
37
|
fi
|
|
33
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
|
+
|
|
34
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"
|