@c4roa0/agentpay-user 0.0.0-stage → 0.3.1

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 (175) 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 +85 -2
  6. package/SECURITY.md +16 -0
  7. package/doc/README.md +52 -0
  8. package/doc/TODO.md +107 -0
  9. package/doc/architecture.md +99 -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 +103 -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 +103 -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 +210 -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 +1233 -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 +316 -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/protocol-package.mjs +25 -0
  145. package/src/readiness-profile.mjs +30 -0
  146. package/src/renew-policy.mjs +605 -0
  147. package/src/repair-market-wallet.mjs +137 -0
  148. package/src/restore-wallet.mjs +255 -0
  149. package/src/result-cache-view.mjs +276 -0
  150. package/src/runtime-client.mjs +1145 -0
  151. package/src/runtime-connection-settings.mjs +101 -0
  152. package/src/runtime-recovery.mjs +667 -0
  153. package/src/runtime-status.mjs +32 -0
  154. package/src/safe-owner-anvil.mjs +115 -0
  155. package/src/safe-policy-plan.mjs +195 -0
  156. package/src/service-reference.mjs +233 -0
  157. package/src/service-url.mjs +52 -0
  158. package/src/settlement-response.mjs +69 -0
  159. package/src/setup-autostart.mjs +197 -0
  160. package/src/setup-demo.mjs +152 -0
  161. package/src/skill-bundle.mjs +142 -0
  162. package/src/skill-client.mjs +198 -0
  163. package/src/skill-install-prompt.mjs +31 -0
  164. package/src/skill-package.mjs +50 -0
  165. package/src/skill-zip.mjs +220 -0
  166. package/src/software-versions.mjs +48 -0
  167. package/src/status-panel.mjs +603 -0
  168. package/src/transport.mjs +48 -0
  169. package/src/uninstall-local.mjs +299 -0
  170. package/src/user-cli.mjs +354 -0
  171. package/src/user-setup-guide.mjs +193 -0
  172. package/src/wallet-control.mjs +182 -0
  173. package/src/wallet-ipc-error.mjs +16 -0
  174. package/src/wallet-package.mjs +28 -0
  175. package/update-pair.json +6 -0
