@lucid-agents/taskmarket 1.17.2 → 1.18.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.
@@ -6,21 +6,23 @@ import {
6
6
  createBaseChannelRpcReader,
7
7
  createWalletAccountFromKeystore,
8
8
  createX402Allocations,
9
- getTaskmarketHome,
10
9
  getX402JournalPath,
11
10
  getX402PolicyPath,
12
- keystoreExists,
13
11
  listX402Payments,
14
- loadKeystore,
15
12
  loadX402Policy,
16
13
  rpcUrlForNetwork
17
- } from "./chunk-FRV3ZHUR.js";
14
+ } from "./chunk-S4A6V3J3.js";
18
15
  import {
19
- AgentLaunchError
20
- } from "./chunk-YM4S6IE4.js";
16
+ getTaskmarketHome,
17
+ keystoreExists,
18
+ loadKeystore
19
+ } from "./chunk-75LS6XS6.js";
21
20
  import {
22
21
  formatUsdc
23
- } from "./chunk-73RXOYME.js";
22
+ } from "./chunk-EB6OJBF5.js";
23
+ import {
24
+ AgentLaunchError
25
+ } from "./chunk-YM4S6IE4.js";
24
26
 
25
27
  // src/lib/agent/channels.ts
26
28
  import { randomUUID as randomUUID2 } from "crypto";
@@ -334,7 +336,11 @@ async function createAgentRails(options) {
334
336
  budgetAmount: rail.limits.total,
335
337
  depositAmount: rail.limits.deposit,
336
338
  policyRuleId: rail.policyRuleId,
337
- ...options.fetch ? { fetch: options.fetch } : {}
339
+ ...options.fetch ? { fetch: options.fetch } : {},
340
+ confirmationWait: {
341
+ ...options.confirmationWait,
342
+ ...options.onConfirmationWait ? { onWait: (progress) => options.onConfirmationWait(rail, progress) } : {}
343
+ }
338
344
  });
