@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.
Files changed (173) hide show
  1. package/AGENTS.md +137 -0
  2. package/CONTRIBUTING.md +16 -0
  3. package/LICENSE +373 -0
  4. package/NOTICE.md +20 -0
  5. package/README.md +84 -2
  6. package/SECURITY.md +16 -0
  7. package/doc/README.md +52 -0
  8. package/doc/TODO.md +97 -0
  9. package/doc/architecture.md +96 -0
  10. package/doc/design.md +60 -0
  11. package/doc/develope.md +68 -0
  12. package/doc/plans/user-wallet-distribution.md +91 -0
  13. package/doc/reference/harness-skill-runtime.md +120 -0
  14. package/doc/reference/local-cache-foundation.md +176 -0
  15. package/doc/reference/local-safe-owner-ui.md +25 -0
  16. package/doc/reference/local-update.md +18 -0
  17. package/doc/reference/multi-service-runtime.md +119 -0
  18. package/doc/reference/npm-integration-verification.md +67 -0
  19. package/doc/reference/onboarding-writing.md +48 -0
  20. package/doc/reference/original-order-recovery.md +97 -0
  21. package/doc/reference/private-anvil-onboarding-config.example.json +18 -0
  22. package/doc/reference/public-deployment-settings.md +56 -0
  23. package/doc/reference/random-skill-contract.md +18 -0
  24. package/doc/reference/result-file-cache.md +166 -0
  25. package/doc/reference/runtime-address-recovery.md +42 -0
  26. package/doc/reference/runtime-result-cache.md +157 -0
  27. package/doc/reference/runtime-startup-recovery.md +141 -0
  28. package/doc/reference/skill-input-output-cache.md +93 -0
  29. package/doc/reference/wallet-identity-policy-history.md +23 -0
  30. package/doc/runbooks/commands.md +89 -0
  31. package/doc/runbooks/deployment-authorization-update.md +42 -0
  32. package/doc/runbooks/emergency-payment-stop.md +80 -0
  33. package/doc/runbooks/local-autostart.md +212 -0
  34. package/doc/runbooks/local-cache.md +115 -0
  35. package/doc/runbooks/local-update.md +84 -0
  36. package/doc/runbooks/market-connection.md +73 -0
  37. package/doc/runbooks/npm-publication.md +56 -0
  38. package/doc/runbooks/official-test-package.md +62 -0
  39. package/doc/runbooks/order-cancellation.md +56 -0
  40. package/doc/runbooks/order-support.md +41 -0
  41. package/doc/runbooks/policy-renewal.md +80 -0
  42. package/doc/runbooks/private-anvil-terminal-setup.md +58 -0
  43. package/doc/runbooks/runtime-skill-client.md +155 -0
  44. package/doc/runbooks/safe-owner-web.md +104 -0
  45. package/doc/runbooks/safe-policy-migration.md +36 -0
  46. package/doc/runbooks/safe-policy-setup.md +52 -0
  47. package/doc/runbooks/terminal-authorization-preview.md +38 -0
  48. package/doc/runbooks/unlock-wallet.md +68 -0
  49. package/doc/runbooks/user-onboarding.md +87 -0
  50. package/doc/runbooks/wallet-identity.md +59 -0
  51. package/doc/security/public-source-review-2026-11-06.md +71 -0
  52. package/doc/usage.md +54 -0
  53. package/package.json +49 -4
  54. package/progress.md +178 -0
  55. package/run +29 -0
  56. package/scripts/agentpay-cli.mjs +11 -0
  57. package/scripts/authorize-existing-safe.mjs +211 -0
  58. package/scripts/autostart.mjs +186 -0
  59. package/scripts/cancel-order.mjs +76 -0
  60. package/scripts/check-installation.mjs +26 -0
  61. package/scripts/check-official-test-package.mjs +35 -0
  62. package/scripts/check-purchase-readiness.mjs +275 -0
  63. package/scripts/configure-runtime-client.mjs +16 -0
  64. package/scripts/guide-policy.mjs +64 -0
  65. package/scripts/initialize-caller-credential.mjs +11 -0
  66. package/scripts/install-skill-bundle.mjs +176 -0
  67. package/scripts/install-user.mjs +24 -0
  68. package/scripts/local-service.mjs +60 -0
  69. package/scripts/lock.mjs +11 -0
  70. package/scripts/login-notify.mjs +39 -0
  71. package/scripts/migrate-safe-policy.mjs +165 -0
  72. package/scripts/official-paid-test.mjs +9 -0
  73. package/scripts/poll-unconfirmed-notice.mjs +75 -0
  74. package/scripts/prepare-onboarding.mjs +33 -0
  75. package/scripts/preview-policy.mjs +65 -0
  76. package/scripts/random-model-ack-smoke.mjs +235 -0
  77. package/scripts/random-reject-smoke.mjs +147 -0
  78. package/scripts/renew.mjs +9 -0
  79. package/scripts/repair.mjs +10 -0
  80. package/scripts/revoke.mjs +9 -0
  81. package/scripts/select-skill-bundle.mjs +39 -0
  82. package/scripts/setup-local.mjs +199 -0
  83. package/scripts/status.mjs +13 -0
  84. package/scripts/test-package.mjs +35 -0
  85. package/scripts/unlock.mjs +13 -0
  86. package/scripts/update.mjs +7 -0
  87. package/scripts/user-setup.mjs +219 -0
  88. package/scripts/verify-npm-release.mjs +32 -0
  89. package/src/active-skill.mjs +160 -0
  90. package/src/anvil-onboarding-state.mjs +96 -0
  91. package/src/anvil-onboarding.mjs +1036 -0
  92. package/src/authorization-question-checkpoint.mjs +114 -0
  93. package/src/autostart-files.mjs +50 -0
  94. package/src/cache-management-cli.mjs +302 -0
  95. package/src/cache-management.mjs +47 -0
  96. package/src/caller-auth.mjs +58 -0
  97. package/src/client-request-lock.mjs +44 -0
  98. package/src/connect-market.mjs +408 -0
  99. package/src/demo-unlock-secret.mjs +64 -0
  100. package/src/deployment-status.mjs +198 -0
  101. package/src/file-cache-cli.mjs +83 -0
  102. package/src/file-cache-owner.mjs +74 -0
  103. package/src/file-cache-settings.mjs +50 -0
  104. package/src/file-cache.mjs +790 -0
  105. package/src/file-download.mjs +281 -0
  106. package/src/finalization-disclosure.mjs +16 -0
  107. package/src/guided-setup.mjs +194 -0
  108. package/src/index.mjs +1231 -0
  109. package/src/install-and-setup.mjs +38 -0
  110. package/src/install-preflight.mjs +201 -0
  111. package/src/install-user.mjs +279 -0
  112. package/src/installation-paths.mjs +31 -0
  113. package/src/invocation-result.mjs +148 -0
  114. package/src/invocation-store.mjs +385 -0
  115. package/src/local-data-directory.mjs +117 -0
  116. package/src/local-info.mjs +181 -0
  117. package/src/local-services.mjs +156 -0
  118. package/src/local-time.mjs +36 -0
  119. package/src/local-update.mjs +306 -0
  120. package/src/local-wallet-ipc.mjs +93 -0
  121. package/src/login-reminder.mjs +28 -0
  122. package/src/market-download-url.mjs +21 -0
  123. package/src/node-version.mjs +12 -0
  124. package/src/npm-update.mjs +126 -0
  125. package/src/official-paid-test.mjs +407 -0
  126. package/src/official-test-package.mjs +41 -0
  127. package/src/official-test-update.mjs +162 -0
  128. package/src/order-cancellation.mjs +241 -0
  129. package/src/order-closure.mjs +42 -0
  130. package/src/order-list-panel.mjs +110 -0
  131. package/src/order-review.mjs +424 -0
  132. package/src/order-status-panel.mjs +65 -0
  133. package/src/order-support.mjs +365 -0
  134. package/src/owner-approval-server.mjs +332 -0
  135. package/src/owner-signer.mjs +67 -0
  136. package/src/policy-info.mjs +38 -0
  137. package/src/policy-migration.mjs +84 -0
  138. package/src/policy-operations.mjs +67 -0
  139. package/src/policy-preview-server.mjs +236 -0
  140. package/src/policy-terminal-guide.mjs +377 -0
  141. package/src/prepare-local-services.mjs +143 -0
  142. package/src/prepare-onboarding.mjs +210 -0
  143. package/src/private-journal-files.mjs +29 -0
  144. package/src/readiness-profile.mjs +30 -0
  145. package/src/renew-policy.mjs +605 -0
  146. package/src/repair-market-wallet.mjs +137 -0
  147. package/src/restore-wallet.mjs +255 -0
  148. package/src/result-cache-view.mjs +276 -0
  149. package/src/runtime-client.mjs +1145 -0
  150. package/src/runtime-connection-settings.mjs +101 -0
  151. package/src/runtime-recovery.mjs +667 -0
  152. package/src/runtime-status.mjs +32 -0
  153. package/src/safe-owner-anvil.mjs +115 -0
  154. package/src/safe-policy-plan.mjs +195 -0
  155. package/src/service-reference.mjs +233 -0
  156. package/src/service-url.mjs +52 -0
  157. package/src/settlement-response.mjs +69 -0
  158. package/src/setup-autostart.mjs +197 -0
  159. package/src/setup-demo.mjs +152 -0
  160. package/src/skill-bundle.mjs +142 -0
  161. package/src/skill-client.mjs +198 -0
  162. package/src/skill-install-prompt.mjs +31 -0
  163. package/src/skill-package.mjs +50 -0
  164. package/src/skill-zip.mjs +220 -0
  165. package/src/status-panel.mjs +589 -0
  166. package/src/transport.mjs +48 -0
  167. package/src/uninstall-local.mjs +299 -0
  168. package/src/user-cli.mjs +354 -0
  169. package/src/user-setup-guide.mjs +193 -0
  170. package/src/wallet-control.mjs +182 -0
  171. package/src/wallet-ipc-error.mjs +16 -0
  172. package/src/wallet-package.mjs +22 -0
  173. package/update-pair.json +6 -0
