@lucid-agents/taskmarket 1.16.0 → 1.17.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 (128) hide show
  1. package/THIRD_PARTY_NOTICES.md +14 -0
  2. package/dist/THIRD_PARTY_NOTICES.md +3108 -0
  3. package/dist/agent-compatibility.json +54 -0
  4. package/dist/budget-view-XKZC3GY5.js +36 -0
  5. package/dist/channels-Q7XMSUSO.js +29 -0
  6. package/dist/chunk-3S63AB3C.js +16068 -0
  7. package/dist/chunk-73RXOYME.js +185 -0
  8. package/dist/chunk-EIHZYRIU.js +1099 -0
  9. package/dist/chunk-F5SS4NUF.js +1062 -0
  10. package/dist/chunk-JAZ5BWPB.js +181 -0
  11. package/dist/chunk-MI2GPMCZ.js +154 -0
  12. package/dist/chunk-OKZ2NRYZ.js +133 -0
  13. package/dist/chunk-S7SJMJ2I.js +38 -0
  14. package/dist/chunk-YM4S6IE4.js +15 -0
  15. package/dist/compatibility-KUYGZSUC.js +19 -0
  16. package/dist/doctor-BY2PBHS7.js +216 -0
  17. package/dist/errors-2L357HGA.js +8 -0
  18. package/dist/index.js +1186 -13492
  19. package/dist/launch-DE65XO5C.js +22476 -0
  20. package/dist/pi/CHANGELOG.md +5689 -0
  21. package/dist/pi/README.md +718 -0
  22. package/dist/pi/dist/bundle/chunks/anthropic-messages-VWZZOSJQ.js +6 -0
  23. package/dist/pi/dist/bundle/chunks/anthropic.js +75 -0
  24. package/dist/pi/dist/bundle/chunks/azure-openai-responses-BJLAM6CD.js +2 -0
  25. package/dist/pi/dist/bundle/chunks/bedrock-converse-stream.js +74 -0
  26. package/dist/pi/dist/bundle/chunks/chunk-4L3WN2XY.js +2 -0
  27. package/dist/pi/dist/bundle/chunks/chunk-6CYCI3EJ.js +12 -0
  28. package/dist/pi/dist/bundle/chunks/chunk-6R7UI4IY.js +2 -0
  29. package/dist/pi/dist/bundle/chunks/chunk-7ZC4UXSL.js +2 -0
  30. package/dist/pi/dist/bundle/chunks/chunk-AXIIZGTV.js +2 -0
  31. package/dist/pi/dist/bundle/chunks/chunk-CLNPYIDP.js +42 -0
  32. package/dist/pi/dist/bundle/chunks/chunk-DRQDKNXA.js +2 -0
  33. package/dist/pi/dist/bundle/chunks/chunk-GMWCTUPB.js +2 -0
  34. package/dist/pi/dist/bundle/chunks/chunk-IDDQWTHI.js +2 -0
  35. package/dist/pi/dist/bundle/chunks/chunk-JVUZSMYM.js +1547 -0
  36. package/dist/pi/dist/bundle/chunks/chunk-L2ZPNLYD.js +2 -0
  37. package/dist/pi/dist/bundle/chunks/chunk-NUHFSC37.js +25 -0
  38. package/dist/pi/dist/bundle/chunks/chunk-OUXLLA64.js +11 -0
  39. package/dist/pi/dist/bundle/chunks/chunk-PGXDEOVV.js +2 -0
  40. package/dist/pi/dist/bundle/chunks/chunk-PJACZCIR.js +2 -0
  41. package/dist/pi/dist/bundle/chunks/chunk-PRRNXB7S.js +2 -0
  42. package/dist/pi/dist/bundle/chunks/chunk-TEPMHNKQ.js +2 -0
  43. package/dist/pi/dist/bundle/chunks/chunk-TRDNDS6A.js +4 -0
  44. package/dist/pi/dist/bundle/chunks/chunk-U6ZMQKGD.js +50 -0
  45. package/dist/pi/dist/bundle/chunks/chunk-UAQELI3K.js +2 -0
  46. package/dist/pi/dist/bundle/chunks/chunk-USJZMAIR.js +2 -0
  47. package/dist/pi/dist/bundle/chunks/chunk-XDUWOHPL.js +6 -0
  48. package/dist/pi/dist/bundle/chunks/chunk-XNGRGP62.js +2 -0
  49. package/dist/pi/dist/bundle/chunks/github-copilot.js +2 -0
  50. package/dist/pi/dist/bundle/chunks/google-generative-ai-Q2K7FBDI.js +2 -0
  51. package/dist/pi/dist/bundle/chunks/google-vertex-7UJBF4S7.js +2 -0
  52. package/dist/pi/dist/bundle/chunks/https-proxy-agent-2VXB7436.js +13 -0
  53. package/dist/pi/dist/bundle/chunks/image-resize-worker.js +2 -0
  54. package/dist/pi/dist/bundle/chunks/kimi-coding.js +2 -0
  55. package/dist/pi/dist/bundle/chunks/lib-HGQMNYVT.js +9 -0
  56. package/dist/pi/dist/bundle/chunks/mistral-conversations-4M6BZBX6.js +5 -0
  57. package/dist/pi/dist/bundle/chunks/multipart-parser-SQ6GDJL7.js +3 -0
  58. package/dist/pi/dist/bundle/chunks/node-KG362ECQ.js +16 -0
  59. package/dist/pi/dist/bundle/chunks/node-TWVVAGPO.js +2 -0
  60. package/dist/pi/dist/bundle/chunks/openai-codex-responses-R6VYZTVY.js +10 -0
  61. package/dist/pi/dist/bundle/chunks/openai-codex.js +75 -0
  62. package/dist/pi/dist/bundle/chunks/openai-completions-EKZT2IH2.js +7 -0
  63. package/dist/pi/dist/bundle/chunks/openai-responses-TFDINO6W.js +2 -0
  64. package/dist/pi/dist/bundle/chunks/openrouter-images-N4XVVFSD.js +2 -0
  65. package/dist/pi/dist/bundle/chunks/openrouter.js +75 -0
  66. package/dist/pi/dist/bundle/chunks/pi-messages-PML4SPVJ.js +8 -0
  67. package/dist/pi/dist/bundle/chunks/radius.js +75 -0
  68. package/dist/pi/dist/bundle/chunks/src-UK77SYZE.js +4 -0
  69. package/dist/pi/dist/bundle/chunks/xai.js +2 -0
  70. package/dist/pi/dist/bundle/cli.js +3 -0
  71. package/dist/pi/dist/bundle/index.js +2 -0
  72. package/dist/pi/dist/bundle/rpc-entry.js +3 -0
  73. package/dist/pi/dist/core/export-html/template.css +1066 -0
  74. package/dist/pi/dist/core/export-html/template.html +55 -0
  75. package/dist/pi/dist/core/export-html/template.js +1864 -0
  76. package/dist/pi/dist/core/export-html/vendor/highlight.min.js +1213 -0
  77. package/dist/pi/dist/core/export-html/vendor/marked.min.js +78 -0
  78. package/dist/pi/dist/modes/interactive/assets/clankolas.png +0 -0
  79. package/dist/pi/dist/modes/interactive/theme/dark.json +91 -0
  80. package/dist/pi/dist/modes/interactive/theme/light.json +90 -0
  81. package/dist/pi/dist/modes/interactive/theme/theme-schema.json +356 -0
  82. package/dist/pi/docs/compaction.md +418 -0
  83. package/dist/pi/docs/containerization.md +156 -0
  84. package/dist/pi/docs/custom-provider.md +777 -0
  85. package/dist/pi/docs/development.md +90 -0
  86. package/dist/pi/docs/environment-variables.md +99 -0
  87. package/dist/pi/docs/extensions.md +3023 -0
  88. package/dist/pi/docs/index.md +84 -0
  89. package/dist/pi/docs/json.md +98 -0
  90. package/dist/pi/docs/keybindings.md +239 -0
  91. package/dist/pi/docs/llama-cpp.md +101 -0
  92. package/dist/pi/docs/models.md +574 -0
  93. package/dist/pi/docs/packages.md +228 -0
  94. package/dist/pi/docs/prompt-templates.md +96 -0
  95. package/dist/pi/docs/providers.md +317 -0
  96. package/dist/pi/docs/quickstart.md +167 -0
  97. package/dist/pi/docs/rpc.md +1618 -0
  98. package/dist/pi/docs/sdk.md +1224 -0
  99. package/dist/pi/docs/security.md +59 -0
  100. package/dist/pi/docs/session-format.md +438 -0
  101. package/dist/pi/docs/sessions.md +145 -0
  102. package/dist/pi/docs/settings.md +369 -0
  103. package/dist/pi/docs/shell-aliases.md +13 -0
  104. package/dist/pi/docs/skills.md +232 -0
  105. package/dist/pi/docs/terminal-setup.md +192 -0
  106. package/dist/pi/docs/termux.md +127 -0
  107. package/dist/pi/docs/themes.md +322 -0
  108. package/dist/pi/docs/tmux.md +63 -0
  109. package/dist/pi/docs/tui.md +961 -0
  110. package/dist/pi/docs/usage.md +311 -0
  111. package/dist/pi/docs/windows.md +39 -0
  112. package/dist/pi/package.json +15 -0
  113. package/dist/pi/vendor/chord-context.js +96 -0
  114. package/dist/pi/vendor/jiti/LICENSE +21 -0
  115. package/dist/pi/vendor/jiti/dist/babel.cjs +257 -0
  116. package/dist/pi/vendor/jiti/dist/jiti.cjs +1 -0
  117. package/dist/pi/vendor/jiti/lib/jiti.cjs +30 -0
  118. package/dist/pi/vendor/jiti/package.json +6 -0
  119. package/dist/pi/vendor/photon-node/LICENSE.md +201 -0
  120. package/dist/pi/vendor/photon-node/package.json +6 -0
  121. package/dist/pi/vendor/photon-node/photon_rs.js +4521 -0
  122. package/dist/pi/vendor/photon-node/photon_rs_bg.wasm +0 -0
  123. package/dist/pi-extensions/lockdown.cjs +433 -0
  124. package/dist/pi-extensions/remote-tools.cjs +22320 -0
  125. package/dist/pi-extensions/xgate.cjs +2565 -0
  126. package/dist/reconcile-VEMPNIAO.js +83 -0
  127. package/dist/status-3Q55PBFC.js +18 -0
  128. package/package.json +13 -2