339
345
  const transport = createTaskMarketTransport({
340
346
  payer,
@@ -438,10 +444,12 @@ function createAgentTransport(rails) {
438
444
  };
439
445
  }
440
446
  async function reconcileLeftoverOperations(rails, log = () => void 0) {
441
- for (const { rail, payer } of rails) {
447
+ const usable = [];
448
+ const blocked = [];
449
+ for (const entry of rails) {
450
+ const { rail, payer } = entry;
442
451
  let pending = (await payer.channelStatus()).pendingOperation;
443
- if (!pending) continue;
444
- if (pending.kind === "payment") {
452
+ if (pending?.kind === "payment") {
445
453
  log(`Reconciling payment ${pending.id} left on rail ${rail.id}`);
446
454
  try {
447
455
  await payer.reconcile(pending.id);
@@ -451,13 +459,22 @@ async function reconcileLeftoverOperations(rails, log = () => void 0) {
451
459
  );
452
460
  }
453
461
  pending = (await payer.channelStatus()).pendingOperation;
454
- if (!pending) continue;
455
462
  }
463
+ if (!pending) {
464
+ usable.push(entry);
465
+ continue;
466
+ }
467
+ const reason = `The ${rail.id} channel has an unresolved ${pending.kind} operation ${pending.id} from an earlier session; it must be reconciled (taskmarket agent reconcile ${pending.id}) before anything else is bought on that channel.`;
468
+ log(`${reason} Its models are left out of this session.`);
469
+ blocked.push(reason);
470
+ }
471
+ if (usable.length === 0 && blocked.length > 0) {
456
472
  throw new AgentLaunchError(
457
473
  "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.`
474
+ `${blocked.join(" ")} Try again later.`
459
475
  );
460
476
  }
477
+ return usable;
461
478
  }
462
479
 
463
480
  // src/lib/agent/policy.ts
@@ -469,7 +486,7 @@ var XGATE_ROUTES = [
469
486
  "/providers/anthropic/v1/messages",
470
487
  "/providers/openai/v1/responses"
471
488
  ];
472
- var WORKSPACE_CONSENT_VERSION = "boat-eu-2026-10";
489
+ var WORKSPACE_CONSENT_VERSION = "boat-eu-2026-10-sync";
473
490
  var address = z.string().regex(/^0x[0-9a-fA-F]{40}$/, "expected a 0x-prefixed 20-byte address");
474
491
  var atomic2 = z.string().regex(/^(0|[1-9][0-9]{0,30})$/, "expected an integer amount string");
475
492
  var positiveAtomic = atomic2.refine((value) => BigInt(value) > 0n, "expected an amount above 0");
@@ -669,15 +686,31 @@ async function assessAgentPolicy(policy, options) {
669
686
  }
670
687
  return { reasons, usage };
671
688
  }
672
- function selectModels(policy, requested) {
673
- if (requested === void 0) return [...policy.models];
674
- if (!policy.models.includes(requested)) {
689
+ function requireApprovedModel(policy, requested) {
690
+ if (requested !== void 0 && !policy.models.includes(requested)) {
675
691
  throw new AgentLaunchError(
676
692
  "model_not_allowed",
677
693
  `Model ${requested} is not in the approved spending policy (${policy.models.join(", ")})`
678
694
  );
679
695
  }
680
- return [requested];
696
+ }
697
+ function planSessionModels(options) {
698
+ const { approved, offered, requested, previous } = options;
699
+ const unavailable = approved.filter((model) => !offered.includes(model));
700
+ if (offered.length === 0) {
701
+ throw new AgentLaunchError(
702
+ "no_qualified_models",
703
+ `None of the approved models (${approved.join(", ")}) is qualified by xgate on a route the policy pays for`
704
+ );
705
+ }
706
+ if (requested !== void 0 && !offered.includes(requested)) {
707
+ throw new AgentLaunchError(
708
+ "model_not_offered",
709
+ `Model ${requested} is approved, but xgate does not offer it on a route a usable rail of the policy pays for, so this session cannot buy it. This session offers ${offered.join(", ")}`
710
+ );
711
+ }
712
+ const start = requested ?? (previous !== void 0 && offered.includes(previous) ? previous : offered[0]);
713
+ return { start, unavailable };
681
714
  }
682
715
 
683
716
  // src/lib/agent/retired.ts
@@ -738,7 +771,9 @@ async function openAgentChannels(options = {}) {
738
771
  models: options.models ? options.models(policy) : [...policy.models],
739
772
  walletAddress: keystore.walletAddress,
740
773
  getAccount,
741
- ...options.fetch ? { fetch: options.fetch } : {}
774
+ ...options.fetch ? { fetch: options.fetch } : {},
775
+ ...options.onConfirmationWait ? { onConfirmationWait: options.onConfirmationWait } : {},
776
+ ...options.confirmationWait ? { confirmationWait: options.confirmationWait } : {}
742
777
  };
743
778
  const rails = await createAgentRails(common);
744
779
  const retired = await createAgentRails({
@@ -863,9 +898,7 @@ async function uncertainPurchases(rails, statuses = /* @__PURE__ */ new Map()) {
863
898
  for (const record2 of records) {
864
899
  if (!record2.invocationId || record2.rail?.id !== BASE_CHANNEL_RAIL_ID) continue;
865
900
  if (!UNDECIDED.has(record2.state)) continue;
866
- const rail = rails.find(
867
- (r) => record2.origin === r.rail.origin && record2.pathname === r.rail.route
868
- );
901
+ const rail = rails.find((r) => record2.funding?.sessionKey === r.payer.sessionKey);
869
902
  if (!rail) continue;
870
903
  const lease = statuses.get(rail.rail.id)?.pendingOperation;
871
904
  found.push({
@@ -955,6 +988,34 @@ function outcomeOf(operation) {
955
988
  if (operation.state === "failed") return "failed";
956
989
  return "pending";
957
990
  }
991
+ var REFUND_WAIT_TIMEOUT_MS = 9e4;
992
+ var REFUND_WAIT_INTERVAL_MS = 3e3;
993
+ async function awaitRefund(rail, operation, wait = {}) {
994
+ const timeoutMs = wait.timeoutMs ?? REFUND_WAIT_TIMEOUT_MS;
995
+ const intervalMs = Math.max(1, wait.intervalMs ?? REFUND_WAIT_INTERVAL_MS);
996
+ const sleep = wait.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
997
+ let current = operation;
998
+ let waitedMs = 0;
999
+ while ((current.state === "dispatched" || current.state === "unknown") && current.transaction && waitedMs < timeoutMs) {
1000
+ try {
1001
+ wait.onWait?.({
1002
+ transaction: current.transaction,
1003
+ amount: current.amount,
1004
+ waitedMs,
1005
+ timeoutMs
1006
+ });
1007
+ } catch {
1008
+ }
1009
+ const step = Math.min(intervalMs, timeoutMs - waitedMs);
1010
+ await sleep(step);
1011
+ waitedMs += step;
1012
+ try {
1013
+ current = await rail.payer.reconcileOperation(current.operationId);
1014
+ } catch {
1015
+ }
1016
+ }
1017
+ return current;
1018
+ }
958
1019
  async function closeSession(allocationId2) {
959
1020
  try {
960
1021
  await allocationsReader().close(allocationId2);
@@ -975,7 +1036,11 @@ function closeLines(channel) {
975
1036
  }
976
1037
  async function closeChannel(rail, confirm, options = {}) {
977
1038
  const retired = options.retired ?? false;
978
- const status = await rail.payer.channelStatus();
1039
+ let status = await rail.payer.channelStatus();
1040
+ if (status.pendingOperation?.kind === "payment" && options.reconcilePurchase) {
1041
+ await options.reconcilePurchase(status.pendingOperation.id);
1042
+ status = await rail.payer.channelStatus();
1043
+ }
979
1044
  const channel = await channelView(rail, status, { retired });
980
1045
  const result = async (outcome, operation2, reconciled) => {
981
1046
  const after = operation2 ? await channelView(rail, void 0, { retired }) : channel;
@@ -1002,7 +1067,11 @@ async function closeChannel(rail, confirm, options = {}) {
1002
1067
  if (status.closed) return result("closed", null, false);
1003
1068
  const lease = status.pendingOperation;
1004
1069
  if (lease?.kind === "lifecycle") {
1005
- const operation2 = await rail.payer.reconcileOperation(lease.id);
1070
+ const operation2 = await awaitRefund(
1071
+ rail,
1072
+ await rail.payer.reconcileOperation(lease.id),
1073
+ options.wait
1074
+ );
1006
1075
  return result(outcomeOf(operation2), operation2, true);
1007
1076
  }
1008
1077
  if (lease?.kind === "payment") {
@@ -1026,10 +1095,17 @@ async function closeChannel(rail, confirm, options = {}) {
1026
1095
  if (!await confirm(closeLines(channel))) {
1027
1096
  throw new AgentLaunchError("not_approved", "The channel was not closed");
1028
1097
  }
1029
- const operation = await rail.payer.refund({
1030
- operationId: `agent-close-${randomUUID2()}`,
1031
- close: true
1032
- });
1098
+ const operationId = `agent-close-${randomUUID2()}`;
1099
+ let operation;
1100
+ try {
1101
+ operation = await rail.payer.refund({ operationId, close: true });
1102
+ } catch (error) {
1103
+ if (error.code !== "outcome_uncertain") throw error;
1104
+ const recorded = await rail.payer.reconcileOperation(operationId).catch(() => void 0);
1105
+ if (!recorded || recorded.state !== "dispatched" && recorded.state !== "unknown") throw error;
1106
+ operation = recorded;
1107
+ }
1108
+ operation = await awaitRefund(rail, operation, options.wait);
1033
1109
  return result(outcomeOf(operation), operation, false);
1034
1110
  }
1035
1111
 
@@ -1047,7 +1123,8 @@ export {
1047
1123
  loadAgentPolicy,
1048
1124
  saveAgentPolicy,
1049
1125
  assessAgentPolicy,
1050
- selectModels,
1126
+ requireApprovedModel,
1127
+ planSessionModels,
1051
1128
  settledRetiredRails,
1052
1129
  openAgentChannels,
1053
1130
  selectChannel,