@@ -0,0 +1,35 @@
1
+ #!/usr/bin/env node
2
+ // SPDX-License-Identifier: MPL-2.0
3
+ // Verify the archive consumers receive, without a sibling repository or local Wallet build.
4
+ import {execFileSync} from 'node:child_process';
5
+ import {mkdtemp,mkdir,readFile,copyFile,rm} from 'node:fs/promises';
6
+ import {tmpdir} from 'node:os';
7
+ import {dirname,join,resolve} from 'node:path';
8
+ import {fileURLToPath} from 'node:url';
9
+ import {createHash} from 'node:crypto';
10
+ const source=fileURLToPath(new URL('..',import.meta.url));
11
+ const output=resolve(process.argv[2] || 'artifacts/npm');
12
+ await mkdir(output,{recursive:true});
13
+ const root=await mkdtemp(join(tmpdir(),'agentpay-user-package-'));
14
+ const env={...process.env,HOME:join(root,'home'),XDG_STATE_HOME:join(root,'state'),PATH:dirname(process.execPath)+':'+process.env.PATH};
15
+ for(const name of Object.keys(env)) if(name.startsWith('AGENTPAY_') || ['NODE_OPTIONS','NODE_PATH'].includes(name)) delete env[name];
16
+ const run=(file,args,cwd)=>execFileSync(file,args,{cwd,env,encoding:'utf8',timeout:180000,maxBuffer:8*1024*1024});
17
+ try {
18
+ await mkdir(env.HOME,{mode:0o700});
19
+ const packed=JSON.parse(run('npm',['pack','--ignore-scripts','--json','--pack-destination',output],source))[0];
20
+ const paths=packed.files.map(x=>x.path);
21
+ for(const name of ['LICENSE','NOTICE.md','SECURITY.md','scripts/agentpay-cli.mjs','scripts/update.mjs','src/index.mjs'])
22
+ if(!paths.includes(name))throw Error('Missing package file: '+name);
23
+ if(paths.some(x=>x.startsWith('test/') || x.startsWith('node_modules/') || /(?:^|\/)\.env(?:\.|$)|\.keystore\.json$|\.token$|\.log$/.test(x)))throw Error('Unexpected private/test material in archive');
24
+ const archive=join(output,packed.filename),consumer=join(root,'consumer');
25
+ await mkdir(consumer);
26
+ run('npm',['install','--ignore-scripts','--no-audit','--no-fund',archive],consumer);
27
+ const installed=join(consumer,'node_modules/@c4roa0/agentpay-user');
28
+ const help=run(join(consumer,'node_modules/.bin/agentpay'),['help'],consumer);
29
+ if(!help.includes('update [-f]') || !help.includes('policy help'))throw Error('Installed command unavailable');
30
+ await mkdir(join(installed,'test'));
31
+ await copyFile(join(source,'test/local-services-process.test.mjs'),join(installed,'test/local-services-process.test.mjs'));
32
+ console.log(run(process.execPath,['--test','test/local-services-process.test.mjs'],installed));
33
+ console.log('Verified archive: '+archive);
34
+ console.log('SHA256: '+createHash('sha256').update(await readFile(archive)).digest('hex'));
35
+ } finally {await rm(root,{recursive:true,force:true});}
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env node
2
+ // SPDX-License-Identifier: MPL-2.0
3
+ // This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
4
+ // If a copy was not distributed with this file, see https://mozilla.org/MPL/2.0/.
5
+ import { controlWallet } from "../src/wallet-control.mjs";
6
+ try {
7
+ process.exitCode = await controlWallet("unlock");
8
+ } catch {
9
+ console.error(
10
+ "Could not start Wallet unlock. Check your local installation.",
11
+ );
12
+ process.exitCode = 1;
13
+ }
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env node
2
+ // SPDX-License-Identifier: MPL-2.0
3
+ import { updateFromNpm } from '../src/npm-update.mjs';
4
+ const args=process.argv.slice(2);
5
+ if(args.length > 1 || (args.length === 1 && args[0] !== '-f')) {
6
+ console.error('Usage: agentpay update [-f]'); process.exitCode=2;
7
+ } else process.exitCode=await updateFromNpm({force:args[0] === '-f'});
@@ -0,0 +1,219 @@
1
+ #!/usr/bin/env node
2
+ // SPDX-License-Identifier: MPL-2.0
3
+ // This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
4
+ // If a copy was not distributed with this file, see https://mozilla.org/MPL/2.0/.
5
+ import {
6
+ confirmDemoUnlockSecret,
7
+ readHiddenTerminal,
8
+ } from "../src/demo-unlock-secret.mjs";
9
+ import { createInterface } from "node:readline/promises";
10
+ import { readFile } from "node:fs/promises";
11
+ import { isAbsolute } from "node:path";
12
+ import { fileURLToPath } from "node:url";
13
+ import { spawn } from "node:child_process";
14
+ import { checkLocalInstallation } from "../src/install-preflight.mjs";
15
+ import {
16
+ inspectLocalUserSetup,
17
+ runUserSetupGuide,
18
+ } from "../src/user-setup-guide.mjs";
19
+ try {
20
+ const args = process.argv.slice(2),
21
+ demo = args[0] === "--demo";
22
+ if (args[0] === "--guided") {
23
+ if (args.length > 2 || (args[1] && !isAbsolute(args[1])))
24
+ throw Error("Usage: user-setup.mjs --guided [ABSOLUTE_PRIVATE_DATA_DIR]");
25
+ if (!process.stdin.isTTY)
26
+ throw Error("Run the setup guide in an interactive terminal.");
27
+ const { resolveUserDataDir } = await import(
28
+ "../src/local-data-directory.mjs"
29
+ );
30
+ const { runGuidedEntry } = await import("../src/guided-setup.mjs");
31
+ process.exit(
32
+ await runGuidedEntry({
33
+ userDir: fileURLToPath(new URL("..", import.meta.url)),
34
+ dataDir: await resolveUserDataDir({ argument: args[1] }),
35
+ }),
36
+ );
37
+ }
38
+
39
+ if (["--config", "--anvil"].includes(args[0])) {
40
+ if (args.length !== 3 || !isAbsolute(args[1]) || !isAbsolute(args[2]))
41
+ throw Error(
42
+ "Usage: user-setup.mjs --config ABSOLUTE_PRIVATE_DATA_DIR ABSOLUTE_CONFIG_JSON",
43
+ );
44
+ if (!process.stdin.isTTY)
45
+ throw Error("Run the setup guide in an interactive terminal.");
46
+ const preflight = await checkLocalInstallation({
47
+ userDir: fileURLToPath(new URL("..", import.meta.url)),
48
+ dataDir: args[1],
49
+ });
50
+ if (!preflight.ok) {
51
+ for (const item of preflight.checks.filter(
52
+ (item) => item.status === "failed",
53
+ ))
54
+ console.error(`${item.message}. ${item.fix || ""}`);
55
+ process.exitCode = 1;
56
+ } else {
57
+ const { runAnvilOnboarding } = await import(
58
+ "../src/anvil-onboarding.mjs"
59
+ );
60
+ const ask = async (prompt) => {
61
+ const terminal = createInterface({
62
+ input: process.stdin,
63
+ output: process.stderr,
64
+ });
65
+ try {
66
+ return await terminal.question(prompt);
67
+ } finally {
68
+ terminal.close();
69
+ }
70
+ };
71
+ const config = JSON.parse(await readFile(args[2], "utf8"));
72
+ if (
73
+ args[0] === "--anvil" &&
74
+ (config.mode !== "private-anvil" || config.chainId !== 31337)
75
+ )
76
+ throw Error("PRIVATE_ANVIL_CONFIG_REQUIRED");
77
+ const result = await runAnvilOnboarding({
78
+ dataDir: args[1],
79
+ config,
80
+ ask,
81
+ write: (value) => process.stdout.write(`${value}\n`),
82
+ });
83
+ process.exit(
84
+ result?.completed === false && result.stage !== "quit" ? 1 : 0,
85
+ );
86
+ }
87
+ if (!preflight.ok) process.exit(1);
88
+ }
89
+ if (
90
+ demo
91
+ ? args.length !== 1
92
+ : !args[0] || !isAbsolute(args[0]) || args.length > 2
93
+ )
94
+ throw Error(
95
+ "Usage: user-setup.mjs --demo | ABSOLUTE_PRIVATE_DATA_DIR [PUBLIC_DEPLOYMENT_JSON]",
96
+ );
97
+ if (!process.stdin.isTTY)
98
+ throw Error("Run the setup guide in an interactive terminal.");
99
+ const ask = async (prompt) => {
100
+ const terminal = createInterface({
101
+ input: process.stdin,
102
+ output: process.stderr,
103
+ });
104
+ try {
105
+ return await terminal.question(prompt);
106
+ } finally {
107
+ terminal.close();
108
+ }
109
+ };
110
+ const write = (text) => process.stdout.write(`${text}\n`);
111
+ let demoInitialized = false;
112
+ await runUserSetupGuide({
113
+ demo,
114
+ ask,
115
+ write,
116
+ check: () =>
117
+ demo
118
+ ? { ok: true }
119
+ : checkLocalInstallation({
120
+ userDir: fileURLToPath(new URL("..", import.meta.url)),
121
+ dataDir: args[0],
122
+ }),
123
+ state: () =>
124
+ demo
125
+ ? {
126
+ stage: demoInitialized ? "initialized" : "not_initialized",
127
+ delegate: demoInitialized ? `0x${"5".repeat(40)}` : null,
128
+ policyStatus: "not_configured",
129
+ runtimeStatus: "not_checked",
130
+ }
131
+ : inspectLocalUserSetup(args[0]),
132
+ initialize: async () => {
133
+ if (demo) {
134
+ write(
135
+ "\n------------------------------------------------------------\nSet your Wallet unlock secret\n------------------------------------------------------------",
136
+ );
137
+ await confirmDemoUnlockSecret({
138
+ readHidden: readHiddenTerminal,
139
+ write,
140
+ });
141
+ write(
142
+ "\n------------------------------------------------------------\nPrepare local runtime access\n------------------------------------------------------------\n Real setup saves a local access credential automatically.\n Demo: skipped; no credential or file is created.",
143
+ );
144
+ demoInitialized = true;
145
+ return;
146
+ }
147
+ const child = spawn(
148
+ process.execPath,
149
+ [
150
+ fileURLToPath(new URL("./setup-local.mjs", import.meta.url)),
151
+ "install",
152
+ args[0],
153
+ ],
154
+ { stdio: "inherit" },
155
+ );
156
+ const code = await new Promise((resolve, reject) => {
157
+ child.once("error", reject);
158
+ child.once("exit", resolve);
159
+ });
160
+ if (code !== 0)
161
+ throw Error(
162
+ "Initialization did not finish. Existing state was preserved; inspect and resume.",
163
+ );
164
+ },
165
+ preview: async (delegate, options = {}) => {
166
+ if (!demo && !args[1]) {
167
+ write(
168
+ "Provide a trusted public deployment JSON as the second argument to preview authorization. No Policy was changed.",
169
+ );
170
+ return;
171
+ }
172
+ const { runPolicyTerminalGuide } = await import(
173
+ "../src/policy-terminal-guide.mjs"
174
+ );
175
+ const deployment = demo
176
+ ? {
177
+ chainId: 31337,
178
+ rpcUrl: "http://127.0.0.1:8545",
179
+ module: `0x${"1".repeat(40)}`,
180
+ token: `0x${"2".repeat(40)}`,
181
+ factory: `0x${"3".repeat(40)}`,
182
+ registry: `0x${"4".repeat(40)}`,
183
+ tokenDecimals: 6,
184
+ source: "Demo data, not an actual deployment",
185
+ }
186
+ : JSON.parse(await readFile(args[1], "utf8"));
187
+ return await runPolicyTerminalGuide({
188
+ deployment,
189
+ delegate,
190
+ demo,
191
+ ask,
192
+ write,
193
+ ...options,
194
+ inspect: async (plan) => {
195
+ const { JsonRpcProvider } = await import("ethers");
196
+ const { inspectSafePolicy } = await import(
197
+ "../src/safe-policy-plan.mjs"
198
+ );
199
+ const rpc = new JsonRpcProvider(plan.rpcUrl);
200
+ let timer;
201
+ try {
202
+ return await Promise.race([
203
+ inspectSafePolicy(rpc, plan),
204
+ new Promise((_, reject) => {
205
+ timer = setTimeout(() => reject(Error("timeout")), 10000);
206
+ }),
207
+ ]);
208
+ } finally {
209
+ clearTimeout(timer);
210
+ rpc.destroy();
211
+ }
212
+ },
213
+ });
214
+ },
215
+ });
216
+ } catch (error) {
217
+ console.error(error.message);
218
+ process.exitCode = 1;
219
+ }
@@ -0,0 +1,32 @@
1
+ // Check public registry data only. Never retries publication after an uncertain result.
2
+ import { readFile } from 'node:fs/promises';
3
+ import { createHash } from 'node:crypto';
4
+ import { setTimeout as delay } from 'node:timers/promises';
5
+
6
+ const [archive, version] = process.argv.slice(2);
7
+ if (!archive || !/^\d+\.\d+\.\d+$/.test(version ?? '')) {
8
+ throw new Error('Usage: node scripts/verify-npm-release.mjs ARCHIVE VERSION');
9
+ }
10
+ const integrity = 'sha512-' + createHash('sha512')
11
+ .update(await readFile(archive)).digest('base64');
12
+ const url = `https://registry.npmjs.org/@c4roa0%2fagentpay-user/${version}`;
13
+ for (let attempt = 1; attempt <= 10; attempt++) {
14
+ let metadata;
15
+ try {
16
+ const response = await fetch(url, { signal: AbortSignal.timeout(15000) });
17
+ if (response.ok) metadata = await response.json();
18
+ else console.log(`Registry check ${attempt}/10: HTTP ${response.status}`);
19
+ } catch {
20
+ console.log(`Registry check ${attempt}/10: request unavailable`);
21
+ }
22
+ if (metadata) {
23
+ if (metadata.name !== '@c4roa0/agentpay-user' || metadata.version !== version ||
24
+ metadata.dist?.integrity !== integrity) {
25
+ throw new Error('Public package identity or integrity differs from the built archive.');
26
+ }
27
+ console.log(`Verified public package @c4roa0/agentpay-user@${version}.`);
28
+ process.exit(0);
29
+ }
30
+ if (attempt < 10) await delay(Math.min(5000 * 2 ** (attempt - 1), 60000));
31
+ }
32
+ throw new Error('Publication may already be accepted. Registry verification is pending; do not republish.');
@@ -0,0 +1,160 @@
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 {
5
+ readFile,
6
+ lstat,
7
+ realpath,
8
+ writeFile,
9
+ rename,
10
+ unlink,
11
+ } from "node:fs/promises";
12
+ import { join, resolve, dirname } from "node:path";
13
+ import { randomUUID } from "node:crypto";
14
+ import {
15
+ parseSkillBundle,
16
+ bundleDigest,
17
+ skillArchiveLimits,
18
+ } from "./skill-bundle.mjs";
19
+ import { parseServicePackage } from "./skill-package.mjs";
20
+ async function privateFile(path, maxBytes = 262144) {
21
+ const st = await lstat(path);
22
+ if (
23
+ !st.isFile() ||
24
+ st.isSymbolicLink() ||
25
+ st.uid !== process.getuid() ||
26
+ st.mode & 0o077
27
+ )
28
+ throw new Error("SKILL_FILE_PERMISSIONS_INVALID");
29
+ if (st.size > maxBytes) throw new Error("SKILL_FILE_TOO_LARGE");
30
+ return readFile(path);
31
+ }
32
+ export async function inspectInstalledBundle(directory) {
33
+ const dir = resolve(directory);
34
+ if ((await realpath(dir)) !== dir)
35
+ throw new Error("SKILL_SYMLINK_PATH_INVALID");
36
+ const original = parseSkillBundle(
37
+ await privateFile(
38
+ join(dir, "publisher.zip"),
39
+ skillArchiveLimits.compressed,
40
+ ),
41
+ );
42
+ const receipt = JSON.parse(await privateFile(join(dir, "installation.json")));
43
+ if (
44
+ receipt.format !== "agentpay.skill-installation.v1" ||
45
+ receipt.sha256 !== original.sha256
46
+ )
47
+ throw new Error("SKILL_INSTALLATION_RECEIPT_MISMATCH");
48
+ const files = {};
49
+ const hashes = {};
50
+ let locallyModified = false;
51
+ for (const name of Object.keys(original.files)) {
52
+ const path = join(dir, name);
53
+ if ((await realpath(path)) !== path)
54
+ throw new Error("SKILL_SYMLINK_PATH_INVALID");
55
+ files[name] = await privateFile(path, skillArchiveLimits.file);
56
+ hashes[name] = bundleDigest(files[name]);
57
+ if (!files[name].equals(original.files[name])) locallyModified = true;
58
+ }
59
+ const manifest = JSON.parse(files[original.root + "agentpay.json"]);
60
+ if (JSON.stringify(manifest) !== JSON.stringify(original.manifest))
61
+ throw Error("SKILL_MANIFEST_INVALID");
62
+ const reference = original.services.main;
63
+ if (!reference) throw Error("TEST_SERVICE_REFERENCE_REQUIRED");
64
+ const service = parseServicePackage(Buffer.from(JSON.stringify(reference)));
65
+ return {
66
+ directory: dir,
67
+ sha256: original.sha256,
68
+ locallyModified,
69
+ hashes,
70
+ service,
71
+ publication: receipt.publication ?? null,
72
+ };
73
+ }
74
+ export async function loadActiveSkill(path) {
75
+ const selected = JSON.parse(await privateFile(path));
76
+ if (
77
+ selected.version !== "agentpay.active-skill.v1" ||
78
+ typeof selected.directory !== "string"
79
+ )
80
+ throw new Error("SKILL_SELECTION_INVALID");
81
+ const current = await inspectInstalledBundle(selected.directory);
82
+ if (
83
+ current.sha256 !== selected.sha256 ||
84
+ JSON.stringify(current.hashes) !== JSON.stringify(selected.hashes)
85
+ )
86
+ throw new Error("SKILL_CHANGED_RESELECT_REQUIRED");
87
+ return {
88
+ ...current.service,
89
+ bundle: {
90
+ directory: current.directory,
91
+ sha256: current.sha256,
92
+ filesHash: bundleDigest(Buffer.from(JSON.stringify(current.hashes))),
93
+ locallyModified: current.locallyModified,
94
+ },
95
+ };
96
+ }
97
+ export async function selectInstalledBundle(
98
+ dataDir,
99
+ directory,
100
+ expectedHashes = null,
101
+ ) {
102
+ const runtime = join(resolve(dataDir), "runtime");
103
+ const dir = resolve(directory);
104
+ if (dirname(dir) !== join(runtime, "skill-bundles"))
105
+ throw new Error("SKILL_NOT_IN_USER_INSTALL_DIRECTORY");
106
+ const info = await lstat(runtime);
107
+ if (
108
+ !info.isDirectory() ||
109
+ info.isSymbolicLink() ||
110
+ info.uid !== process.getuid() ||
111
+ info.mode & 0o077
112
+ )
113
+ throw new Error("PRIVATE_RUNTIME_REQUIRED");
114
+ const current = await inspectInstalledBundle(dir);
115
+ if (
116
+ expectedHashes &&
117
+ JSON.stringify(expectedHashes) !== JSON.stringify(current.hashes)
118
+ )
119
+ throw new Error("SKILL_CHANGED_DURING_CONFIRMATION");
120
+ const selected = {
121
+ version: "agentpay.active-skill.v1",
122
+ directory: dir,
123
+ sha256: current.sha256,
124
+ hashes: current.hashes,
125
+ };
126
+ const path = join(runtime, "active-skill.json");
127
+ let existing = null;
128
+ try {
129
+ existing = JSON.parse(await privateFile(path));
130
+ } catch (e) {
131
+ if (e.code !== "ENOENT") throw e;
132
+ }
133
+ if (JSON.stringify(existing) === JSON.stringify(selected))
134
+ return { changed: false, path, ...current };
135
+ // This file selects only the official test material. Runtime binds each call
136
+ // independently. Never replace material while a test checkpoint still refers to it.
137
+ try {
138
+ await lstat(join(runtime, "model-ack-smoke.json"));
139
+ throw Error("TEST_SELECTION_REQUIRES_CHECKPOINT_REVIEW");
140
+ } catch (e) {
141
+ if (e.code !== "ENOENT") throw e;
142
+ }
143
+ const temp = `${path}.${randomUUID()}.tmp`;
144
+ try {
145
+ await writeFile(temp, JSON.stringify(selected) + "\n", {
146
+ flag: "wx",
147
+ mode: 0o600,
148
+ });
149
+ await rename(temp, path);
150
+ } finally {
151
+ await unlink(temp).catch((e) => {
152
+ if (e.code !== "ENOENT") throw e;
153
+ });
154
+ }
155
+ return { changed: true, path, ...current };
156
+ }
157
+ // Retained checkpoint filename selects official test material only.
158
+ export async function installedSkillPath(dataDir) {
159
+ return join(dataDir, "runtime", "active-skill.json");
160
+ }
@@ -0,0 +1,96 @@
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 { createHash } from "node:crypto";
5
+ import { hashPrivateRequest } from "@c4roa0/agentpay-protocol";
6
+
7
+ // These are local guide checks, not a new Policy or payment authority.
8
+ export function sameWalletPolicy(actual, expected) {
9
+ if (!actual || !expected) return false;
10
+ const keys = Object.keys(expected);
11
+ return (
12
+ Object.keys(actual).length === keys.length &&
13
+ keys.every((key) => {
14
+ if (key === "registryRpcUrl")
15
+ return new URL(actual[key]).href === new URL(expected[key]).href;
16
+ if (typeof expected[key] === "string" && expected[key].startsWith("0x")) {
17
+ return (
18
+ typeof actual[key] === "string" &&
19
+ actual[key].toLowerCase() === expected[key].toLowerCase()
20
+ );
21
+ }
22
+ return actual[key] === expected[key];
23
+ })
24
+ );
25
+ }
26
+ export function validateGuideTestCheckpoint(checkpoint, history, skill) {
27
+ if (!checkpoint) return null;
28
+ const b32 = (value) => /^0x[0-9a-f]{64}$/.test(value || "");
29
+ if (
30
+ checkpoint.version !== "agentpay.model-ack-smoke.v1" ||
31
+ !/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/.test(
32
+ checkpoint.key || "",
33
+ ) ||
34
+ typeof checkpoint.confirmed !== "boolean" ||
35
+ !(checkpoint.orderId === null || b32(checkpoint.orderId)) ||
36
+ !(checkpoint.outputHash === null || b32(checkpoint.outputHash)) ||
37
+ (checkpoint.confirmed &&
38
+ (!b32(checkpoint.orderId) || !b32(checkpoint.outputHash)))
39
+ ) {
40
+ throw Error("GUIDE_TEST_CHECKPOINT_INVALID");
41
+ }
42
+ if (!skill) throw Error("GUIDE_TEST_SKILL_REQUIRED");
43
+ if (
44
+ !skill.reference ||
45
+ skill.reference.skillId !== skill.skillId ||
46
+ skill.reference.profileHash !== skill.profileHash
47
+ )
48
+ throw Error("GUIDE_TEST_SKILL_HISTORY_MISMATCH");
49
+ if (checkpoint.routingVersion !== "agentpay.runtime-call.v1")
50
+ throw Error("GUIDE_LEGACY_TEST_REVIEW_REQUIRED");
51
+ const id = `0x${createHash("sha256").update(`agentpay.user-invocation.v1:${checkpoint.key}`).digest("hex")}`;
52
+ if (
53
+ history &&
54
+ (history.version !== "agentpay.user-invocations.v1" ||
55
+ !history.invocations ||
56
+ typeof history.invocations !== "object" ||
57
+ Array.isArray(history.invocations))
58
+ )
59
+ throw Error("GUIDE_INVOCATION_HISTORY_INVALID");
60
+ const entry = history?.invocations?.[id];
61
+ if (checkpoint.orderId && (!entry || entry.orderId !== checkpoint.orderId))
62
+ throw Error("GUIDE_TEST_ORDER_HISTORY_MISMATCH");
63
+ if (entry) {
64
+ const binding = JSON.stringify({
65
+ version: "agentpay.runtime-service.v1",
66
+ reference: skill.reference,
67
+ requestedMaxUsageUnits: "1",
68
+ });
69
+ if (
70
+ entry.invocationId !== id ||
71
+ entry.requestHash !== hashPrivateRequest({ min: 10, max: 99 }) ||
72
+ entry.serviceBinding !== binding
73
+ )
74
+ throw Error("GUIDE_TEST_SKILL_HISTORY_MISMATCH");
75
+ }
76
+ return { ...checkpoint, hasOrder: Boolean(entry?.orderId) };
77
+ }
78
+ export function guideNextAction({
79
+ initialized,
80
+ plan,
81
+ chain,
82
+ bound,
83
+ installed,
84
+ liveWallet,
85
+ liveRuntime,
86
+ checked,
87
+ test,
88
+ }) {
89
+ if (!initialized) return "initialize";
90
+ if (!plan) return "limits";
91
+ if (!chain?.moduleEnabled || !chain?.policyMatches) return "approve";
92
+ if (!bound) return "bind";
93
+ if (!liveWallet || liveWallet.locked || !liveRuntime) return "start";
94
+ if (!installed) return "install";
95
+ return checked || test ? "test" : "check";
96
+ }