@@ -0,0 +1,162 @@
1
+ import { deploymentFilePath, importSetupFiles } from "./installation-paths.mjs";
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 { constants } from "node:fs";
6
+ import { open } from "node:fs/promises";
7
+ import { dirname, join } from "node:path";
8
+ import {
9
+ OFFICIAL_TEST_PACKAGE,
10
+ verifyOfficialTestBytes,
11
+ verifyOfficialTestDigest,
12
+ } from "./official-test-package.mjs";
13
+ import { marketDownloadUrl } from "./market-download-url.mjs";
14
+
15
+ async function localJson(path) {
16
+ let handle;
17
+ try {
18
+ handle = await open(path, constants.O_RDONLY | constants.O_NOFOLLOW);
19
+ const info = await handle.stat();
20
+ if (!info.isFile() || info.size > 65536)
21
+ throw Error("Invalid saved configuration");
22
+ return JSON.parse(await handle.readFile("utf8"));
23
+ } catch (e) {
24
+ if (e.code === "ENOENT") return null;
25
+ throw e;
26
+ } finally {
27
+ await handle?.close();
28
+ }
29
+ }
30
+ function url(value) {
31
+ const u = new URL(value);
32
+ if (
33
+ u.username ||
34
+ u.password ||
35
+ u.hash ||
36
+ u.search ||
37
+ !(
38
+ u.protocol === "https:" ||
39
+ (u.protocol === "http:" &&
40
+ ["127.0.0.1", "localhost", "[::1]"].includes(u.hostname))
41
+ )
42
+ )
43
+ throw Error("Invalid package source");
44
+ return u;
45
+ }
46
+ async function download(fetchImpl, address, max) {
47
+ const response = await fetchImpl(address, {
48
+ redirect: "error",
49
+ signal: AbortSignal.timeout(10000),
50
+ });
51
+ if (!response.ok || !response.body) throw Error("Market request failed");
52
+ let length = 0;
53
+ const chunks = [];
54
+ for await (const chunk of response.body) {
55
+ length += chunk.length;
56
+ if (length > max) throw Error("Market response too large");
57
+ chunks.push(Buffer.from(chunk));
58
+ }
59
+ return Buffer.concat(chunks);
60
+ }
61
+ export async function prepareOfficialTestUpdate({
62
+ userDir,
63
+ dataDir,
64
+ write = console.log,
65
+ fetchImpl = fetch,
66
+ readJson = localJson,
67
+ install,
68
+ } = {}) {
69
+ write("\n> Official test Skill");
70
+ try {
71
+ let source;
72
+ for (const name of ["anvil-onboarding.json", "user-deployment.json"]) {
73
+ const saved = await readJson(await deploymentFilePath(dataDir, userDir, name));
74
+ if (saved?.marketBundleMetadataUrl) {
75
+ source = saved.marketBundleMetadataUrl;
76
+ break;
77
+ }
78
+ }
79
+ if (!source) {
80
+ const saved = await readJson(join(dataDir, "market-connection.json"));
81
+ if (saved?.marketOrigin) {
82
+ const origin = url(saved.marketOrigin);
83
+ if (origin.pathname !== "/") throw Error("Invalid Market origin");
84
+ const deployment = JSON.parse(
85
+ (
86
+ await download(
87
+ fetchImpl,
88
+ new URL("/market/web/onboarding/user-config", origin),
89
+ 16384,
90
+ )
91
+ ).toString("utf8"),
92
+ );
93
+ source = deployment.marketBundleMetadataUrl;
94
+ }
95
+ }
96
+ if (!source) {
97
+ write(
98
+ "Not checked: no saved Market test source. Continue agentpay setup when ready.",
99
+ );
100
+ return { status: "unconfigured" };
101
+ }
102
+ const metadataUrl = url(source);
103
+ if (
104
+ !/^\/(?:market\/)?skills\/bundles\/[^/]+\/metadata$/.test(
105
+ metadataUrl.pathname,
106
+ )
107
+ )
108
+ throw Error("Invalid package metadata path");
109
+ const body = JSON.parse(
110
+ (await download(fetchImpl, metadataUrl, 65536)).toString("utf8"),
111
+ );
112
+ if (body.ok !== true) throw Error("Metadata unavailable");
113
+ verifyOfficialTestDigest(body.result?.sha256);
114
+ const downloadUrl = marketDownloadUrl(
115
+ metadataUrl,
116
+ body.result.marketServiceId,
117
+ );
118
+ const bytes = await download(fetchImpl, downloadUrl, 2097152);
119
+ verifyOfficialTestBytes(bytes);
120
+ // Load package dependencies only after the software dependency update completed.
121
+ const { parseSkillBundle, installSkillBundle } = await import(
122
+ "./skill-bundle.mjs"
123
+ );
124
+ const bundle = parseSkillBundle(bytes);
125
+ if (
126
+ bundle.manifest.packageName !== OFFICIAL_TEST_PACKAGE.name ||
127
+ bundle.manifest.packageVersion !== OFFICIAL_TEST_PACKAGE.version
128
+ )
129
+ throw Error("Official package identity mismatch");
130
+ const saved = await (install || installSkillBundle)({
131
+ dataDir,
132
+ bytes,
133
+ source: metadataUrl.href,
134
+ });
135
+ if (saved.locallyModified) {
136
+ write(
137
+ "Official ZIP verified, but its installed files have local edits. Existing files were kept; review them before testing.",
138
+ );
139
+ return { status: "local_edits" };
140
+ }
141
+ write(
142
+ `Version ${OFFICIAL_TEST_PACKAGE.version} verified against User's built-in SHA-256 and saved locally.`,
143
+ );
144
+ write(
145
+ "Original test records and package selection were kept. No test or payment was started.",
146
+ );
147
+ return { status: "ready", directory: saved.directory };
148
+ } catch (error) {
149
+ if (error.code === "OFFICIAL_TEST_PACKAGE_MISMATCH")
150
+ write(
151
+ "Not updated: the published ZIP does not match this User release. Check for a reviewed User update; do not bypass the file check.",
152
+ );
153
+ else
154
+ write(
155
+ "Could not prepare the official test Skill. Check your Market connection and retry agentpay repair.",
156
+ );
157
+ write(
158
+ "The software update is kept; existing test files and Orders were not replaced.",
159
+ );
160
+ return { status: "unavailable" };
161
+ }
162
+ }
@@ -0,0 +1,241 @@
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 { isUnpaidExpiry } from "./order-closure.mjs";
5
+ import { randomBytes, createHash } from "node:crypto";
6
+ import { constants } from "node:fs";
7
+ import { mkdir, open, link, unlink } from "node:fs/promises";
8
+ import { join, resolve, dirname } from "node:path";
9
+ import { callWallet, readCapability } from "@c4roa0/agentpay-wallet";
10
+ import { readPrivateFile } from "./runtime-client.mjs";
11
+ import { readSettlementResult } from "./settlement-response.mjs";
12
+ const B32 = /^0x[0-9a-f]{64}$/;
13
+ const fail = (code) => Object.assign(Error(code), { code });
14
+ export function cancellationSettlementUrl(value) {
15
+ let url;
16
+ try {
17
+ url = new URL(value);
18
+ } catch {
19
+ throw fail("INVALID_SETTLEMENT_URL");
20
+ }
21
+ if (
22
+ (url.protocol !== "https:" &&
23
+ !(
24
+ url.protocol === "http:" &&
25
+ ["127.0.0.1", "localhost", "[::1]"].includes(url.hostname)
26
+ )) ||
27
+ url.username ||
28
+ url.password ||
29
+ url.search ||
30
+ url.hash
31
+ )
32
+ throw fail("INVALID_SETTLEMENT_URL");
33
+ return url.href.replace(/\/$/, "");
34
+ }
35
+ async function sync(path) {
36
+ const handle = await open(
37
+ path,
38
+ constants.O_RDONLY | constants.O_DIRECTORY | constants.O_NOFOLLOW,
39
+ );
40
+ try {
41
+ await handle.sync();
42
+ } finally {
43
+ await handle.close();
44
+ }
45
+ }
46
+ async function privateDirectory(path) {
47
+ await mkdir(path, { recursive: true, mode: 0o700 });
48
+ const h = await open(
49
+ path,
50
+ constants.O_RDONLY | constants.O_DIRECTORY | constants.O_NOFOLLOW,
51
+ );
52
+ try {
53
+ const s = await h.stat();
54
+ if (s.uid !== process.getuid() || s.mode & 0o077)
55
+ throw fail("LOCAL_STATE_UNSAFE");
56
+ } finally {
57
+ await h.close();
58
+ }
59
+ await sync(dirname(path));
60
+ }
61
+ async function context(input, walletCall) {
62
+ if (
63
+ !input ||
64
+ Object.keys(input).sort().join(",") !== "dataDir,orderId,settlementUrl"
65
+ )
66
+ throw fail("INVALID_CANCEL_INPUT");
67
+ const dataDir = resolve(input.dataDir),
68
+ orderId = String(input.orderId).toLowerCase();
69
+ if (!B32.test(orderId)) throw fail("INVALID_ORDER_ID");
70
+ const settlementUrl = cancellationSettlementUrl(input.settlementUrl);
71
+ const call =
72
+ walletCall ||
73
+ (async (method, params = {}) =>
74
+ callWallet(
75
+ join(dataDir, "wallet/wallet.sock"),
76
+ await readCapability(join(dataDir, "wallet"), "agent"),
77
+ method,
78
+ params,
79
+ ));
80
+ const status = await call("status");
81
+ if (!status.bound || !B32.test(status.policyId || ""))
82
+ throw fail("POLICY_UNBOUND");
83
+ return {
84
+ dataDir,
85
+ orderId,
86
+ settlementUrl,
87
+ policyId: status.policyId,
88
+ call,
89
+ path: join(
90
+ dataDir,
91
+ "runtime/order-cancellations",
92
+ createHash("sha256").update(orderId).digest("hex") + ".json",
93
+ ),
94
+ };
95
+ }
96
+ async function readIntent(ctx) {
97
+ let intent;
98
+ try {
99
+ intent = JSON.parse(await readPrivateFile(ctx.path));
100
+ } catch (e) {
101
+ if (e.code === "ENOENT") return null;
102
+ throw fail("CANCELLATION_STATE_INVALID");
103
+ }
104
+ if (
105
+ !intent ||
106
+ Object.keys(intent).sort().join(",") !==
107
+ "cancellationId,createdAtMs,orderId,policyId,settlementUrl,version" ||
108
+ intent.version !== "agentpay.user-order-cancellation.v1" ||
109
+ !B32.test(intent.cancellationId || "") ||
110
+ intent.orderId !== ctx.orderId ||
111
+ intent.policyId !== ctx.policyId ||
112
+ intent.settlementUrl !== ctx.settlementUrl ||
113
+ !Number.isSafeInteger(intent.createdAtMs) ||
114
+ intent.createdAtMs < 0
115
+ )
116
+ throw fail("CANCELLATION_STATE_CONFLICT");
117
+ return intent;
118
+ }
119
+ async function ensureIntent(ctx) {
120
+ const existing = await readIntent(ctx);
121
+ if (existing) return existing;
122
+ await privateDirectory(ctx.dataDir);
123
+ await privateDirectory(join(ctx.dataDir, "runtime"));
124
+ await privateDirectory(dirname(ctx.path));
125
+ const value = {
126
+ version: "agentpay.user-order-cancellation.v1",
127
+ orderId: ctx.orderId,
128
+ policyId: ctx.policyId,
129
+ settlementUrl: ctx.settlementUrl,
130
+ cancellationId: "0x" + randomBytes(32).toString("hex"),
131
+ createdAtMs: Date.now(),
132
+ };
133
+ const temp = ctx.path + "." + randomBytes(12).toString("hex") + ".tmp";
134
+ const h = await open(temp, "wx", 0o600);
135
+ try {
136
+ try {
137
+ await h.writeFile(JSON.stringify(value) + "\n");
138
+ await h.sync();
139
+ } finally {
140
+ await h.close();
141
+ }
142
+ try {
143
+ await link(temp, ctx.path);
144
+ } catch (e) {
145
+ if (e.code !== "EEXIST") throw e;
146
+ }
147
+ await sync(dirname(ctx.path));
148
+ } finally {
149
+ await unlink(temp).catch((e) => {
150
+ if (e.code !== "ENOENT") throw e;
151
+ });
152
+ }
153
+ return readIntent(ctx);
154
+ }
155
+ async function readOrder(ctx, intent) {
156
+ const result = readSettlementResult(
157
+ await ctx.call("readSettlementOrder", {
158
+ settlementUrl: ctx.settlementUrl,
159
+ orderId: ctx.orderId,
160
+ }),
161
+ );
162
+ if (result.order?.orderId !== ctx.orderId)
163
+ throw fail("CANCELLATION_RESPONSE_INVALID");
164
+ const state = result.closure?.state,
165
+ terminal = state === "cancelled" || state === "refunded";
166
+ return {
167
+ orderId: ctx.orderId,
168
+ cancellationId: intent?.cancellationId || null,
169
+ orderStatus: result.order.orderStatus,
170
+ vaultState: result.order.vaultState,
171
+ waiting: result.waiting || null,
172
+ closure: result.closure || null,
173
+ message: isUnpaidExpiry(result)
174
+ ? "Order expired without payment. No service result is expected."
175
+ : terminal
176
+ ? state === "refunded"
177
+ ? "Principal refunded; cumulative Policy spend is not restored."
178
+ : "Order cancelled."
179
+ : state
180
+ ? "Closure is pending. Check this same Order again."
181
+ : "No confirmed cancellation. Check this same Order before retrying.",
182
+ };
183
+ }
184
+ export async function cancelOrder(input, { walletCall } = {}) {
185
+ const ctx = await context(input, walletCall),
186
+ intent = await ensureIntent(ctx);
187
+ // The immutable identity is durable before the first HTTP request. Response loss
188
+ // retries the same cancellation; no invocation/Commitment or payment is created.
189
+ const response = readSettlementResult(
190
+ await ctx.call("cancelSettlementOrder", {
191
+ settlementUrl: ctx.settlementUrl,
192
+ orderId: ctx.orderId,
193
+ cancellationId: intent.cancellationId,
194
+ }),
195
+ );
196
+ if (
197
+ response.orderId !== ctx.orderId ||
198
+ !response.closure ||
199
+ typeof response.duplicate !== "boolean"
200
+ )
201
+ throw fail("CANCELLATION_RESPONSE_INVALID");
202
+ return readOrder(ctx, intent);
203
+ }
204
+ export async function cancellationStatus(input, { walletCall } = {}) {
205
+ const ctx = await context(input, walletCall);
206
+ return readOrder(ctx, await readIntent(ctx));
207
+ }
208
+
209
+ // Resolve only existing Runtime history; never prepare a replacement Order here.
210
+ export async function cancellationOrderForRequest({ dataDir, requestId }) {
211
+ if (
212
+ typeof requestId !== "string" ||
213
+ !/^[A-Za-z0-9._:-]{8,128}$/.test(requestId)
214
+ )
215
+ throw fail("INVALID_REQUEST_ID");
216
+ const document = JSON.parse(
217
+ await readPrivateFile(
218
+ join(resolve(dataDir), "runtime/invocations.json"),
219
+ 16 * 1048576,
220
+ ),
221
+ );
222
+ const id =
223
+ "0x" +
224
+ createHash("sha256")
225
+ .update("agentpay.user-invocation.v1:" + requestId)
226
+ .digest("hex");
227
+ const entry =
228
+ document?.version === "agentpay.user-invocations.v1"
229
+ ? document.invocations?.[id]
230
+ : null;
231
+ if (
232
+ !entry ||
233
+ entry.invocationId !== id ||
234
+ entry.routingVersion !== "agentpay.runtime-call.v1" ||
235
+ !B32.test(entry.requestHash || "")
236
+ )
237
+ throw fail("INVOCATION_NOT_FOUND");
238
+ if (entry.orderId === null) throw fail("ORDER_NOT_CREATED");
239
+ if (!B32.test(entry.orderId || "")) throw fail("CANCELLATION_STATE_INVALID");
240
+ return entry.orderId;
241
+ }
@@ -0,0 +1,42 @@
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
+ // A business expiry is not a claim that the old Vault was cancelled on chain.
5
+ export function isUnpaidExpiry(result) {
6
+ return (
7
+ result?.preparationState === "expired" &&
8
+ result?.closure?.state === "cancelled" &&
9
+ result.closure.reason === "payment_timeout" &&
10
+ result.closure.transactionHash === null &&
11
+ result.order?.vaultState === "Created" &&
12
+ result.order?.serviceState === "closed"
13
+ );
14
+ }
15
+ // Closure is independent of the frozen participant Order status enum.
16
+ export function assertOrderCanContinue(result) {
17
+ const state = result?.closure?.state,
18
+ vault = result?.order?.vaultState;
19
+ if (isUnpaidExpiry(result))
20
+ throw coded(
21
+ "ORDER_EXPIRED",
22
+ "This Order expired without payment. No service result is expected. Keep the original request; it will not be paid again automatically.",
23
+ );
24
+ if (state === "cancelled" || vault === "Cancelled")
25
+ throw coded(
26
+ "ORDER_CANCELLED",
27
+ "This Order was cancelled. Preserve its request and payment history.",
28
+ );
29
+ if (state === "refunded" || vault === "Refunded")
30
+ throw coded(
31
+ "ORDER_REFUNDED",
32
+ "This Order was refunded. Its cumulative Policy spend is not restored.",
33
+ );
34
+ if (state)
35
+ throw coded(
36
+ "ORDER_CLOSING",
37
+ "This Order is closing. Query the same Order; do not submit another payment or service request.",
38
+ );
39
+ }
40
+ function coded(code, message) {
41
+ return Object.assign(Error(message), { code });
42
+ }
@@ -0,0 +1,110 @@
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 { localOrderSummary } from "./order-review.mjs";
5
+
6
+ // Rendering only: saved observations are not fresh financial verification.
7
+ export function renderOrderList(
8
+ entries,
9
+ observations,
10
+ notes,
11
+ { color = false, columns = 80 } = {},
12
+ ) {
13
+ const width = Math.max(
14
+ 48,
15
+ Math.min(72, Number.isFinite(columns) ? Math.floor(columns) : 80),
16
+ );
17
+ const paint = (text, style) =>
18
+ color ? `\x1b[${style}m${text}\x1b[0m` : text;
19
+ const lines = [],
20
+ rule = "─".repeat(width);
21
+ function text(value, { indent = " ", style = null } = {}) {
22
+ let line = indent;
23
+ for (const word of value.split(/\s+/)) {
24
+ if (line.trim() && line.length + 1 + word.length > width) {
25
+ lines.push(style ? paint(line, style) : line);
26
+ line = indent;
27
+ }
28
+ line += (line.trim() ? " " : "") + word;
29
+ }
30
+ lines.push(style ? paint(line, style) : line);
31
+ }
32
+ lines.push(
33
+ "",
34
+ paint(rule, "2"),
35
+ paint("> Local Orders", "1"),
36
+ paint(rule, "2"),
37
+ );
38
+ text(`${entries.length} saved · latest first`, { style: "2" });
39
+ if (!entries.length) text("No saved Orders or requests.");
40
+ entries.forEach((e, index) => {
41
+ const summary = localOrderSummary(
42
+ e,
43
+ observations[e.invocationId],
44
+ notes[e.invocationId],
45
+ );
46
+ const id = e.orderId || e.invocationId;
47
+ let length = 12;
48
+ while (
49
+ length < 66 &&
50
+ entries.filter(
51
+ (v) =>
52
+ v.invocationId.startsWith(id.slice(0, length)) ||
53
+ v.orderId?.startsWith(id.slice(0, length)),
54
+ ).length > 1
55
+ )
56
+ length++;
57
+ const selector = id.slice(0, length);
58
+ const style =
59
+ summary.marker === "ARCHIVED"
60
+ ? "2"
61
+ : summary.marker === "OK"
62
+ ? "1;32"
63
+ : "1;33";
64
+ lines.push("", paint(rule, "2"));
65
+ text(
66
+ `${index + 1}. [${summary.marker}] ${summary.state}${summary.localResultUnavailable ? " · Local result unavailable" : ""}`,
67
+ { style },
68
+ );
69
+ const date = Number.isSafeInteger(e.createdAtMs)
70
+ ? new Date(e.createdAtMs).toLocaleString()
71
+ : "Date not recorded";
72
+ text(date, { style: "2" });
73
+ if (Number.isSafeInteger(summary.checkedAtMs))
74
+ text("Last check: " + new Date(summary.checkedAtMs).toLocaleString(), {
75
+ style: "2",
76
+ });
77
+ text(
78
+ `${e.orderId ? "Order" : "Request"} ${id.slice(0, 12)}…${id.slice(-6)}`,
79
+ { style: "2" },
80
+ );
81
+ if (summary.warning) {
82
+ lines.push("");
83
+ text(summary.marker === "ARCHIVED" ? "Saved note" : "Warning", { style });
84
+ text(summary.warning);
85
+ }
86
+ if (summary.changed)
87
+ text("Changed since archiving. Review this record again.", {
88
+ style: "1;33",
89
+ });
90
+ lines.push("");
91
+ text("View details", { style: "1" });
92
+ text(`agentpay orders details ${selector}`, { style: "36" });
93
+ text(
94
+ notes[e.invocationId]?.archived
95
+ ? "Restore reminder"
96
+ : "Archive after review",
97
+ { style: "2" },
98
+ );
99
+ text(
100
+ `agentpay orders ${notes[e.invocationId]?.archived ? "unarchive" : "archive"} ${selector}`,
101
+ { style: "36" },
102
+ );
103
+ });
104
+ lines.push("", paint(rule, "2"));
105
+ text("Saved checks only. Full IDs and review notes are in details.", {
106
+ style: "2",
107
+ });
108
+ lines.push("");
109
+ return lines.join("\n");
110
+ }