@c4roa0/agentpay-user 0.0.0-stage → 0.3.0
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/AGENTS.md +137 -0
- package/CONTRIBUTING.md +16 -0
- package/LICENSE +373 -0
- package/NOTICE.md +20 -0
- package/README.md +84 -2
- package/SECURITY.md +16 -0
- package/doc/README.md +52 -0
- package/doc/TODO.md +97 -0
- package/doc/architecture.md +96 -0
- package/doc/design.md +60 -0
- package/doc/develope.md +68 -0
- package/doc/plans/user-wallet-distribution.md +91 -0
- package/doc/reference/harness-skill-runtime.md +120 -0
- package/doc/reference/local-cache-foundation.md +176 -0
- package/doc/reference/local-safe-owner-ui.md +25 -0
- package/doc/reference/local-update.md +18 -0
- package/doc/reference/multi-service-runtime.md +119 -0
- package/doc/reference/npm-integration-verification.md +67 -0
- package/doc/reference/onboarding-writing.md +48 -0
- package/doc/reference/original-order-recovery.md +97 -0
- package/doc/reference/private-anvil-onboarding-config.example.json +18 -0
- package/doc/reference/public-deployment-settings.md +56 -0
- package/doc/reference/random-skill-contract.md +18 -0
- package/doc/reference/result-file-cache.md +166 -0
- package/doc/reference/runtime-address-recovery.md +42 -0
- package/doc/reference/runtime-result-cache.md +157 -0
- package/doc/reference/runtime-startup-recovery.md +141 -0
- package/doc/reference/skill-input-output-cache.md +93 -0
- package/doc/reference/wallet-identity-policy-history.md +23 -0
- package/doc/runbooks/commands.md +89 -0
- package/doc/runbooks/deployment-authorization-update.md +42 -0
- package/doc/runbooks/emergency-payment-stop.md +80 -0
- package/doc/runbooks/local-autostart.md +212 -0
- package/doc/runbooks/local-cache.md +115 -0
- package/doc/runbooks/local-update.md +84 -0
- package/doc/runbooks/market-connection.md +73 -0
- package/doc/runbooks/npm-publication.md +56 -0
- package/doc/runbooks/official-test-package.md +62 -0
- package/doc/runbooks/order-cancellation.md +56 -0
- package/doc/runbooks/order-support.md +41 -0
- package/doc/runbooks/policy-renewal.md +80 -0
- package/doc/runbooks/private-anvil-terminal-setup.md +58 -0
- package/doc/runbooks/runtime-skill-client.md +155 -0
- package/doc/runbooks/safe-owner-web.md +104 -0
- package/doc/runbooks/safe-policy-migration.md +36 -0
- package/doc/runbooks/safe-policy-setup.md +52 -0
- package/doc/runbooks/terminal-authorization-preview.md +38 -0
- package/doc/runbooks/unlock-wallet.md +68 -0
- package/doc/runbooks/user-onboarding.md +87 -0
- package/doc/runbooks/wallet-identity.md +59 -0
- package/doc/security/public-source-review-2026-11-06.md +71 -0
- package/doc/usage.md +54 -0
- package/package.json +49 -4
- package/progress.md +178 -0
- package/run +29 -0
- package/scripts/agentpay-cli.mjs +11 -0
- package/scripts/authorize-existing-safe.mjs +211 -0
- package/scripts/autostart.mjs +186 -0
- package/scripts/cancel-order.mjs +76 -0
- package/scripts/check-installation.mjs +26 -0
- package/scripts/check-official-test-package.mjs +35 -0
- package/scripts/check-purchase-readiness.mjs +275 -0
- package/scripts/configure-runtime-client.mjs +16 -0
- package/scripts/guide-policy.mjs +64 -0
- package/scripts/initialize-caller-credential.mjs +11 -0
- package/scripts/install-skill-bundle.mjs +176 -0
- package/scripts/install-user.mjs +24 -0
- package/scripts/local-service.mjs +60 -0
- package/scripts/lock.mjs +11 -0
- package/scripts/login-notify.mjs +39 -0
- package/scripts/migrate-safe-policy.mjs +165 -0
- package/scripts/official-paid-test.mjs +9 -0
- package/scripts/poll-unconfirmed-notice.mjs +75 -0
- package/scripts/prepare-onboarding.mjs +33 -0
- package/scripts/preview-policy.mjs +65 -0
- package/scripts/random-model-ack-smoke.mjs +235 -0
- package/scripts/random-reject-smoke.mjs +147 -0
- package/scripts/renew.mjs +9 -0
- package/scripts/repair.mjs +10 -0
- package/scripts/revoke.mjs +9 -0
- package/scripts/select-skill-bundle.mjs +39 -0
- package/scripts/setup-local.mjs +199 -0
- package/scripts/status.mjs +13 -0
- package/scripts/test-package.mjs +35 -0
- package/scripts/unlock.mjs +13 -0
- package/scripts/update.mjs +7 -0
- package/scripts/user-setup.mjs +219 -0
- package/scripts/verify-npm-release.mjs +32 -0
- package/src/active-skill.mjs +160 -0
- package/src/anvil-onboarding-state.mjs +96 -0
- package/src/anvil-onboarding.mjs +1036 -0
- package/src/authorization-question-checkpoint.mjs +114 -0
- package/src/autostart-files.mjs +50 -0
- package/src/cache-management-cli.mjs +302 -0
- package/src/cache-management.mjs +47 -0
- package/src/caller-auth.mjs +58 -0
- package/src/client-request-lock.mjs +44 -0
- package/src/connect-market.mjs +408 -0
- package/src/demo-unlock-secret.mjs +64 -0
- package/src/deployment-status.mjs +198 -0
- package/src/file-cache-cli.mjs +83 -0
- package/src/file-cache-owner.mjs +74 -0
- package/src/file-cache-settings.mjs +50 -0
- package/src/file-cache.mjs +790 -0
- package/src/file-download.mjs +281 -0
- package/src/finalization-disclosure.mjs +16 -0
- package/src/guided-setup.mjs +194 -0
- package/src/index.mjs +1231 -0
- package/src/install-and-setup.mjs +38 -0
- package/src/install-preflight.mjs +201 -0
- package/src/install-user.mjs +279 -0
- package/src/installation-paths.mjs +31 -0
- package/src/invocation-result.mjs +148 -0
- package/src/invocation-store.mjs +385 -0
- package/src/local-data-directory.mjs +117 -0
- package/src/local-info.mjs +181 -0
- package/src/local-services.mjs +156 -0
- package/src/local-time.mjs +36 -0
- package/src/local-update.mjs +306 -0
- package/src/local-wallet-ipc.mjs +93 -0
- package/src/login-reminder.mjs +28 -0
- package/src/market-download-url.mjs +21 -0
- package/src/node-version.mjs +12 -0
- package/src/npm-update.mjs +126 -0
- package/src/official-paid-test.mjs +407 -0
- package/src/official-test-package.mjs +41 -0
- package/src/official-test-update.mjs +162 -0
- package/src/order-cancellation.mjs +241 -0
- package/src/order-closure.mjs +42 -0
- package/src/order-list-panel.mjs +110 -0
- package/src/order-review.mjs +424 -0
- package/src/order-status-panel.mjs +65 -0
- package/src/order-support.mjs +365 -0
- package/src/owner-approval-server.mjs +332 -0
- package/src/owner-signer.mjs +67 -0
- package/src/policy-info.mjs +38 -0
- package/src/policy-migration.mjs +84 -0
- package/src/policy-operations.mjs +67 -0
- package/src/policy-preview-server.mjs +236 -0
- package/src/policy-terminal-guide.mjs +377 -0
- package/src/prepare-local-services.mjs +143 -0
- package/src/prepare-onboarding.mjs +210 -0
- package/src/private-journal-files.mjs +29 -0
- package/src/readiness-profile.mjs +30 -0
- package/src/renew-policy.mjs +605 -0
- package/src/repair-market-wallet.mjs +137 -0
- package/src/restore-wallet.mjs +255 -0
- package/src/result-cache-view.mjs +276 -0
- package/src/runtime-client.mjs +1145 -0
- package/src/runtime-connection-settings.mjs +101 -0
- package/src/runtime-recovery.mjs +667 -0
- package/src/runtime-status.mjs +32 -0
- package/src/safe-owner-anvil.mjs +115 -0
- package/src/safe-policy-plan.mjs +195 -0
- package/src/service-reference.mjs +233 -0
- package/src/service-url.mjs +52 -0
- package/src/settlement-response.mjs +69 -0
- package/src/setup-autostart.mjs +197 -0
- package/src/setup-demo.mjs +152 -0
- package/src/skill-bundle.mjs +142 -0
- package/src/skill-client.mjs +198 -0
- package/src/skill-install-prompt.mjs +31 -0
- package/src/skill-package.mjs +50 -0
- package/src/skill-zip.mjs +220 -0
- package/src/status-panel.mjs +589 -0
- package/src/transport.mjs +48 -0
- package/src/uninstall-local.mjs +299 -0
- package/src/user-cli.mjs +354 -0
- package/src/user-setup-guide.mjs +193 -0
- package/src/wallet-control.mjs +182 -0
- package/src/wallet-ipc-error.mjs +16 -0
- package/src/wallet-package.mjs +22 -0
- package/update-pair.json +6 -0
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { ensureRuntimeConnection } from "./runtime-connection-settings.mjs";
|
|
2
|
+
import { walletPackage } from "./wallet-package.mjs";
|
|
3
|
+
// SPDX-License-Identifier: MPL-2.0
|
|
4
|
+
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
|
|
5
|
+
// If a copy was not distributed with this file, see https://mozilla.org/MPL/2.0/.
|
|
6
|
+
import { spawn } from "node:child_process";
|
|
7
|
+
import { open, readFile } from "node:fs/promises";
|
|
8
|
+
import { dirname, join } from "node:path";
|
|
9
|
+
import { randomUUID } from "node:crypto";
|
|
10
|
+
import { installedUserDir } from "./local-data-directory.mjs";
|
|
11
|
+
import { checkServiceDirectory } from "./local-services.mjs";
|
|
12
|
+
import { ensureLocalServices } from "./prepare-local-services.mjs";
|
|
13
|
+
import { runInstall } from "./install-user.mjs";
|
|
14
|
+
import { localWalletAdmin } from "./local-wallet-ipc.mjs";
|
|
15
|
+
import { controlWallet } from "./wallet-control.mjs";
|
|
16
|
+
|
|
17
|
+
async function service(action, { userDir, dataDir, logPath }) {
|
|
18
|
+
const log = await open(logPath, "a", 0o600);
|
|
19
|
+
try {
|
|
20
|
+
await new Promise((resolve, reject) => {
|
|
21
|
+
const child = spawn(
|
|
22
|
+
process.execPath,
|
|
23
|
+
[join(userDir, "scripts/autostart.mjs"), action],
|
|
24
|
+
{
|
|
25
|
+
shell: false,
|
|
26
|
+
env: { ...process.env, AGENTPAY_USER_DATA_DIR: dataDir },
|
|
27
|
+
stdio: ["ignore", log.fd, log.fd],
|
|
28
|
+
},
|
|
29
|
+
);
|
|
30
|
+
child.once("error", reject);
|
|
31
|
+
child.once("exit", (code) =>
|
|
32
|
+
code === 0 ? resolve() : reject(Error("Service preparation failed.")),
|
|
33
|
+
);
|
|
34
|
+
});
|
|
35
|
+
} finally {
|
|
36
|
+
await log.close();
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
export async function repairMarketWallet({
|
|
40
|
+
dataDir,
|
|
41
|
+
write = console.log,
|
|
42
|
+
userDir = installedUserDir,
|
|
43
|
+
prepare = runInstall,
|
|
44
|
+
connection = ensureRuntimeConnection,
|
|
45
|
+
manage = service,
|
|
46
|
+
wallet = localWalletAdmin,
|
|
47
|
+
unlock = controlWallet,
|
|
48
|
+
inspectPackage = walletPackage,
|
|
49
|
+
checkDirectory = checkServiceDirectory,
|
|
50
|
+
ensureSettings = ensureLocalServices,
|
|
51
|
+
pause = (ms) => new Promise((r) => setTimeout(r, ms)),
|
|
52
|
+
}) {
|
|
53
|
+
let logPath,
|
|
54
|
+
servicesPaused = false;
|
|
55
|
+
try {
|
|
56
|
+
await inspectPackage(userDir);
|
|
57
|
+
await checkDirectory(dataDir);
|
|
58
|
+
await ensureSettings({ dataDir, userDir, write, connectionOptions: { refreshClientModule: true, checkOnly: true } });
|
|
59
|
+
logPath = join(
|
|
60
|
+
dataDir,
|
|
61
|
+
"runtime",
|
|
62
|
+
`wallet-prepare-${Date.now()}-${randomUUID()}.log`,
|
|
63
|
+
);
|
|
64
|
+
const context = { userDir, dataDir, logPath };
|
|
65
|
+
write("1/3 Prepare Wallet. Your background services will pause briefly.");
|
|
66
|
+
await prepare({
|
|
67
|
+
userDir,
|
|
68
|
+
dataDir,
|
|
69
|
+
prepareOnly: true,
|
|
70
|
+
write: () => {},
|
|
71
|
+
// The installer lock covers service changes as well as dependency checks.
|
|
72
|
+
// A competing repair must fail before pausing a running Wallet.
|
|
73
|
+
beforePrepare: async () => {
|
|
74
|
+
await connection({ dataDir, write, refreshClientModule: true, checkOnly: true });
|
|
75
|
+
await manage("prepare-repair", context);
|
|
76
|
+
await manage("stop", context);
|
|
77
|
+
servicesPaused = true;
|
|
78
|
+
write("2/3 Verify installed User and Wallet packages.");
|
|
79
|
+
},
|
|
80
|
+
afterPrepare: async () => {
|
|
81
|
+
await connection({ dataDir, write, refreshClientModule: true });
|
|
82
|
+
write("3/3 Start Wallet and Runtime.");
|
|
83
|
+
await manage("start", context);
|
|
84
|
+
servicesPaused = false;
|
|
85
|
+
},
|
|
86
|
+
});
|
|
87
|
+
let ready = false;
|
|
88
|
+
for (let attempt = 0; attempt < 30; attempt++) {
|
|
89
|
+
try {
|
|
90
|
+
await wallet(dataDir, "status");
|
|
91
|
+
ready = true;
|
|
92
|
+
break;
|
|
93
|
+
} catch (e) {
|
|
94
|
+
if (e.code !== "WALLET_NOT_RUNNING") throw e;
|
|
95
|
+
await pause(500);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
if (!ready) throw Error("START_TIMEOUT");
|
|
99
|
+
await wallet(dataDir, "marketConnectionScope");
|
|
100
|
+
const state = await wallet(dataDir, "status");
|
|
101
|
+
if (
|
|
102
|
+
state.locked &&
|
|
103
|
+
(await unlock("unlock", {
|
|
104
|
+
args: [dataDir],
|
|
105
|
+
write: (m) => {
|
|
106
|
+
if (!m.startsWith("Wallet unlocked.")) write(m);
|
|
107
|
+
},
|
|
108
|
+
})) !== 0
|
|
109
|
+
)
|
|
110
|
+
return false;
|
|
111
|
+
if ((await wallet(dataDir, "status")).locked)
|
|
112
|
+
throw Error("UNLOCK_REQUIRED");
|
|
113
|
+
write("Wallet is ready. Continuing your Market connection.");
|
|
114
|
+
return true;
|
|
115
|
+
} catch (e) {
|
|
116
|
+
if (e.message === "CURRENT_SOURCE_REQUIRED")
|
|
117
|
+
write(
|
|
118
|
+
"Install the reviewed User package, then run agentpay market connect again.",
|
|
119
|
+
);
|
|
120
|
+
else {
|
|
121
|
+
if (!logPath)
|
|
122
|
+
write(
|
|
123
|
+
"Startup settings were not completed. Run agentpay service start in your terminal, then retry this connection.",
|
|
124
|
+
);
|
|
125
|
+
if (e.preparationMessage) write(e.preparationMessage);
|
|
126
|
+
if (servicesPaused)
|
|
127
|
+
write(
|
|
128
|
+
"Wallet and Runtime remain stopped until preparation succeeds. Your keys and Order records are kept.",
|
|
129
|
+
);
|
|
130
|
+
write(
|
|
131
|
+
"Wallet preparation stopped. Run agentpay market connect again after checking the log.",
|
|
132
|
+
);
|
|
133
|
+
if (e.logPath || logPath) write("Log: " + (e.logPath || logPath));
|
|
134
|
+
}
|
|
135
|
+
return false;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MPL-2.0
|
|
2
|
+
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
|
|
3
|
+
// If a copy was not distributed with this file, see https://mozilla.org/MPL/2.0/.
|
|
4
|
+
import { createInterface } from "node:readline/promises";
|
|
5
|
+
import { spawn } from "node:child_process";
|
|
6
|
+
import { join } from "node:path";
|
|
7
|
+
import { getAddress, formatUnits } from "ethers";
|
|
8
|
+
import {
|
|
9
|
+
resolveUserDataDir,
|
|
10
|
+
installedUserDir,
|
|
11
|
+
} from "./local-data-directory.mjs";
|
|
12
|
+
import { inspectLocalUserSetup } from "./user-setup-guide.mjs";
|
|
13
|
+
import { ensureLocalServices } from "./prepare-local-services.mjs";
|
|
14
|
+
import { localWalletAdmin } from "./local-wallet-ipc.mjs";
|
|
15
|
+
import { controlWallet } from "./wallet-control.mjs";
|
|
16
|
+
import { displaySeconds } from "./local-time.mjs";
|
|
17
|
+
export const restoreMessages = {
|
|
18
|
+
METHOD_NOT_ALLOWED:
|
|
19
|
+
"This Wallet build does not support restoration. Install compatible User and Wallet versions, then retry agentpay wallet restore.",
|
|
20
|
+
RESTORE_CONTRACT_MISSING:
|
|
21
|
+
"A required contract is missing. Check the network, Safe and Module addresses.",
|
|
22
|
+
RESTORE_CHAIN_UNAVAILABLE:
|
|
23
|
+
"The chain could not be read. Check your RPC connection and retry agentpay wallet restore.",
|
|
24
|
+
RESTORE_TARGET_INVALID:
|
|
25
|
+
"Check the network, Module, Safe and original Policy ID.",
|
|
26
|
+
POLICY_KEY_MISMATCH:
|
|
27
|
+
"This Policy belongs to a different Session Key. Use its original key.",
|
|
28
|
+
POLICY_ALREADY_BOUND:
|
|
29
|
+
"This Wallet already uses another Policy. Its binding was kept.",
|
|
30
|
+
RESTORE_POLICY_NOT_FOUND:
|
|
31
|
+
"No Policy was found for this Safe and ID. Check the network, Module and Policy ID.",
|
|
32
|
+
RESTORE_POLICY_REVOKED:
|
|
33
|
+
"This Policy was revoked and cannot be restored for spending.",
|
|
34
|
+
RESTORE_POLICY_EXPIRED:
|
|
35
|
+
"This Policy has expired. Restoring a key does not renew spending permission.",
|
|
36
|
+
RESTORE_POLICY_NOT_ACTIVE:
|
|
37
|
+
"This Policy is not active yet. Try again after its start time.",
|
|
38
|
+
RESTORE_MODULE_DISABLED:
|
|
39
|
+
"The Safe has disabled this Module. Restore does not enable it.",
|
|
40
|
+
MIGRATION_CHAIN_MISMATCH:
|
|
41
|
+
"The RPC returned a different network. Check its address.",
|
|
42
|
+
LOCKED: "Wallet is locked. Unlock it before restoring the authorization.",
|
|
43
|
+
CANCELLED: "Restoration paused. Existing Wallet data was kept.",
|
|
44
|
+
};
|
|
45
|
+
async function askTerminal(message) {
|
|
46
|
+
if (!process.stdin.isTTY)
|
|
47
|
+
throw Error("Run agentpay wallet restore in an interactive terminal.");
|
|
48
|
+
const r = createInterface({ input: process.stdin, output: process.stderr });
|
|
49
|
+
try {
|
|
50
|
+
return await r.question(message + "\n> ");
|
|
51
|
+
} finally {
|
|
52
|
+
r.close();
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
async function run(script, args, dataDir) {
|
|
56
|
+
const code = await new Promise((done, reject) => {
|
|
57
|
+
const c = spawn(
|
|
58
|
+
process.execPath,
|
|
59
|
+
[join(installedUserDir, "scripts", script), ...args],
|
|
60
|
+
{
|
|
61
|
+
stdio: "inherit",
|
|
62
|
+
env: { ...process.env, AGENTPAY_USER_DATA_DIR: dataDir },
|
|
63
|
+
},
|
|
64
|
+
);
|
|
65
|
+
c.once("error", reject);
|
|
66
|
+
c.once("exit", done);
|
|
67
|
+
});
|
|
68
|
+
if (code !== 0)
|
|
69
|
+
throw Error(
|
|
70
|
+
"Local preparation did not finish. Retry agentpay wallet restore.",
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
export async function restoreExistingPolicy({
|
|
74
|
+
dataDir,
|
|
75
|
+
target,
|
|
76
|
+
ask = askTerminal,
|
|
77
|
+
write = console.log,
|
|
78
|
+
expectedDeployment = null,
|
|
79
|
+
previewOnly = false,
|
|
80
|
+
ops = {},
|
|
81
|
+
}) {
|
|
82
|
+
const services = {
|
|
83
|
+
ensure: ensureLocalServices,
|
|
84
|
+
wallet: localWalletAdmin,
|
|
85
|
+
unlock: controlWallet,
|
|
86
|
+
run,
|
|
87
|
+
...ops,
|
|
88
|
+
};
|
|
89
|
+
let status;
|
|
90
|
+
try {
|
|
91
|
+
status = await services.wallet(dataDir, "status");
|
|
92
|
+
} catch (e) {
|
|
93
|
+
if (e.code !== "WALLET_NOT_RUNNING") throw e;
|
|
94
|
+
}
|
|
95
|
+
if (!status) {
|
|
96
|
+
await services.ensure({ dataDir, ask, write });
|
|
97
|
+
await services.run("autostart.mjs", ["prepare"], dataDir);
|
|
98
|
+
await services.run("autostart.mjs", ["start"], dataDir);
|
|
99
|
+
for (let i = 0; i < 30; i++) {
|
|
100
|
+
try {
|
|
101
|
+
status = await services.wallet(dataDir, "status");
|
|
102
|
+
break;
|
|
103
|
+
} catch (e) {
|
|
104
|
+
if (e.code !== "WALLET_NOT_RUNNING") throw e;
|
|
105
|
+
await new Promise((r) => setTimeout(r, 300));
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
if (!status)
|
|
110
|
+
throw Error("Wallet did not start. Run agentpay status before retrying.");
|
|
111
|
+
if (
|
|
112
|
+
status.locked &&
|
|
113
|
+
(await services.unlock("unlock", { args: [dataDir], write })) !== 0
|
|
114
|
+
)
|
|
115
|
+
throw Object.assign(Error(), { code: "CANCELLED" });
|
|
116
|
+
const preview = await services.wallet(dataDir, "previewPolicyRestore", {
|
|
117
|
+
target,
|
|
118
|
+
});
|
|
119
|
+
if (
|
|
120
|
+
expectedDeployment &&
|
|
121
|
+
["factory", "registry", "token", "module"].some(
|
|
122
|
+
(k) =>
|
|
123
|
+
preview.policy[k].toLowerCase() !== expectedDeployment[k].toLowerCase(),
|
|
124
|
+
)
|
|
125
|
+
)
|
|
126
|
+
throw Error(
|
|
127
|
+
"This Policy uses another deployment. Use agentpay wallet restore separately; the current setup test cannot use it.",
|
|
128
|
+
);
|
|
129
|
+
if (previewOnly) return preview;
|
|
130
|
+
write(
|
|
131
|
+
"\n------------------------------------------------------------\n> Restore spending permission\n------------------------------------------------------------",
|
|
132
|
+
);
|
|
133
|
+
write(
|
|
134
|
+
`Safe: ${preview.policy.safe}\nPolicy ID: ${preview.policy.policyId}\nNetwork: eip155:${preview.policy.chainId}`,
|
|
135
|
+
);
|
|
136
|
+
const amount = (value) =>
|
|
137
|
+
expectedDeployment
|
|
138
|
+
? `${formatUnits(value, expectedDeployment.tokenDecimals)} ${expectedDeployment.tokenSymbol || "tokens"}`
|
|
139
|
+
: `${value} atomic units (token ${preview.policy.token})`;
|
|
140
|
+
if (expectedDeployment)
|
|
141
|
+
write(
|
|
142
|
+
`Remaining budget: ${amount(preview.remainingBudget)}\nPer-purchase limit: ${amount(preview.policy.maxAmount)}`,
|
|
143
|
+
);
|
|
144
|
+
write(`Expires: ${displaySeconds(preview.policy.validUntil)}`);
|
|
145
|
+
if (preview.remainingBudget === "0")
|
|
146
|
+
write(
|
|
147
|
+
"No budget remains. Restoring this identity does not enable new purchases.",
|
|
148
|
+
);
|
|
149
|
+
write("Use this existing permission. Its limits and expiry will not change.");
|
|
150
|
+
while (true) {
|
|
151
|
+
const value = (
|
|
152
|
+
await ask(
|
|
153
|
+
"Type 'confirm' to restore, 'details' for existing limits, or 'q' to return.",
|
|
154
|
+
)
|
|
155
|
+
).trim();
|
|
156
|
+
if (value === "q") throw Object.assign(Error(), { code: "CANCELLED" });
|
|
157
|
+
if (value === "details") {
|
|
158
|
+
write(
|
|
159
|
+
`Remaining budget: ${amount(preview.remainingBudget)}\nPer-purchase limit: ${amount(preview.policy.maxAmount)}`,
|
|
160
|
+
);
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
if (value === "confirm") break;
|
|
164
|
+
}
|
|
165
|
+
const result = await services.wallet(dataDir, "restorePolicy", { target });
|
|
166
|
+
write(
|
|
167
|
+
"Original Policy restored locally. No new authorization or payment was made.",
|
|
168
|
+
);
|
|
169
|
+
return result;
|
|
170
|
+
}
|
|
171
|
+
export async function runWalletRestore({
|
|
172
|
+
ask = askTerminal,
|
|
173
|
+
write = console.log,
|
|
174
|
+
resolveDirectory = resolveUserDataDir,
|
|
175
|
+
ops = {},
|
|
176
|
+
} = {}) {
|
|
177
|
+
try {
|
|
178
|
+
const dataDir = await resolveDirectory();
|
|
179
|
+
let local = await inspectLocalUserSetup(dataDir);
|
|
180
|
+
if (local.stage !== "initialized") {
|
|
181
|
+
write(
|
|
182
|
+
"First restore your original Session Key. Wallet will ask for secrets privately.",
|
|
183
|
+
);
|
|
184
|
+
await (ops.run || run)(
|
|
185
|
+
"setup-local.mjs",
|
|
186
|
+
["install", dataDir, "--restore"],
|
|
187
|
+
dataDir,
|
|
188
|
+
);
|
|
189
|
+
local = await inspectLocalUserSetup(dataDir);
|
|
190
|
+
if (local.stage !== "initialized") return 0;
|
|
191
|
+
}
|
|
192
|
+
const field = async (prompt, validate) => {
|
|
193
|
+
while (true) {
|
|
194
|
+
const value = (await ask(prompt + " ('q' to return)")).trim();
|
|
195
|
+
if (value === "q") throw Object.assign(Error(), { code: "CANCELLED" });
|
|
196
|
+
try {
|
|
197
|
+
return validate(value);
|
|
198
|
+
} catch {
|
|
199
|
+
write("Check this value and try again.");
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
};
|
|
203
|
+
write(
|
|
204
|
+
"Find your original Policy ID in Market → Wallets, or your saved agentpay wallet info.",
|
|
205
|
+
);
|
|
206
|
+
const chainId = await field("Network chain ID (for example 31337)", (v) => {
|
|
207
|
+
const n = Number(v);
|
|
208
|
+
if (!/^[1-9][0-9]*$/.test(v) || !Number.isSafeInteger(n)) throw Error();
|
|
209
|
+
return n;
|
|
210
|
+
});
|
|
211
|
+
const rpcUrl = await field("RPC address for this network", (v) => {
|
|
212
|
+
const u = new URL(v);
|
|
213
|
+
if (
|
|
214
|
+
u.username ||
|
|
215
|
+
u.password ||
|
|
216
|
+
u.search ||
|
|
217
|
+
u.hash ||
|
|
218
|
+
!(
|
|
219
|
+
u.protocol === "https:" ||
|
|
220
|
+
(u.protocol === "http:" &&
|
|
221
|
+
["127.0.0.1", "localhost", "[::1]"].includes(u.hostname))
|
|
222
|
+
)
|
|
223
|
+
)
|
|
224
|
+
throw Error();
|
|
225
|
+
return u.href;
|
|
226
|
+
});
|
|
227
|
+
const module = await field(
|
|
228
|
+
"AgentPay Module address from your saved settings",
|
|
229
|
+
getAddress,
|
|
230
|
+
),
|
|
231
|
+
safe = await field("Original Safe account address", getAddress),
|
|
232
|
+
policyId = await field("Original Policy ID", (v) => {
|
|
233
|
+
if (!/^0x[0-9a-f]{64}$/i.test(v) || /^0x0+$/.test(v)) throw Error();
|
|
234
|
+
return v.toLowerCase();
|
|
235
|
+
});
|
|
236
|
+
await restoreExistingPolicy({
|
|
237
|
+
dataDir,
|
|
238
|
+
target: { chainId, rpcUrl, module, safe, policyId },
|
|
239
|
+
ask,
|
|
240
|
+
write,
|
|
241
|
+
ops,
|
|
242
|
+
});
|
|
243
|
+
write(
|
|
244
|
+
"Restore finished. Use agentpay status to check services and retained Orders.",
|
|
245
|
+
);
|
|
246
|
+
return 0;
|
|
247
|
+
} catch (e) {
|
|
248
|
+
write(
|
|
249
|
+
restoreMessages[e.code] ||
|
|
250
|
+
e.message ||
|
|
251
|
+
"Restoration could not complete. Check the network and retry agentpay wallet restore; existing data was kept.",
|
|
252
|
+
);
|
|
253
|
+
return e.code === "CANCELLED" ? 0 : 1;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MPL-2.0
|
|
2
|
+
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
|
|
3
|
+
// If a copy was not distributed with this file, see https://mozilla.org/MPL/2.0/.
|
|
4
|
+
import { constants } from "node:fs";
|
|
5
|
+
import { open, opendir, lstat } from "node:fs/promises";
|
|
6
|
+
import { join } from "node:path";
|
|
7
|
+
import { createHash } from "node:crypto";
|
|
8
|
+
|
|
9
|
+
const hash = (value) => createHash("sha256").update(value).digest("hex");
|
|
10
|
+
const b32 = /^0x[0-9a-f]{64}$/;
|
|
11
|
+
// A display-only view. No Skill package selection, execution or payment is involved.
|
|
12
|
+
// Both durable result stores stay unchanged and excluded from attachment cleanup.
|
|
13
|
+
export function withResultCacheView(
|
|
14
|
+
operate,
|
|
15
|
+
dataDir,
|
|
16
|
+
{ statePath = join(dataDir, "runtime", "invocations.json") } = {},
|
|
17
|
+
) {
|
|
18
|
+
const clientDirectory = join(dataDir, "runtime", "client-requests");
|
|
19
|
+
async function privateJson(path, max = 2 * 1048576) {
|
|
20
|
+
let h;
|
|
21
|
+
try {
|
|
22
|
+
h = await open(path, constants.O_RDONLY | constants.O_NOFOLLOW);
|
|
23
|
+
const stat = await h.stat();
|
|
24
|
+
if (
|
|
25
|
+
!stat.isFile() ||
|
|
26
|
+
stat.nlink !== 1 ||
|
|
27
|
+
stat.size > max ||
|
|
28
|
+
(typeof process.getuid === "function" &&
|
|
29
|
+
(stat.uid !== process.getuid() || stat.mode & 0o077))
|
|
30
|
+
)
|
|
31
|
+
throw Error("Saved cache metadata needs inspection.");
|
|
32
|
+
const bytes = await h.readFile();
|
|
33
|
+
if (bytes.length > max) throw Error("Saved cache metadata is too large.");
|
|
34
|
+
return { value: JSON.parse(bytes.toString("utf8")), stat, bytes };
|
|
35
|
+
} finally {
|
|
36
|
+
await h?.close();
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
async function scan(directory, pattern, read) {
|
|
40
|
+
let st;
|
|
41
|
+
try {
|
|
42
|
+
st = await lstat(directory);
|
|
43
|
+
} catch (e) {
|
|
44
|
+
if (e.code === "ENOENT") return [];
|
|
45
|
+
throw e;
|
|
46
|
+
}
|
|
47
|
+
if (
|
|
48
|
+
!st.isDirectory() ||
|
|
49
|
+
st.isSymbolicLink() ||
|
|
50
|
+
(typeof process.getuid === "function" &&
|
|
51
|
+
(st.uid !== process.getuid() || st.mode & 0o077))
|
|
52
|
+
)
|
|
53
|
+
throw Error("Saved cache directory needs inspection.");
|
|
54
|
+
const rows = [];
|
|
55
|
+
for await (const entry of await opendir(directory))
|
|
56
|
+
if (pattern.test(entry.name))
|
|
57
|
+
rows.push(await read(join(directory, entry.name), entry.name));
|
|
58
|
+
return rows;
|
|
59
|
+
}
|
|
60
|
+
const source = (reference) =>
|
|
61
|
+
typeof reference?.profile?.providerId === "string"
|
|
62
|
+
? reference.profile.providerId
|
|
63
|
+
: undefined;
|
|
64
|
+
function row(file, result, id, requestId, providerId) {
|
|
65
|
+
if (!b32.test(result?.orderId || "") || result.output === undefined)
|
|
66
|
+
throw Error("Saved result needs inspection.");
|
|
67
|
+
return {
|
|
68
|
+
id,
|
|
69
|
+
kind: "result",
|
|
70
|
+
purpose: "output",
|
|
71
|
+
state: "available",
|
|
72
|
+
filename: "Result.json",
|
|
73
|
+
fileKey: "structured-result",
|
|
74
|
+
contentDigest: hash(JSON.stringify(result.output)),
|
|
75
|
+
contentBytes: Buffer.byteLength(JSON.stringify(result.output)),
|
|
76
|
+
bytes: file.bytes.length,
|
|
77
|
+
sha256: hash(file.bytes),
|
|
78
|
+
localPath: file.path,
|
|
79
|
+
requestId,
|
|
80
|
+
orderId: result.orderId,
|
|
81
|
+
providerId,
|
|
82
|
+
skillId: result.service?.skillId,
|
|
83
|
+
profileHash: result.service?.profileHash,
|
|
84
|
+
requestHash: result.requestHash,
|
|
85
|
+
outputHash: result.outputHash,
|
|
86
|
+
updatedAtMs: file.stat.mtimeMs,
|
|
87
|
+
protected: true,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
async function inventory() {
|
|
91
|
+
const clients = await scan(
|
|
92
|
+
clientDirectory,
|
|
93
|
+
/^[0-9a-f]{64}\.result\.json$/,
|
|
94
|
+
async (path, name) => {
|
|
95
|
+
const file = await privateJson(path),
|
|
96
|
+
v = file.value;
|
|
97
|
+
if (
|
|
98
|
+
v.version !== "agentpay.runtime-client-result.v1" ||
|
|
99
|
+
typeof v.requestId !== "string" ||
|
|
100
|
+
hash(v.requestId) + ".result.json" !== name ||
|
|
101
|
+
v.body?.ok !== true
|
|
102
|
+
)
|
|
103
|
+
throw Error("Saved result needs inspection.");
|
|
104
|
+
let reference;
|
|
105
|
+
try {
|
|
106
|
+
const c = (
|
|
107
|
+
await privateJson(path.replace(/\.result\.json$/, ".json"))
|
|
108
|
+
).value;
|
|
109
|
+
if (
|
|
110
|
+
c.version === "agentpay.runtime-client-request.v1" &&
|
|
111
|
+
c.requestId === v.requestId
|
|
112
|
+
)
|
|
113
|
+
reference = c.service;
|
|
114
|
+
} catch (e) {
|
|
115
|
+
if (e.code !== "ENOENT") throw e;
|
|
116
|
+
}
|
|
117
|
+
return row(
|
|
118
|
+
{ ...file, path },
|
|
119
|
+
v.body.result,
|
|
120
|
+
"result:" + name.slice(0, 64),
|
|
121
|
+
v.requestId,
|
|
122
|
+
source(reference),
|
|
123
|
+
);
|
|
124
|
+
},
|
|
125
|
+
);
|
|
126
|
+
const runtime = await scan(
|
|
127
|
+
statePath + ".results",
|
|
128
|
+
/^0x[0-9a-f]{64}\.json$/,
|
|
129
|
+
async (path, name) => {
|
|
130
|
+
const file = await privateJson(path),
|
|
131
|
+
v = file.value;
|
|
132
|
+
if (
|
|
133
|
+
v.version !== "agentpay.invocation-result.v1" ||
|
|
134
|
+
v.invocationId + ".json" !== name
|
|
135
|
+
)
|
|
136
|
+
throw Error("Saved Runtime result needs inspection.");
|
|
137
|
+
let reference;
|
|
138
|
+
try {
|
|
139
|
+
reference = JSON.parse(v.serviceBinding).reference;
|
|
140
|
+
} catch {}
|
|
141
|
+
return row(
|
|
142
|
+
{ ...file, path },
|
|
143
|
+
v.result,
|
|
144
|
+
"runtime-result:" + v.invocationId,
|
|
145
|
+
undefined,
|
|
146
|
+
source(reference),
|
|
147
|
+
);
|
|
148
|
+
},
|
|
149
|
+
);
|
|
150
|
+
const all = [...clients, ...runtime],
|
|
151
|
+
logical = new Map();
|
|
152
|
+
for (const r of all) {
|
|
153
|
+
// Merge only proven identical output for the same Order/service/request. Missing
|
|
154
|
+
// legacy identity stays separate rather than guessing that two records match.
|
|
155
|
+
const key = [
|
|
156
|
+
r.orderId,
|
|
157
|
+
r.skillId,
|
|
158
|
+
r.profileHash,
|
|
159
|
+
r.requestHash,
|
|
160
|
+
r.outputHash,
|
|
161
|
+
].every(Boolean)
|
|
162
|
+
? JSON.stringify([
|
|
163
|
+
r.orderId,
|
|
164
|
+
r.skillId,
|
|
165
|
+
r.profileHash,
|
|
166
|
+
r.requestHash,
|
|
167
|
+
r.outputHash,
|
|
168
|
+
r.contentDigest,
|
|
169
|
+
])
|
|
170
|
+
: r.id;
|
|
171
|
+
const previous = logical.get(key);
|
|
172
|
+
if (previous) {
|
|
173
|
+
previous.storageBytes += r.bytes;
|
|
174
|
+
previous.copyIds.push(r.id);
|
|
175
|
+
previous.providerId ??= r.providerId;
|
|
176
|
+
previous.requestIds.push(...(r.requestId ? [r.requestId] : []));
|
|
177
|
+
} else
|
|
178
|
+
logical.set(key, {
|
|
179
|
+
...r,
|
|
180
|
+
storageBytes: r.bytes,
|
|
181
|
+
copyIds: [r.id],
|
|
182
|
+
requestIds: r.requestId ? [r.requestId] : [],
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
return {
|
|
186
|
+
all,
|
|
187
|
+
rows: [...logical.values()],
|
|
188
|
+
bytes: all.reduce((n, r) => n + r.bytes, 0),
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
return async (body) => {
|
|
192
|
+
if (
|
|
193
|
+
["details", "verified"].includes(body.action) &&
|
|
194
|
+
/^(?:result|runtime-result):/.test(String(body.id))
|
|
195
|
+
) {
|
|
196
|
+
if (
|
|
197
|
+
!/^(?:result:[0-9a-f]{64}|runtime-result:0x[0-9a-f]{64})$/.test(body.id)
|
|
198
|
+
)
|
|
199
|
+
throw Error("Invalid cache ID.");
|
|
200
|
+
const inv = await inventory(),
|
|
201
|
+
found = inv.all.find((r) => r.id === body.id);
|
|
202
|
+
if (!found) throw Error("Cached result not found.");
|
|
203
|
+
return {
|
|
204
|
+
...found,
|
|
205
|
+
storageBytes: inv.rows.find((r) => r.copyIds.includes(body.id))
|
|
206
|
+
?.storageBytes,
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
if (body.action === "summary") {
|
|
210
|
+
const base = await operate(body),
|
|
211
|
+
results = await inventory();
|
|
212
|
+
return {
|
|
213
|
+
...base,
|
|
214
|
+
resultRecords: results.rows.length,
|
|
215
|
+
resultBytes: results.bytes,
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
if (body.action === "list") {
|
|
219
|
+
const { offset = 0, limit = 100 } = body;
|
|
220
|
+
if (
|
|
221
|
+
!Number.isSafeInteger(offset) ||
|
|
222
|
+
offset < 0 ||
|
|
223
|
+
!Number.isSafeInteger(limit) ||
|
|
224
|
+
limit < 1 ||
|
|
225
|
+
limit > 100
|
|
226
|
+
)
|
|
227
|
+
throw Error("Invalid cache page.");
|
|
228
|
+
const first = await operate({
|
|
229
|
+
...body,
|
|
230
|
+
offset: 0,
|
|
231
|
+
limit: Math.min(100, offset + limit),
|
|
232
|
+
}),
|
|
233
|
+
items = [...first.items];
|
|
234
|
+
// Only the first offset+limit attachments can occur in the merged page.
|
|
235
|
+
for (
|
|
236
|
+
let at = items.length;
|
|
237
|
+
at < Math.min(first.total, offset + limit);
|
|
238
|
+
|
|
239
|
+
) {
|
|
240
|
+
const page = await operate({
|
|
241
|
+
...body,
|
|
242
|
+
offset: at,
|
|
243
|
+
limit: Math.min(100, offset + limit - at),
|
|
244
|
+
});
|
|
245
|
+
if (!page.items.length) break;
|
|
246
|
+
items.push(...page.items);
|
|
247
|
+
at += page.items.length;
|
|
248
|
+
}
|
|
249
|
+
const results = await inventory();
|
|
250
|
+
for (const r of items) {
|
|
251
|
+
const matching = results.rows.find(
|
|
252
|
+
(v) =>
|
|
253
|
+
(r.orderId && v.orderId === r.orderId) ||
|
|
254
|
+
(r.requestId && v.requestIds.includes(r.requestId)),
|
|
255
|
+
);
|
|
256
|
+
r.providerId = matching?.providerId;
|
|
257
|
+
}
|
|
258
|
+
const text = results.rows.filter(
|
|
259
|
+
(r) =>
|
|
260
|
+
(!body.purpose || body.purpose === r.purpose) &&
|
|
261
|
+
(!body.state || body.state === r.state) &&
|
|
262
|
+
(!body.requestId || r.requestIds.includes(body.requestId)) &&
|
|
263
|
+
(!body.orderId || body.orderId === r.orderId),
|
|
264
|
+
);
|
|
265
|
+
items.push(...text);
|
|
266
|
+
items.sort(
|
|
267
|
+
(a, b) => b.updatedAtMs - a.updatedAtMs || a.id.localeCompare(b.id),
|
|
268
|
+
);
|
|
269
|
+
return {
|
|
270
|
+
total: first.total + text.length,
|
|
271
|
+
items: items.slice(offset, offset + limit),
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
return operate(body);
|
|
275
|
+
};
|
|
276
|
+
}
|