@@ -0,0 +1,1062 @@
1
+ import { createRequire as __tmCreateRequire } from 'node:module'; import { fileURLToPath as __tmFileURLToPath } from 'node:url'; import { dirname as __tmDirname } from 'node:path'; const require = __tmCreateRequire(import.meta.url); const __tmFilename = __tmFileURLToPath(import.meta.url); const __tmDirnameValue = __tmDirname(__tmFilename);
2
+ import {
3
+ BASE_CHANNEL_RAIL_ID,
4
+ WITHDRAW_DELAY_SECONDS,
5
+ createBaseChannelPayer,
6
+ createBaseChannelRpcReader,
7
+ createWalletAccountFromKeystore,
8
+ createX402Allocations,
9
+ getTaskmarketHome,
10
+ getX402JournalPath,
11
+ getX402PolicyPath,
12
+ keystoreExists,
13
+ listX402Payments,
14
+ loadKeystore,
15
+ loadX402Policy,
16
+ rpcUrlForNetwork
17
+ } from "./chunk-3S63AB3C.js";
18
+ import {
19
+ AgentLaunchError
20
+ } from "./chunk-YM4S6IE4.js";
21
+ import {
22
+ formatUsdc
23
+ } from "./chunk-73RXOYME.js";
24
+
25
+ // src/lib/agent/channels.ts
26
+ import { randomUUID as randomUUID2 } from "crypto";
27
+
28
+ // ../../node_modules/.pnpm/@lucid-agents+xgate-client@0.1.6_@solana+sysvars@5.5.1_typescript@5.7.3_zod@3.25.76/node_modules/@lucid-agents/xgate-client/dist/types.js
29
+ var XgateError = class extends Error {
30
+ code;
31
+ constructor(code) {
32
+ super(`Xgate: ${code}. Inspect /xgate status before another purchase.`);
33
+ this.name = "XgateError";
34
+ this.code = code;
35
+ }
36
+ };
37
+ function requireValue(condition, code) {
38
+ if (!condition)
39
+ throw new XgateError(code);
40
+ }
41
+ function record(value) {
42
+ return value !== null && typeof value === "object" && !Array.isArray(value);
43
+ }
44
+ function atomic(value) {
45
+ requireValue(typeof value === "string" && /^(0|[1-9][0-9]{0,12})$/.test(value), "invalid_amount");
46
+ const n = BigInt(value);
47
+ requireValue(n <= 1000000000000n, "amount_bound");
48
+ return n;
49
+ }
50
+ function origin(value, loopback = false) {
51
+ const u = new URL(value);
52
+ requireValue((u.protocol === "https:" || loopback && u.protocol === "http:" && ["127.0.0.1", "[::1]"].includes(u.hostname)) && !u.username && !u.password && u.pathname === "/" && !u.search && !u.hash, "invalid_origin");
53
+ return u.origin;
54
+ }
55
+ function route(value) {
56
+ requireValue([
57
+ "/v1/chat/completions",
58
+ "/providers/openai/v1/responses",
59
+ "/providers/anthropic/v1/messages"
60
+ ].includes(String(value)), "invalid_route");
61
+ }
62
+
63
+ // ../../node_modules/.pnpm/@lucid-agents+pi-xgate@0.1.6_@earendil-works+pi-ai@0.85.1_@earendil-works+pi-coding-agent@0.8_344x6vs3jrzh5eh3xewwgv3ik4/node_modules/@lucid-agents/pi-xgate/dist/transport.js
64
+ var XgatePurchaseError = class extends XgateError {
65
+ invocationId;
66
+ pending;
67
+ payment;
68
+ constructor(code, invocationId, pending, payment) {
69
+ super(code);
70
+ this.name = "XgatePurchaseError";
71
+ this.message = pending ? `Xgate: ${code}. The payment outcome is unknown; do not retry. Reconcile it before another purchase.` : `Xgate: ${code}. Inspect the payment status before another purchase.`;
72
+ this.invocationId = invocationId;
73
+ this.pending = pending;
74
+ if (payment)
75
+ this.payment = payment;
76
+ }
77
+ };
78
+
79
+ // ../../node_modules/.pnpm/@lucid-agents+pi-xgate@0.1.6_@earendil-works+pi-ai@0.85.1_@earendil-works+pi-coding-agent@0.8_344x6vs3jrzh5eh3xewwgv3ik4/node_modules/@lucid-agents/pi-xgate/dist/taskmarket.js
80
+ var RESOLVED_CHARGES = ["none", "released", "settled", "manually_resolved"];
81
+ var RESOLVED_STATES = ["completed", "failed"];
82
+ var CATALOG_BYTES = 2 * 1024 * 1024;
83
+ var REQUEST_BYTES = 8 * 1024 * 1024;
84
+ var CATALOG_DEADLINE_MS = 15e3;
85
+ function createTaskMarketTransport(options) {
86
+ const { payer } = options, base = origin(options.origin), fetcher = options.fetch ?? fetch;
87
+ if (payer.apiVersion !== 1)
88
+ throw new XgateError("unsupported_payer_api");
89
+ const channel = async (payment) => {
90
+ if (!options.balances || payment.rail.kind !== "session")
91
+ return;
92
+ try {
93
+ const matches = (await options.balances()).filter((b2) => b2.railId === payment.rail.id && b2.network === payment.network && b2.asset === payment.asset);
94
+ if (matches.length !== 1)
95
+ return;
96
+ const b = matches[0];
97
+ return {
98
+ depositingAtomic: b.depositing,
99
+ lockedAtomic: b.locked,
100
+ chargedAtomic: b.charged,
101
+ pendingAtomic: b.pending,
102
+ refundableAtomic: b.refundable
103
+ };
104
+ } catch {
105
+ return;
106
+ }
107
+ };
108
+ return {
109
+ async models(signal) {
110
+ const deadline = AbortSignal.timeout(CATALOG_DEADLINE_MS);
111
+ let response;
112
+ try {
113
+ response = await fetcher(base + "/v1/models", {
114
+ signal: signal ? AbortSignal.any([signal, deadline]) : deadline,
115
+ redirect: "error",
116
+ cache: "no-store",
117
+ credentials: "omit",
118
+ referrerPolicy: "no-referrer",
119
+ headers: { accept: "application/json" }
120
+ });
121
+ } catch {
122
+ throw new XgateError("catalog_unavailable");
123
+ }
124
+ if (!response.ok) {
125
+ await response.body?.cancel().catch(() => {
126
+ });
127
+ throw new XgateError("catalog_unavailable");
128
+ }
129
+ const v = parse(await bounded(response, CATALOG_BYTES));
130
+ if (!record(v) || !Array.isArray(v.data) || v.data.length > 128)
131
+ throw new XgateError("invalid_catalog");
132
+ return v.data.filter((m) => record(m) && (options.models === void 0 || options.models.includes(m.id))).map((m) => ({ ...m, xgate_origin: base }));
133
+ },
134
+ async purchase(invocation, signal, onResponse) {
135
+ route(invocation.route);
136
+ const body = new TextEncoder().encode(invocation.body);
137
+ if (body.byteLength === 0 || body.byteLength > REQUEST_BYTES)
138
+ throw new XgateError("invalid_invocation");
139
+ const id = invocation.id;
140
+ let result;
141
+ try {
142
+ result = await payer.request({
143
+ apiVersion: 1,
144
+ invocationId: id,
145
+ url: base + invocation.route,
146
+ method: "POST",
147
+ // Application headers only: the payer owns every payment header.
148
+ headers: {
149
+ "content-type": "application/json",
150
+ accept: "application/json"
151
+ },
152
+ body,
153
+ limits: { maxRequestBodyBytes: REQUEST_BYTES },
154
+ ...options.allocationId !== void 0 ? { allocationId: options.allocationId } : {},
155
+ ...options.policyRuleId !== void 0 ? { policyRuleId: options.policyRuleId } : {},
156
+ ...options.nonInteractive !== void 0 ? { nonInteractive: options.nonInteractive } : {}
157
+ }, {
158
+ ...signal ? { signal } : {},
159
+ ...options.confirmPayment ? { confirmPayment: options.confirmPayment } : {}
160
+ });
161
+ } catch (error) {
162
+ throw failure(error, id);
163
+ }
164
+ const payment = result.payment;
165
+ if (result.invocationId !== id || result.apiVersion !== 1)
166
+ throw new XgatePurchaseError("payer_result_mismatch", id, true, payment && display(payment));
167
+ if (!payment)
168
+ throw new XgatePurchaseError(result.response.status === 200 ? "payment_missing" : "request_rejected", id, false);
169
+ const shown = display(payment);
170
+ if (payment.charge !== "settled" || typeof payment.settledAmount !== "string")
171
+ throw new XgatePurchaseError("payment_unverified", id, true, shown);
172
+ shown.chargedAtomic = payment.settledAmount;
173
+ try {
174
+ if (atomic(payment.settledAmount) > atomic(payment.authorizedAmount))
175
+ throw new XgateError("charge_over_authorization");
176
+ } catch {
177
+ throw new XgatePurchaseError("invalid_settlement", id, true, shown);
178
+ }
179
+ const r = result.response, h = r.headers;
180
+ if (r.status !== 200 || !/^application\/json\b/i.test(r.contentType) || h["x-inference-privacy"] !== "anonymous" || h["x-result-retention"] !== "none" || !h["cache-control"]?.includes("no-store"))
181
+ throw new XgatePurchaseError("response_rejected", id, false, shown);
182
+ let value;
183
+ try {
184
+ value = parse(r.body);
185
+ } catch {
186
+ throw new XgatePurchaseError("invalid_response", id, false, shown);
187
+ }
188
+ if (!record(value) || typeof value.id !== "string" || value.result_replay_available !== void 0)
189
+ throw new XgatePurchaseError("invalid_response", id, false, shown);
190
+ const head = {
191
+ status: r.status,
192
+ headers: { "content-type": r.contentType, "cache-control": "no-store" }
193
+ };
194
+ try {
195
+ await onResponse?.(head);
196
+ } catch {
197
+ throw new XgatePurchaseError("response_hook_failed", id, false, shown);
198
+ }
199
+ const balance = await channel(payment);
200
+ if (balance)
201
+ shown.channel = balance;
202
+ return {
203
+ body: value,
204
+ settlement: {
205
+ chargedAtomic: payment.settledAmount,
206
+ reference: payment.transaction ?? payment.commitment ?? payment.paymentId
207
+ },
208
+ purchase: payment.commitment ?? payment.paymentId,
209
+ payment: shown
210
+ };
211
+ },
212
+ async status() {
213
+ return {
214
+ transport: "taskmarket",
215
+ origin: base,
216
+ ...options.balances ? {
217
+ balances: (await options.balances()).map((b) => ({
218
+ rail: b.railId,
219
+ network: b.network,
220
+ asset: b.asset,
221
+ depositingAtomic: b.depositing,
222
+ lockedAtomic: b.locked,
223
+ chargedAtomic: b.charged,
224
+ pendingAtomic: b.pending,
225
+ refundableAtomic: b.refundable
226
+ }))
227
+ } : {}
228
+ };
229
+ },
230
+ async reconcile(invocationId) {
231
+ const s = await payer.reconcile(invocationId);
232
+ return {
233
+ invocationId: s.invocationId,
234
+ state: s.state,
235
+ charge: s.charge,
236
+ uncertain: s.uncertain,
237
+ ...s.errorCode ? { errorCode: s.errorCode } : {},
238
+ ...s.payment ? { payment: display(s.payment) } : {}
239
+ };
240
+ }
241
+ };
242
+ }
243
+ function display(p) {
244
+ return {
245
+ network: p.network,
246
+ scheme: p.scheme,
247
+ asset: p.asset,
248
+ payTo: p.payTo,
249
+ rail: p.rail.kind,
250
+ authorizedAtomic: p.authorizedAmount,
251
+ ...p.charge === "settled" && p.settledAmount !== void 0 ? { chargedAtomic: p.settledAmount } : {},
252
+ status: p.charge,
253
+ ...p.transaction ?? p.commitment ? { reference: p.transaction ?? p.commitment } : {}
254
+ };
255
+ }
256
+ function failure(error, id) {
257
+ const e = error;
258
+ const code = typeof e?.code === "string" && /^[a-z0-9_]{1,64}$/.test(e.code) ? e.code : "payer_failed";
259
+ const recorded = e?.invocation;
260
+ const pending = e?.pending !== false || recorded !== void 0 && !(recorded.uncertain === false && RESOLVED_CHARGES.includes(recorded.charge) && RESOLVED_STATES.includes(recorded.state));
261
+ const summary = e?.invocation?.payment;
262
+ return new XgatePurchaseError(code, id, pending, record(summary) ? display(summary) : void 0);
263
+ }
264
+ async function bounded(response, limit) {
265
+ if (!response.body)
266
+ throw new XgateError("invalid_catalog");
267
+ const reader = response.body.getReader(), chunks = [];
268
+ let length = 0;
269
+ try {
270
+ for (; ; ) {
271
+ const part = await reader.read();
272
+ if (part.done)
273
+ break;
274
+ length += part.value.byteLength;
275
+ if (length > limit)
276
+ throw new XgateError("catalog_bound");
277
+ chunks.push(part.value);
278
+ }
279
+ } catch (error) {
280
+ await reader.cancel().catch(() => {
281
+ });
282
+ throw error instanceof XgateError ? error : new XgateError("catalog_unavailable");
283
+ } finally {
284
+ reader.releaseLock();
285
+ }
286
+ return Buffer.concat(chunks, length);
287
+ }
288
+ function parse(bytes) {
289
+ try {
290
+ return JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes));
291
+ } catch {
292
+ throw new XgateError("invalid_response");
293
+ }
294
+ }
295
+
296
+ // src/lib/agent/payer.ts
297
+ function routeOfCatalogRow(row) {
298
+ return row?.owned_by === "anthropic" ? "/providers/anthropic/v1/messages" : "/providers/openai/v1/responses";
299
+ }
300
+ async function createAgentRails(options) {
301
+ const journalPath = getX402JournalPath();
302
+ const policyPath = getX402PolicyPath();
303
+ const loadPolicy = (loadOptions) => loadX402Policy(policyPath, loadOptions);
304
+ const x402Policy = await loadX402Policy(policyPath);
305
+ const allocations = createX402Allocations({ journalPath, loadPolicy });
306
+ return (options.rails ?? options.policy.rails).map((rail) => {
307
+ if (rail.wallet.toLowerCase() !== options.walletAddress.toLowerCase()) {
308
+ throw new AgentLaunchError(
309
+ "invalid_agent_policy",
310
+ `Rail ${rail.id} pays from ${rail.wallet}, but the wallet is ${options.walletAddress}`
311
+ );
312
+ }
313
+ let rpcUrl;
314
+ try {
315
+ rpcUrl = rpcUrlForNetwork(x402Policy, rail.network);
316
+ } catch (error) {
317
+ throw new AgentLaunchError("invalid_agent_policy", error.message);
318
+ }
319
+ const channel = {
320
+ network: rail.network,
321
+ payer: rail.wallet,
322
+ receiver: rail.receiver,
323
+ receiverAuthorizer: rail.receiverAuthorizer,
324
+ salt: rail.salt
325
+ };
326
+ const payer = createBaseChannelPayer({
327
+ channel,
328
+ reader: createBaseChannelRpcReader({ channel, rpcUrl, fromBlock: BigInt(rail.fromBlock) }),
329
+ resourceUrl: `${rail.origin}${rail.route}`,
330
+ journalPath,
331
+ loadPolicy,
332
+ getAccount: options.getAccount,
333
+ allocationId: rail.allocationId,
334
+ budgetAmount: rail.limits.total,
335
+ depositAmount: rail.limits.deposit,
336
+ policyRuleId: rail.policyRuleId,
337
+ ...options.fetch ? { fetch: options.fetch } : {}
338
+ });
339
+ const transport = createTaskMarketTransport({
340
+ payer,
341
+ origin: rail.origin,
342
+ allocationId: rail.allocationId,
343
+ policyRuleId: rail.policyRuleId,
344
+ nonInteractive: true,
345
+ models: options.models,
346
+ balances: () => allocations.balances(),
347
+ ...options.fetch ? { fetch: options.fetch } : {}
348
+ });
349
+ return { rail, payer, transport };
350
+ });
351
+ }
352
+ function bodyModel(body) {
353
+ try {
354
+ const model = JSON.parse(body)?.model;
355
+ return typeof model === "string" ? model : void 0;
356
+ } catch {
357
+ return void 0;
358
+ }
359
+ }
360
+ async function neverRecorded(payer, invocationId) {
361
+ try {
362
+ await payer.status(invocationId);
363
+ return false;
364
+ } catch (error) {
365
+ return error.code === "invocation_not_found";
366
+ }
367
+ }
368
+ function createAgentTransport(rails) {
369
+ const byRoute = new Map(rails.map((r) => [r.rail.route, r]));
370
+ let offered = /* @__PURE__ */ new Map();
371
+ return {
372
+ async models(signal) {
373
+ const seen = /* @__PURE__ */ new Map();
374
+ const rows = [];
375
+ for (const { rail, transport } of rails) {
376
+ for (const row of await transport.models(signal)) {
377
+ const id = row.id;
378
+ if (routeOfCatalogRow(row) !== rail.route || typeof id !== "string" || seen.has(id)) {
379
+ continue;
380
+ }
381
+ seen.set(id, rail.route);
382
+ rows.push(row);
383
+ }
384
+ }
385
+ offered = seen;
386
+ return rows;
387
+ },
388
+ async purchase(invocation, signal, onResponse) {
389
+ const rail = byRoute.get(invocation.route);
390
+ if (!rail) {
391
+ throw new XgatePurchaseError("no_rail_for_route", invocation.id, false);
392
+ }
393
+ if (Date.now() >= Date.parse(rail.rail.limits.expiresAt)) {
394
+ throw new XgatePurchaseError("policy_expired", invocation.id, false);
395
+ }
396
+ const model = typeof invocation.body === "string" ? bodyModel(invocation.body) : void 0;
397
+ if (model === void 0 || offered.get(model) !== invocation.route) {
398
+ throw new XgatePurchaseError("model_not_offered", invocation.id, false);
399
+ }
400
+ const leftover = (await rail.payer.channelStatus()).pendingOperation;
401
+ if (leftover && leftover.id !== invocation.id) {
402
+ throw new XgatePurchaseError("channel_operation_pending", invocation.id, false);
403
+ }
404
+ try {
405
+ return await rail.transport.purchase(invocation, signal, onResponse);
406
+ } catch (error) {
407
+ if (error instanceof XgatePurchaseError && error.pending && await neverRecorded(rail.payer, invocation.id)) {
408
+ throw new XgatePurchaseError(
409
+ error.code === "outcome_uncertain" ? "channel_operation_pending" : error.code,
410
+ invocation.id,
411
+ false
412
+ );
413
+ }
414
+ throw error;
415
+ }
416
+ },
417
+ async status() {
418
+ return {
419
+ transport: "taskmarket",
420
+ rails: await Promise.all(
421
+ rails.map(async ({ rail, transport }) => ({
422
+ rail: rail.id,
423
+ ...await transport.status()
424
+ }))
425
+ )
426
+ };
427
+ },
428
+ async reconcile(invocationId) {
429
+ for (const { transport } of rails) {
430
+ try {
431
+ return await transport.reconcile(invocationId);
432
+ } catch (error) {
433
+ if (error.code !== "invocation_not_found") throw error;
434
+ }
435
+ }
436
+ throw new XgateError("invocation_not_found");
437
+ }
438
+ };
439
+ }
440
+ async function reconcileLeftoverOperations(rails, log = () => void 0) {
441
+ for (const { rail, payer } of rails) {
442
+ let pending = (await payer.channelStatus()).pendingOperation;
443
+ if (!pending) continue;
444
+ if (pending.kind === "payment") {
445
+ log(`Reconciling payment ${pending.id} left on rail ${rail.id}`);
446
+ try {
447
+ await payer.reconcile(pending.id);
448
+ } catch (error) {
449
+ log(
450
+ `Payment ${pending.id} could not be reconciled: ${error.code ?? error.message}`
451
+ );
452
+ }
453
+ pending = (await payer.channelStatus()).pendingOperation;
454
+ if (!pending) continue;
455
+ }
456
+ throw new AgentLaunchError(
457
+ "channel_operation_pending",
458
+ `The ${rail.id} channel has an unresolved ${pending.kind} operation ${pending.id} from an earlier session; it must be reconciled before anything else is bought on that channel. Try again later.`
459
+ );
460
+ }
461
+ }
462
+
463
+ // src/lib/agent/policy.ts
464
+ import { randomUUID } from "crypto";
465
+ import { promises as fs } from "fs";
466
+ import path from "path";
467
+ import { z } from "zod";
468
+ var XGATE_ROUTES = [
469
+ "/providers/anthropic/v1/messages",
470
+ "/providers/openai/v1/responses"
471
+ ];
472
+ var WORKSPACE_CONSENT_VERSION = "boat-eu-2026-10";
473
+ var address = z.string().regex(/^0x[0-9a-fA-F]{40}$/, "expected a 0x-prefixed 20-byte address");
474
+ var atomic2 = z.string().regex(/^(0|[1-9][0-9]{0,30})$/, "expected an integer amount string");
475
+ var positiveAtomic = atomic2.refine((value) => BigInt(value) > 0n, "expected an amount above 0");
476
+ var ruleId = z.string().regex(/^[a-z0-9][a-z0-9._-]{0,63}$/, "invalid id");
477
+ var allocationId = z.string().regex(/^[A-Za-z0-9._:-]{1,128}$/, "invalid allocation id");
478
+ var origin2 = z.string().url().refine((value) => {
479
+ const url = new URL(value);
480
+ return url.protocol === "https:" && !url.username && !url.password && url.pathname === "/" && !url.search && !url.hash && !value.endsWith("/");
481
+ }, "expected an HTTPS origin such as https://api.xgate.run");
482
+ var RailLimitsSchema = z.object({
483
+ perRequest: positiveAtomic,
484
+ deposit: positiveAtomic,
485
+ total: positiveAtomic,
486
+ expiresAt: z.string().datetime()
487
+ }).strict();
488
+ var BaseBatchChannelRailSchema = z.object({
489
+ id: ruleId,
490
+ kind: z.literal("base-batch-channel"),
491
+ /** The wallet that funds deposits and signs vouchers; must be the keystore's wallet. */
492
+ wallet: address,
493
+ network: z.enum(["eip155:8453", "eip155:84532"]),
494
+ scheme: z.literal("batch-settlement"),
495
+ /** USDC on `network`. */
496
+ asset: address,
497
+ origin: origin2,
498
+ route: z.enum(XGATE_ROUTES),
499
+ receiver: address,
500
+ receiverAuthorizer: address,
501
+ salt: z.string().regex(/^0x[0-9a-fA-F]{64}$/, "expected a 32-byte hex salt"),
502
+ /** A block at or before the channel's first deposit; return scans start here. */
503
+ fromBlock: z.string().regex(/^(0|[1-9][0-9]{0,19})$/),
504
+ /** The channel's withdraw delay, as onboarding showed it. */
505
+ withdrawDelaySeconds: z.number().int().nonnegative(),
506
+ /**
507
+ * The durable root allocation, shared by every rail of the policy: all routes and every
508
+ * renewal's history count against its one `total`.
509
+ */
510
+ budgetAllocationId: allocationId,
511
+ /** The current session allocation, a child of the budget, bound to the channel. */
512
+ allocationId,
513
+ /** The x402 policy rule that authorizes this rail's vouchers and deposits. */
514
+ policyRuleId: ruleId,
515
+ limits: RailLimitsSchema
516
+ }).strict();
517
+ var AgentRailSchema = z.discriminatedUnion("kind", [BaseBatchChannelRailSchema]);
518
+ var MAX_RETIRED_RAILS = 64;
519
+ var AgentPolicySchema = z.object({
520
+ version: z.literal(1),
521
+ approvedAt: z.string().datetime(),
522
+ /** xgate model ids the user approved. Only those the router also qualifies are offered. */
523
+ models: z.array(z.string().regex(/^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/, "invalid model id")).min(1).max(64),
524
+ rails: z.array(AgentRailSchema).min(1).max(XGATE_ROUTES.length),
525
+ /**
526
+ * Channels that buy nothing more but may still hold a refundable deposit: those a renewal
527
+ * replaced (a channel's budget and allocation are fixed for its life, ADR-0243, so a larger
528
+ * budget is a new channel under the same budget allocation) and those whose route no longer
529
+ * has an approved model. Their session allocations are closed. The launcher and
530
+ * `assessAgentPolicy` ignore them; `taskmarket agent status` and `close` (#1198) read them to
531
+ * show and refund what they hold. An entry is removed only once its allocation is closed and
532
+ * the journal shows nothing deposited, pending or refundable on its channel; onboarding never
533
+ * drops one to make room, and refuses to retire more than `MAX_RETIRED_RAILS`.
534
+ */
535
+ retiredRails: z.array(AgentRailSchema).max(MAX_RETIRED_RAILS).default([]),
536
+ workspace: z.object({
537
+ consentVersion: z.string().min(1).max(64),
538
+ approvedAt: z.string().datetime()
539
+ }).strict()
540
+ }).strict().superRefine((policy, ctx) => {
541
+ const ids = /* @__PURE__ */ new Set();
542
+ const routes = /* @__PURE__ */ new Set();
543
+ policy.rails.forEach((rail, index) => {
544
+ if (ids.has(rail.id)) {
545
+ ctx.addIssue({
546
+ code: "custom",
547
+ message: `duplicate rail id ${rail.id}`,
548
+ path: ["rails", index]
549
+ });
550
+ }
551
+ if (routes.has(rail.route)) {
552
+ ctx.addIssue({
553
+ code: "custom",
554
+ message: `more than one rail pays ${rail.route}`,
555
+ path: ["rails", index]
556
+ });
557
+ }
558
+ const { perRequest, deposit, total } = rail.limits;
559
+ if (BigInt(perRequest) > BigInt(deposit) || BigInt(deposit) > BigInt(total)) {
560
+ ctx.addIssue({
561
+ code: "custom",
562
+ message: "limits must satisfy perRequest <= deposit <= total",
563
+ path: ["rails", index, "limits"]
564
+ });
565
+ }
566
+ if (rail.budgetAllocationId !== policy.rails[0].budgetAllocationId) {
567
+ ctx.addIssue({
568
+ code: "custom",
569
+ message: "every rail must draw on the one aggregate budget allocation",
570
+ path: ["rails", index, "budgetAllocationId"]
571
+ });
572
+ }
573
+ ids.add(rail.id);
574
+ routes.add(rail.route);
575
+ });
576
+ if (new Set(policy.models).size !== policy.models.length) {
577
+ ctx.addIssue({ code: "custom", message: "duplicate model id", path: ["models"] });
578
+ }
579
+ });
580
+ function getAgentPolicyPath() {
581
+ return path.join(getTaskmarketHome(), "agent", "policy.json");
582
+ }
583
+ async function readAgentPolicy(policyPath = getAgentPolicyPath()) {
584
+ let raw;
585
+ try {
586
+ raw = await fs.readFile(policyPath, "utf8");
587
+ } catch (error) {
588
+ if (error.code === "ENOENT") return void 0;
589
+ throw error;
590
+ }
591
+ let parsed;
592
+ try {
593
+ parsed = JSON.parse(raw);
594
+ } catch {
595
+ throw new AgentLaunchError("invalid_agent_policy", `${policyPath} is not valid JSON`);
596
+ }
597
+ const result = AgentPolicySchema.safeParse(parsed);
598
+ if (!result.success) {
599
+ const issue = result.error.issues[0];
600
+ throw new AgentLaunchError(
601
+ "invalid_agent_policy",
602
+ `${policyPath}: ${issue.path.join(".") || "(root)"}: ${issue.message}`
603
+ );
604
+ }
605
+ return result.data;
606
+ }
607
+ async function loadAgentPolicy(policyPath = getAgentPolicyPath()) {
608
+ const policy = await readAgentPolicy(policyPath);
609
+ if (!policy) {
610
+ throw new AgentLaunchError(
611
+ "setup_required",
612
+ `taskmarket agent has no approved spending policy at ${policyPath}; run taskmarket agent at a terminal to set it up`,
613
+ { reasons: ["no_policy"] }
614
+ );
615
+ }
616
+ return policy;
617
+ }
618
+ async function saveAgentPolicy(policy, policyPath = getAgentPolicyPath()) {
619
+ const validated = AgentPolicySchema.parse(policy);
620
+ await fs.mkdir(path.dirname(policyPath), { recursive: true, mode: 448 });
621
+ const temporary = `${policyPath}.${process.pid}.${randomUUID()}.tmp`;
622
+ try {
623
+ await fs.writeFile(temporary, `${JSON.stringify(validated, null, 2)}
624
+ `, {
625
+ mode: 384,
626
+ flag: "wx"
627
+ });
628
+ await fs.rename(temporary, policyPath);
629
+ } finally {
630
+ await fs.rm(temporary, { force: true }).catch(() => void 0);
631
+ }
632
+ }
633
+ async function assessAgentPolicy(policy, options) {
634
+ const now = options.now ?? /* @__PURE__ */ new Date();
635
+ const reasons = [];
636
+ const usage = [];
637
+ if (policy.workspace.consentVersion !== WORKSPACE_CONSENT_VERSION) {
638
+ reasons.push({ reason: "consent_outdated" });
639
+ }
640
+ for (const rail of policy.rails) {
641
+ if (now.getTime() >= Date.parse(rail.limits.expiresAt)) {
642
+ reasons.push({ reason: "expired", railId: rail.id });
643
+ }
644
+ let budget;
645
+ let session;
646
+ try {
647
+ [budget, session] = await Promise.all([
648
+ options.allocation(rail.budgetAllocationId),
649
+ options.allocation(rail.allocationId)
650
+ ]);
651
+ } catch (error) {
652
+ if (error.code !== "allocation_not_found") throw error;
653
+ reasons.push({ reason: "allocation_unavailable", railId: rail.id });
654
+ continue;
655
+ }
656
+ usage.push({
657
+ railId: rail.id,
658
+ used: budget.usage.used,
659
+ cap: budget.allocation.caps.amount
660
+ });
661
+ if (budget.allocation.closedAt || session.allocation.closedAt) {
662
+ reasons.push({ reason: "allocation_unavailable", railId: rail.id });
663
+ continue;
664
+ }
665
+ const remaining = BigInt(budget.allocation.caps.amount) - BigInt(budget.usage.used);
666
+ if (remaining < BigInt(rail.limits.perRequest)) {
667
+ reasons.push({ reason: "exhausted", railId: rail.id });
668
+ }
669
+ }
670
+ return { reasons, usage };
671
+ }
672
+ function selectModels(policy, requested) {
673
+ if (requested === void 0) return [...policy.models];
674
+ if (!policy.models.includes(requested)) {
675
+ throw new AgentLaunchError(
676
+ "model_not_allowed",
677
+ `Model ${requested} is not in the approved spending policy (${policy.models.join(", ")})`
678
+ );
679
+ }
680
+ return [requested];
681
+ }
682
+
683
+ // src/lib/agent/retired.ts
684
+ async function settledRetiredRails(store, retired) {
685
+ const settled = /* @__PURE__ */ new Set();
686
+ if (retired.length === 0) return settled;
687
+ const balances = await store.balances();
688
+ for (const rail of retired) {
689
+ let session;
690
+ try {
691
+ session = (await store.get(rail.allocationId)).allocation;
692
+ } catch {
693
+ continue;
694
+ }
695
+ const sessionKey = session.funding?.sessionKey;
696
+ if (!session.closedAt || !sessionKey) continue;
697
+ const balance = balances.find(
698
+ (entry) => entry.railId === BASE_CHANNEL_RAIL_ID && entry.sessionKey === sessionKey
699
+ );
700
+ if (!balance || balance.depositing === "0" && balance.pending === "0" && balance.refundable === "0") {
701
+ settled.add(rail.salt);
702
+ }
703
+ }
704
+ return settled;
705
+ }
706
+ async function pruneRetiredRails(store) {
707
+ const policy = await readAgentPolicy();
708
+ if (!policy || policy.retiredRails.length === 0) return [];
709
+ const settled = await settledRetiredRails(store, policy.retiredRails);
710
+ if (settled.size === 0) return [];
711
+ await saveAgentPolicy({
712
+ ...policy,
713
+ retiredRails: policy.retiredRails.filter((rail) => !settled.has(rail.salt))
714
+ });
715
+ return [...settled];
716
+ }
717
+
718
+ // src/lib/agent/channels.ts
719
+ async function openAgentChannels(options = {}) {
720
+ if (!options.keystore && !await keystoreExists()) {
721
+ throw new AgentLaunchError(
722
+ "wallet_required",
723
+ "No wallet is set up; run `taskmarket wallet import` or `taskmarket init` first"
724
+ );
725
+ }
726
+ const keystore = options.keystore ?? await loadKeystore();
727
+ const policy = await loadAgentPolicy();
728
+ let account;
729
+ const getAccount = () => {
730
+ account ??= createWalletAccountFromKeystore(keystore).catch((error) => {
731
+ account = void 0;
732
+ throw error;
733
+ });
734
+ return account;
735
+ };
736
+ const common = {
737
+ policy,
738
+ models: options.models ? options.models(policy) : [...policy.models],
739
+ walletAddress: keystore.walletAddress,
740
+ getAccount,
741
+ ...options.fetch ? { fetch: options.fetch } : {}
742
+ };
743
+ const rails = await createAgentRails(common);
744
+ const retired = await createAgentRails({
745
+ ...common,
746
+ rails: policy.retiredRails.filter(
747
+ (rail) => rail.wallet.toLowerCase() === keystore.walletAddress.toLowerCase()
748
+ )
749
+ });
750
+ return { keystore, policy, rails, retired };
751
+ }
752
+ function selectChannel(opened, choice) {
753
+ if (choice.channel !== void 0) {
754
+ const wanted = choice.channel.toLowerCase();
755
+ for (const [list, retired] of [
756
+ [opened.rails, false],
757
+ [opened.retired, true]
758
+ ]) {
759
+ const rail = list.find((candidate) => candidate.payer.channelId.toLowerCase() === wanted);
760
+ if (rail) return { rail, retired };
761
+ }
762
+ throw new AgentLaunchError(
763
+ "rail_not_found",
764
+ `No channel ${choice.channel} in the spending policy; taskmarket agent status lists them`
765
+ );
766
+ }
767
+ return { rail: selectRail(opened.rails, choice.rail), retired: false };
768
+ }
769
+ function selectRail(rails, id) {
770
+ if (id !== void 0) {
771
+ const rail = rails.find((candidate) => candidate.rail.id === id);
772
+ if (!rail) {
773
+ throw new AgentLaunchError(
774
+ "rail_not_found",
775
+ `No rail ${id} in the spending policy (${rails.map((r) => r.rail.id).join(", ")})`
776
+ );
777
+ }
778
+ return rail;
779
+ }
780
+ if (rails.length !== 1) {
781
+ throw new AgentLaunchError(
782
+ "rail_required",
783
+ `The spending policy has ${rails.length} rails; name one with --rail (${rails.map((r) => r.rail.id).join(", ")})`
784
+ );
785
+ }
786
+ return rails[0];
787
+ }
788
+ async function channelView(rail, status, options = {}) {
789
+ const s = status ?? await rail.payer.channelStatus();
790
+ const f = s.funding;
791
+ const budget = BigInt(rail.rail.limits.total);
792
+ const room = budget - BigInt(f.locked) - BigInt(f.depositing);
793
+ const blocking = s.divergences.filter((d) => d.blocking).map((d) => d.kind);
794
+ return {
795
+ rail: rail.rail.id,
796
+ route: rail.rail.route,
797
+ network: rail.rail.network,
798
+ asset: f.asset,
799
+ channelId: s.channelId,
800
+ state: s.closed ? "closed" : s.closing ? "closing" : s.withdrawalPending ? "withdrawal_pending" : blocking.length > 0 ? "blocked" : "open",
801
+ amounts: {
802
+ budget: budget.toString(),
803
+ depositTarget: rail.rail.limits.deposit,
804
+ depositing: f.depositing,
805
+ locked: f.locked,
806
+ charged: f.charged,
807
+ pending: f.pending,
808
+ refunded: f.refunded,
809
+ refundable: f.refundable,
810
+ lifetimeRoom: (room > 0n ? room : 0n).toString()
811
+ },
812
+ pendingOperation: s.pendingOperation ? { kind: s.pendingOperation.kind, id: s.pendingOperation.id } : null,
813
+ blockingDivergences: blocking,
814
+ retired: options.retired ?? false
815
+ };
816
+ }
817
+ function allocationsReader() {
818
+ const policyPath = getX402PolicyPath();
819
+ return createX402Allocations({
820
+ journalPath: getX402JournalPath(),
821
+ loadPolicy: (options) => loadX402Policy(policyPath, options)
822
+ });
823
+ }
824
+ async function allocationViews(rails, extra = []) {
825
+ const ids = [
826
+ .../* @__PURE__ */ new Set([
827
+ ...rails.flatMap((r) => [r.rail.budgetAllocationId, r.rail.allocationId]),
828
+ ...extra
829
+ ])
830
+ ].filter((id) => id !== void 0);
831
+ const reader = allocationsReader();
832
+ const views = [];
833
+ for (const id of ids) {
834
+ try {
835
+ const { allocation, usage } = await reader.get(id);
836
+ views.push({
837
+ allocationId: id,
838
+ exposure: allocation.exposure,
839
+ closed: allocation.closedAt !== void 0,
840
+ caps: {
841
+ amount: allocation.caps.amount,
842
+ ...allocation.caps.perDay ? { perDay: allocation.caps.perDay } : {},
843
+ ...allocation.caps.perMonth ? { perMonth: allocation.caps.perMonth } : {}
844
+ },
845
+ used: { total: usage.used, today: usage.day, thisMonth: usage.month }
846
+ });
847
+ } catch {
848
+ }
849
+ }
850
+ return views;
851
+ }
852
+ var UNDECIDED = /* @__PURE__ */ new Set([
853
+ "reserved",
854
+ "approval_pending",
855
+ "ready",
856
+ "dispatched",
857
+ "unknown",
858
+ "settled_amount_unknown"
859
+ ]);
860
+ async function uncertainPurchases(rails, statuses = /* @__PURE__ */ new Map()) {
861
+ const records = await listX402Payments({}, getX402JournalPath());
862
+ const found = [];
863
+ for (const record2 of records) {
864
+ if (!record2.invocationId || record2.rail?.id !== BASE_CHANNEL_RAIL_ID) continue;
865
+ if (!UNDECIDED.has(record2.state)) continue;
866
+ const rail = rails.find(
867
+ (r) => record2.origin === r.rail.origin && record2.pathname === r.rail.route
868
+ );
869
+ if (!rail) continue;
870
+ const lease = statuses.get(rail.rail.id)?.pendingOperation;
871
+ found.push({
872
+ invocationId: record2.invocationId,
873
+ rail: rail.rail.id,
874
+ state: record2.state,
875
+ authorizedAtomic: record2.authorizedAmount,
876
+ createdAt: record2.createdAt,
877
+ blocking: lease?.kind === "payment" && lease.id === record2.invocationId
878
+ });
879
+ }
880
+ for (const rail of rails) {
881
+ const lease = statuses.get(rail.rail.id)?.pendingOperation;
882
+ if (lease?.kind === "payment" && !found.some((p) => p.invocationId === lease.id)) {
883
+ found.push({
884
+ invocationId: lease.id,
885
+ rail: rail.rail.id,
886
+ state: "leased",
887
+ authorizedAtomic: "0",
888
+ createdAt: "",
889
+ blocking: true
890
+ });
891
+ }
892
+ }
893
+ return found.sort((a, b) => a.createdAt.localeCompare(b.createdAt));
894
+ }
895
+ function withdrawalHatch(channel) {
896
+ return {
897
+ kind: "timed-withdrawal",
898
+ delaySeconds: WITHDRAW_DELAY_SECONDS,
899
+ state: channel.state === "withdrawal_pending" ? "pending" : "not_started",
900
+ availableInCli: false,
901
+ note: `If the router cannot refund cooperatively, the wallet can withdraw the escrow on chain itself: initiate a timed withdrawal, wait ${WITHDRAW_DELAY_SECONDS / 60} minutes, then finalize it to get back what valid claims do not cover. It needs ETH on Base for gas, is never started automatically, and this CLI does not send it yet.`
902
+ };
903
+ }
904
+ async function requestTopUp(rail, amount) {
905
+ if (!/^[1-9][0-9]{0,30}$/.test(amount)) {
906
+ throw new AgentLaunchError("invalid_amount", "A top-up must be a positive USDC amount");
907
+ }
908
+ const channel = await channelView(rail);
909
+ if (channel.state === "closed" || channel.state === "closing") {
910
+ throw new AgentLaunchError(
911
+ "channel_closed",
912
+ `The ${rail.rail.id} channel is ${channel.state}; it takes no more deposits`
913
+ );
914
+ }
915
+ if (channel.state === "withdrawal_pending") {
916
+ throw new AgentLaunchError(
917
+ "channel_closed",
918
+ `A timed withdrawal was started on the ${rail.rail.id} channel; it takes no more deposits`
919
+ );
920
+ }
921
+ if (channel.state === "blocked") {
922
+ throw new AgentLaunchError(
923
+ "channel_blocked",
924
+ `The ${rail.rail.id} channel is blocked (${channel.blockingDivergences.join(", ")}); nothing is deposited until the records agree`
925
+ );
926
+ }
927
+ const room = BigInt(channel.amounts.lifetimeRoom);
928
+ if (BigInt(amount) > room) {
929
+ throw new AgentLaunchError(
930
+ "topup_over_cap",
931
+ `A top-up of ${formatUsdc(amount)} is over the approved budget: ${formatUsdc(channel.amounts.budget)} in total, ${formatUsdc(BigInt(channel.amounts.budget) - room)} already deposited, so at most ${formatUsdc(room)} more. Refunds do not free budget; a larger budget needs a new approval of the spending policy.`
932
+ );
933
+ }
934
+ try {
935
+ const allocation = await allocationsReader().get(rail.rail.allocationId);
936
+ if (allocation.allocation.closedAt !== void 0) {
937
+ throw new AgentLaunchError(
938
+ "allocation_closed",
939
+ `The ${rail.rail.allocationId} allocation is closed; it admits no more purchases or deposits`
940
+ );
941
+ }
942
+ } catch (error) {
943
+ if (error instanceof AgentLaunchError) throw error;
944
+ }
945
+ return {
946
+ rail: rail.rail.id,
947
+ amount,
948
+ outcome: "automatic",
949
+ channel,
950
+ note: `${formatUsdc(amount)} fits the approved budget (${formatUsdc(room)} may still be deposited). The channel tops itself up: when a turn needs more than the ${formatUsdc(channel.amounts.refundable)} left, a gasless deposit of ${formatUsdc(channel.amounts.depositTarget)} (or what the turn needs, if more) goes with it. The router takes a deposit only together with a turn's voucher, so nothing was deposited now.`
951
+ };
952
+ }
953
+ function outcomeOf(operation) {
954
+ if (operation.state === "confirmed") return "refunded";
955
+ if (operation.state === "failed") return "failed";
956
+ return "pending";
957
+ }
958
+ async function closeSession(allocationId2) {
959
+ try {
960
+ await allocationsReader().close(allocationId2);
961
+ return true;
962
+ } catch (error) {
963
+ if (error.code === "allocation_not_found") return false;
964
+ throw error;
965
+ }
966
+ }
967
+ function closeLines(channel) {
968
+ const a = channel.amounts;
969
+ return [
970
+ `Close the ${channel.rail} channel (${channel.network}) and refund its unused balance`,
971
+ `Locked ${formatUsdc(a.locked)} (deposited); charged ${formatUsdc(a.charged)} (kept by the router for turns already paid); pending vouchers ${formatUsdc(a.pending)}`,
972
+ `Refundable now: ${formatUsdc(a.refundable)}, returned to the wallet by the router`,
973
+ "After closing, the channel takes no more turns. The budget is not freed by the refund."
974
+ ];
975
+ }
976
+ async function closeChannel(rail, confirm, options = {}) {
977
+ const retired = options.retired ?? false;
978
+ const status = await rail.payer.channelStatus();
979
+ const channel = await channelView(rail, status, { retired });
980
+ const result = async (outcome, operation2, reconciled) => {
981
+ const after = operation2 ? await channelView(rail, void 0, { retired }) : channel;
982
+ let sessionClosed = false;
983
+ let dropped = false;
984
+ if (outcome !== "failed") {
985
+ sessionClosed = await closeSession(rail.rail.allocationId);
986
+ if (retired) {
987
+ dropped = (await pruneRetiredRails(allocationsReader())).includes(rail.rail.salt);
988
+ }
989
+ }
990
+ return {
991
+ rail: rail.rail.id,
992
+ outcome,
993
+ reconciled,
994
+ retired,
995
+ sessionClosed,
996
+ dropped,
997
+ operation: operation2,
998
+ channel: after,
999
+ withdrawal: withdrawalHatch(after)
1000
+ };
1001
+ };
1002
+ if (status.closed) return result("closed", null, false);
1003
+ const lease = status.pendingOperation;
1004
+ if (lease?.kind === "lifecycle") {
1005
+ const operation2 = await rail.payer.reconcileOperation(lease.id);
1006
+ return result(outcomeOf(operation2), operation2, true);
1007
+ }
1008
+ if (lease?.kind === "payment") {
1009
+ throw new AgentLaunchError(
1010
+ "channel_operation_pending",
1011
+ `Purchase ${lease.id} on the ${rail.rail.id} channel is unresolved; reconcile it first with taskmarket agent reconcile ${lease.id}. Nothing was refunded.`
1012
+ );
1013
+ }
1014
+ if (channel.state === "blocked") {
1015
+ throw new AgentLaunchError(
1016
+ "channel_blocked",
1017
+ `The ${rail.rail.id} channel is blocked (${channel.blockingDivergences.join(", ")}), so the router cannot refund it cooperatively. ${withdrawalHatch(channel).note}`
1018
+ );
1019
+ }
1020
+ if (channel.state === "withdrawal_pending") {
1021
+ throw new AgentLaunchError(
1022
+ "channel_closed",
1023
+ `A timed withdrawal was started on the ${rail.rail.id} channel; finish it on chain instead`
1024
+ );
1025
+ }
1026
+ if (!await confirm(closeLines(channel))) {
1027
+ throw new AgentLaunchError("not_approved", "The channel was not closed");
1028
+ }
1029
+ const operation = await rail.payer.refund({
1030
+ operationId: `agent-close-${randomUUID2()}`,
1031
+ close: true
1032
+ });
1033
+ return result(outcomeOf(operation), operation, false);
1034
+ }
1035
+
1036
+ export {
1037
+ XgateError,
1038
+ XgatePurchaseError,
1039
+ routeOfCatalogRow,
1040
+ createAgentTransport,
1041
+ reconcileLeftoverOperations,
1042
+ XGATE_ROUTES,
1043
+ WORKSPACE_CONSENT_VERSION,
1044
+ MAX_RETIRED_RAILS,
1045
+ getAgentPolicyPath,
1046
+ readAgentPolicy,
1047
+ loadAgentPolicy,
1048
+ saveAgentPolicy,
1049
+ assessAgentPolicy,
1050
+ selectModels,
1051
+ settledRetiredRails,
1052
+ openAgentChannels,
1053
+ selectChannel,
1054
+ selectRail,
1055
+ channelView,
1056
+ allocationViews,
1057
+ uncertainPurchases,
1058
+ withdrawalHatch,
1059
+ requestTopUp,
1060
+ closeLines,
1061
+ closeChannel
1062
+ };