@dvmkit/sdk 0.14.1 → 0.15.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 (53) hide show
  1. package/dist/{chunk-ITRSITFP.js → chunk-2I6NXIPG.js} +1 -1
  2. package/dist/{chunk-TKJBUFYY.js → chunk-33J3NQDZ.js} +1 -1
  3. package/dist/{chunk-7KQLPVV7.js → chunk-7NZRJPEP.js} +1 -1
  4. package/dist/{chunk-Z4FOPROB.js → chunk-BAHZK334.js} +2 -0
  5. package/dist/{chunk-D4FFS3IS.js → chunk-CRH6BMMG.js} +1 -1
  6. package/dist/{chunk-LDKV4UHI.js → chunk-CVT5LTAK.js} +1 -1
  7. package/dist/{chunk-EP7OVWI5.js → chunk-FOXGN4KQ.js} +1 -1
  8. package/dist/{chunk-VWIMQ5H5.js → chunk-G47UY3BK.js} +1 -1
  9. package/dist/{chunk-JLT2YIBS.js → chunk-GB2N4NG5.js} +2 -2
  10. package/dist/{chunk-E7ZBCT5U.js → chunk-I2H2XYQV.js} +1 -1
  11. package/dist/{chunk-3YFFZLW7.js → chunk-IUODXRQN.js} +2 -2
  12. package/dist/{chunk-566CFXGU.js → chunk-IVW5UMD3.js} +1 -1
  13. package/dist/{chunk-AFV77S4C.js → chunk-KVKJ7R5V.js} +81 -2
  14. package/dist/{chunk-A2YESDQM.js → chunk-LM6E4YHQ.js} +2 -2
  15. package/dist/{chunk-5JSPTVIK.js → chunk-LZFZGY7L.js} +1 -1
  16. package/dist/{chunk-FFRTAQUW.js → chunk-P5KQKLWL.js} +1 -1
  17. package/dist/{chunk-KJZVTWMX.js → chunk-PKLNMMKF.js} +52 -15
  18. package/dist/{chunk-M4NFYEEY.js → chunk-QRYGBKCR.js} +1 -1
  19. package/dist/{chunk-HNXMWHLU.js → chunk-R7FH7SHX.js} +1 -1
  20. package/dist/{chunk-NS2OMIU7.js → chunk-RQK7OFJO.js} +1 -1
  21. package/dist/{chunk-TWKQFIOW.js → chunk-YMMCRQ2H.js} +84 -14
  22. package/dist/{chunk-JFH34P3M.js → chunk-ZJRGORJ3.js} +359 -169
  23. package/dist/{chunk-HULU2UVM.js → chunk-ZO2ZAPG3.js} +1 -1
  24. package/dist/{content-retention-BcHNpP2F.d.ts → content-retention-BASsjgKw.d.ts} +1 -1
  25. package/dist/{credit-ledger-D3UUR2GW.js → credit-ledger-EZDZYLSK.js} +3 -3
  26. package/dist/{credit-menu-C-kmf61K.d.ts → credit-menu-B239nIY5.d.ts} +23 -9
  27. package/dist/{fx-hFlF4Cn8.d.ts → fx-BmvWGISH.d.ts} +1 -1
  28. package/dist/index.d.ts +6 -5
  29. package/dist/index.js +12 -5
  30. package/dist/internal/caller.d.ts +11 -7
  31. package/dist/internal/caller.js +7 -7
  32. package/dist/internal/server.d.ts +7 -7
  33. package/dist/internal/server.js +21 -21
  34. package/dist/{job-store-DVCATwhU.d.ts → job-store-CA7u1nHV.d.ts} +22 -4
  35. package/dist/{lightning-backend-DXNVlVs9.d.ts → lightning-backend-BOcopzXk.d.ts} +1 -1
  36. package/dist/{memory-credit-ledger-SCXQO4HZ.js → memory-credit-ledger-REZGHPJP.js} +4 -4
  37. package/dist/{mpp-setup-JU3MT7QX.js → mpp-setup-RG5JDMIO.js} +3 -3
  38. package/dist/{payout-reporter-NXWQVKXW.js → payout-reporter-DDQ7FGRC.js} +2 -2
  39. package/dist/{postgres-consumed-credential-store-WXJLM6J7.js → postgres-consumed-credential-store-I4WTNVQZ.js} +1 -1
  40. package/dist/{postgres-job-store-ZVMAN6L2.js → postgres-job-store-IXTNWT4K.js} +3 -3
  41. package/dist/{postgres-replay-store-QJ4FG6AR.js → postgres-replay-store-3F5FCUXN.js} +2 -2
  42. package/dist/{pricing-KCLENT2C.js → pricing-HLYEGPXD.js} +3 -3
  43. package/dist/{revenue-reporter-HD4GMXGD.js → revenue-reporter-RIEHBKRA.js} +2 -2
  44. package/dist/server/index.d.ts +15 -12
  45. package/dist/server/index.js +28 -28
  46. package/dist/{step-cache-CoORQQ_Y.d.ts → step-cache-BCRHimiY.d.ts} +48 -9
  47. package/dist/{tempo-charge-store-7LTUAQHQ.js → tempo-charge-store-B5SYDCBK.js} +2 -2
  48. package/dist/{tempo-session-store-LYAIDR2B.js → tempo-session-store-FHW7N22I.js} +3 -3
  49. package/dist/testing/index.d.ts +7 -4
  50. package/dist/testing/index.js +7 -7
  51. package/dist/{usd-CeXGiyTC.d.ts → usd-B8_WMfHT.d.ts} +1 -1
  52. package/dist/{x402-RUJ4LUKF.js → x402-AY6P5KV4.js} +3 -3
  53. package/package.json +1 -1
@@ -5,7 +5,7 @@ import {
5
5
  } from "./chunk-XRLKGCAW.js";
6
6
  import {
7
7
  x402EarnedCeiling
8
- } from "./chunk-ITRSITFP.js";
8
+ } from "./chunk-2I6NXIPG.js";
9
9
  import {
10
10
  createX402FacilitatorAuthHeaders,
11
11
  createX402FacilitatorClient,
@@ -14,13 +14,13 @@ import {
14
14
  verifyWithFacilitator,
15
15
  withPaymentDiagnostics,
16
16
  withPaymentStage
17
- } from "./chunk-E7ZBCT5U.js";
17
+ } from "./chunk-I2H2XYQV.js";
18
18
  import {
19
19
  MemoryCreditLedger
20
- } from "./chunk-TKJBUFYY.js";
20
+ } from "./chunk-33J3NQDZ.js";
21
21
  import {
22
22
  PostgresJobStore
23
- } from "./chunk-TWKQFIOW.js";
23
+ } from "./chunk-YMMCRQ2H.js";
24
24
  import {
25
25
  SIGNED_ENVELOPE_FIELDS,
26
26
  SignedRequestError,
@@ -28,7 +28,7 @@ import {
28
28
  humanizeZodIssues,
29
29
  signedRequestInput,
30
30
  zodIssuesOf
31
- } from "./chunk-HULU2UVM.js";
31
+ } from "./chunk-ZO2ZAPG3.js";
32
32
  import {
33
33
  MemoryJobStore,
34
34
  UNCAPPABLE_ASK_TOTAL,
@@ -37,21 +37,26 @@ import {
37
37
  isJobRetentionStore,
38
38
  isReceiptIssuingStore,
39
39
  isStreamableJobStore
40
- } from "./chunk-KJZVTWMX.js";
40
+ } from "./chunk-PKLNMMKF.js";
41
41
  import {
42
42
  ContentHoldError,
43
43
  JobCancelledError,
44
44
  abortJob,
45
45
  baselineContentExpiry,
46
+ callerDeadline,
46
47
  contentMetadata,
47
48
  fromJobRecord,
49
+ hasCallerDeadline,
50
+ holdJobHistory,
48
51
  isContentHoldStore,
49
52
  isTerminal,
50
53
  isYieldMessage,
51
54
  localMessage,
52
55
  providerMessage,
53
- toJobRecord
54
- } from "./chunk-AFV77S4C.js";
56
+ releaseJobHistory,
57
+ toJobRecord,
58
+ visibleJobMessages
59
+ } from "./chunk-KVKJ7R5V.js";
55
60
  import {
56
61
  ADMIN_AUTH_CLOCK_SKEW_SECONDS,
57
62
  ADMIN_AUTH_NONCE_TTL_SECONDS,
@@ -73,7 +78,7 @@ import {
73
78
  receiptPubkeyFromSecret,
74
79
  toLockPubkey,
75
80
  verifyChallenge
76
- } from "./chunk-5JSPTVIK.js";
81
+ } from "./chunk-LZFZGY7L.js";
77
82
  import {
78
83
  X402_BATCH_SETTLEMENT_SCHEME,
79
84
  X402_DEFAULT_FACILITATOR,
@@ -100,7 +105,7 @@ import {
100
105
  } from "./chunk-SXL44PL5.js";
101
106
  import {
102
107
  createConsoleLogger
103
- } from "./chunk-7KQLPVV7.js";
108
+ } from "./chunk-7NZRJPEP.js";
104
109
  import {
105
110
  StepCache
106
111
  } from "./chunk-BQJIFRAZ.js";
@@ -117,7 +122,7 @@ import {
117
122
  roundUsd,
118
123
  satsToFiat,
119
124
  validateCurrency
120
- } from "./chunk-LDKV4UHI.js";
125
+ } from "./chunk-CVT5LTAK.js";
121
126
  import {
122
127
  CreditLedger,
123
128
  CreditLedgerError,
@@ -133,7 +138,7 @@ import {
133
138
  isNonChannelBitcoinRail,
134
139
  netOwedSats,
135
140
  x402SettlementPending
136
- } from "./chunk-A2YESDQM.js";
141
+ } from "./chunk-LM6E4YHQ.js";
137
142
  import {
138
143
  parseStoreValue
139
144
  } from "./chunk-YG7G4DPZ.js";
@@ -151,7 +156,7 @@ import {
151
156
  } from "./chunk-5UC7TOQC.js";
152
157
  import {
153
158
  withPostgresRecoveryContext
154
- } from "./chunk-EP7OVWI5.js";
159
+ } from "./chunk-FOXGN4KQ.js";
155
160
  import {
156
161
  DvmError,
157
162
  x402NetworkByCaip2
@@ -169,17 +174,17 @@ import {
169
174
  installHostedTempoCloseGuard,
170
175
  isTempoInsufficientFundsError,
171
176
  resolveTempoNetwork
172
- } from "./chunk-JLT2YIBS.js";
177
+ } from "./chunk-GB2N4NG5.js";
173
178
  import {
174
179
  loggers,
175
180
  pinDimension,
176
181
  withSpan,
177
182
  withTraceContext
178
- } from "./chunk-HNXMWHLU.js";
183
+ } from "./chunk-R7FH7SHX.js";
179
184
  import {
180
185
  createDiagnosticCapabilityAuthority,
181
186
  diagnosticConsole
182
- } from "./chunk-Z4FOPROB.js";
187
+ } from "./chunk-BAHZK334.js";
183
188
 
184
189
  // src/sdk/server/lock-pubkey-store.ts
185
190
  async function initLockPubkeyTable(db) {
@@ -3181,6 +3186,26 @@ function isRecord(value) {
3181
3186
  return !!value && typeof value === "object" && !Array.isArray(value);
3182
3187
  }
3183
3188
 
3189
+ // src/sdk/server/upfront-payment.ts
3190
+ function upfrontPaymentFromDraw(draw) {
3191
+ const value = draw.railValue;
3192
+ const rail = value.rail;
3193
+ return {
3194
+ amount_msats: value.drawMsats,
3195
+ ...value.creditCurrency !== null && {
3196
+ amount_micro: draw.amountMicro,
3197
+ currency: value.creditCurrency
3198
+ },
3199
+ ...rail === "cashu" || rail === "x402" || rail === "tempo" || rail === "lightning" ? { rail } : {},
3200
+ ...value.drawNative !== null && value.nativeAsset !== null && {
3201
+ native_amount: value.drawNative,
3202
+ native_asset: value.nativeAsset
3203
+ },
3204
+ credit_id: draw.creditId,
3205
+ draw_id: draw.drawId
3206
+ };
3207
+ }
3208
+
3184
3209
  // src/sdk/server/payment-diagnostics.ts
3185
3210
  import { Errors } from "mppx";
3186
3211
  function paymentFailureFields(error) {
@@ -3560,7 +3585,18 @@ async function issueUpfrontChallenges(mpp, requiredMsats, resourcePath, btcUsdRa
3560
3585
  );
3561
3586
  }
3562
3587
  async function verifyUpfrontPayment(opts) {
3563
- return withPaymentDiagnostics(() => verifyUpfrontPaymentInScope(opts));
3588
+ const result = await withPaymentDiagnostics(() => verifyUpfrontPaymentInScope(opts));
3589
+ if (!result.error && !result.replayedDraw && !result.upfrontPayment && !opts.fundOnly) {
3590
+ result.upfrontPayment = {
3591
+ amount_msats: result.paidMsats,
3592
+ ...result.paymentRail && { rail: result.paymentRail },
3593
+ ...result.nativeAmount !== void 0 && result.nativeAsset !== void 0 && {
3594
+ native_amount: result.nativeAmount,
3595
+ native_asset: result.nativeAsset
3596
+ }
3597
+ };
3598
+ }
3599
+ return result;
3564
3600
  }
3565
3601
  async function verifyUpfrontPaymentInScope(opts) {
3566
3602
  const {
@@ -3892,7 +3928,7 @@ async function verifyUpfrontPaymentInScope(opts) {
3892
3928
  x402Requirements
3893
3929
  });
3894
3930
  }
3895
- const { verifyX402Payment } = await import("./x402-RUJ4LUKF.js");
3931
+ const { verifyX402Payment } = await import("./x402-AY6P5KV4.js");
3896
3932
  const receipt = await withPaymentStage("verifier_broadcast", () => verifyX402Payment(
3897
3933
  x402Payment,
3898
3934
  x402Config,
@@ -4328,7 +4364,8 @@ async function verifyUpfrontPaymentInScope(opts) {
4328
4364
  creditId: draw.creditId,
4329
4365
  drawId: draw.drawId,
4330
4366
  drawAmountMicro: draw.amountMicro,
4331
- creditCurrency: ledgerCtx.priceFiat.currency
4367
+ creditCurrency: ledgerCtx.priceFiat.currency,
4368
+ upfrontPayment: upfrontPaymentFromDraw(draw)
4332
4369
  };
4333
4370
  } catch (err) {
4334
4371
  if (err instanceof CreditLedgerError) return creditLedgerErrorResult(err, x402Requirements);
@@ -4916,7 +4953,7 @@ async function verifyIncomingPaymentInScope(body, opts, snapshot) {
4916
4953
  snapshot
4917
4954
  });
4918
4955
  }
4919
- const { verifyX402Payment } = await import("./x402-RUJ4LUKF.js");
4956
+ const { verifyX402Payment } = await import("./x402-AY6P5KV4.js");
4920
4957
  const receipt = await withPaymentStage("verifier_broadcast", () => verifyX402Payment(
4921
4958
  body.content.x402_payment,
4922
4959
  opts.x402Config,
@@ -5155,7 +5192,9 @@ async function verifyIncomingPaymentInScope(body, opts, snapshot) {
5155
5192
  };
5156
5193
  }
5157
5194
  }
5158
- return {};
5195
+ return { error: { status: 402, body: paymentErrorBody("payment_invalid", {
5196
+ message: "Payment method is not configured"
5197
+ }) } };
5159
5198
  }
5160
5199
  function midJobFundedMsats(snapshot, railMsats) {
5161
5200
  const pending = snapshot.pendingPaymentMsats ?? 0;
@@ -5365,7 +5404,7 @@ async function processIncomingPaymentInScope(job, body, opts) {
5365
5404
  try {
5366
5405
  const requiredMsats = job.pendingPaymentMsats ?? 0;
5367
5406
  const requiredUsdcMicro = job.pendingX402AmountUsdcMicro !== void 0 ? BigInt(job.pendingX402AmountUsdcMicro) : BigInt(msatsToUsdc(requiredMsats, rate));
5368
- const { verifyX402Payment } = await import("./x402-RUJ4LUKF.js");
5407
+ const { verifyX402Payment } = await import("./x402-AY6P5KV4.js");
5369
5408
  const receipt = await withPaymentStage("verifier_broadcast", () => verifyX402Payment(
5370
5409
  body.content.x402_payment,
5371
5410
  opts.x402Config,
@@ -5964,7 +6003,8 @@ function withDrawBasis(info, ctx, committed) {
5964
6003
  const withDraw = {
5965
6004
  ...base2,
5966
6005
  drawId,
5967
- drawAmountMicro: draw.amountMicro
6006
+ drawAmountMicro: draw.amountMicro,
6007
+ ...!ctx.midJob && !ctx.fundOnly && { upfrontPayment: upfrontPaymentFromDraw(draw) }
5968
6008
  };
5969
6009
  if (!ctx.drawBasis) return withDraw;
5970
6010
  const { drawMsats, drawNative } = committed.added ?? draw.railValue;
@@ -12514,6 +12554,12 @@ function buildContext(job, opts) {
12514
12554
  log.error("Terminal handling failed", { error: String(err) });
12515
12555
  });
12516
12556
  };
12557
+ let replayPrompts;
12558
+ const captureReplayPrompts = () => {
12559
+ if (replayPrompts === void 0 && job.replayHighSeq !== null) {
12560
+ replayPrompts = job.messages.filter((m) => m.from === "provider" && m.type === "prompt" && m.seq <= job.replayHighSeq);
12561
+ }
12562
+ };
12517
12563
  let creditedToHandlerMsats = 0;
12518
12564
  let creditedToHandlerMicro = 0;
12519
12565
  let poolBasis = false;
@@ -12521,6 +12567,7 @@ function buildContext(job, opts) {
12521
12567
  let declaredCostCurrency;
12522
12568
  let declaredCostRevision = 0;
12523
12569
  let costPoisoned = false;
12570
+ const stepWrites = /* @__PURE__ */ new Map();
12524
12571
  const stepCostOwner = {};
12525
12572
  const declareCost = (declared) => {
12526
12573
  if (terminated("cost")) return;
@@ -12576,29 +12623,41 @@ function buildContext(job, opts) {
12576
12623
  },
12577
12624
  sendMessage: (type, content) => {
12578
12625
  if (terminated("sendMessage")) return;
12626
+ if (type === "payment-request" && typeof content.amount_msats === "number") job.pendingPaymentMsats = content.amount_msats;
12579
12627
  providerMessage(job, type, content);
12628
+ if ((type === "prompt" || type === "payment-request") && opts.onYield) {
12629
+ void opts.onYield().catch((err) => {
12630
+ log.error("Failed to persist event-driven wait", { error: String(err) });
12631
+ });
12632
+ }
12580
12633
  },
12581
12634
  prompt: (id, text, promptOpts) => {
12582
12635
  if (terminated("prompt")) {
12583
12636
  return Promise.reject(new JobCancelledError(`Job already ${job.status}`));
12584
12637
  }
12585
12638
  job.status = "awaiting-input";
12586
- providerMessage(job, "prompt", {
12587
- id,
12588
- text,
12589
- ...promptOpts?.options && { options: promptOpts.options },
12590
- ...promptOpts?.schema && { schema: promptOpts.schema }
12591
- });
12592
- if (job.replayHighSeq !== null) {
12639
+ captureReplayPrompts();
12640
+ const originalIndex = replayPrompts?.findIndex((m) => m.type === "prompt" && m.content.id === id) ?? -1;
12641
+ const original = originalIndex < 0 ? void 0 : replayPrompts.splice(originalIndex, 1)[0];
12642
+ if (original) {
12643
+ if (job.replayProviderSkip > 0) job.replayProviderSkip--;
12593
12644
  const response = job.messages.find(
12594
- (m) => m.type === "response" && m.from === "requester" && m.content.prompt_id === id
12645
+ (m) => m.type === "response" && m.from === "requester" && m.content.prompt_id === id && m.seq > original.seq
12595
12646
  );
12596
12647
  if (response) {
12597
- job.status = "processing";
12648
+ job.status = job.pendingPrompts.size > 0 || job.pendingPayment ? "awaiting-input" : "processing";
12649
+ opts.onCallerResume?.();
12598
12650
  return Promise.resolve(response.content);
12599
12651
  }
12652
+ } else {
12600
12653
  job.replayHighSeq = null;
12601
12654
  job.replayProviderSkip = 0;
12655
+ providerMessage(job, "prompt", {
12656
+ id,
12657
+ text,
12658
+ ...promptOpts?.options && { options: promptOpts.options },
12659
+ ...promptOpts?.schema && { schema: promptOpts.schema }
12660
+ });
12602
12661
  }
12603
12662
  const p = new Promise((resolve, reject) => {
12604
12663
  job.pendingPrompts.set(id, { resolve, reject });
@@ -12611,6 +12670,7 @@ function buildContext(job, opts) {
12611
12670
  return p;
12612
12671
  },
12613
12672
  requestPayment: async (amount, reason, payOpts) => {
12673
+ captureReplayPrompts();
12614
12674
  if (terminated("requestPayment")) {
12615
12675
  throw new JobCancelledError(`Job already ${job.status}`);
12616
12676
  }
@@ -12719,135 +12779,153 @@ function buildContext(job, opts) {
12719
12779
  poolRemainingMicro: pool.amountMicro - creditedToHandlerMicro
12720
12780
  } : { poolRemaining: job.paidMsats - creditedToHandlerMsats }
12721
12781
  });
12782
+ if (job.replayHighSeq !== null) {
12783
+ job.status = job.pendingPrompts.size > 0 ? "awaiting-input" : "processing";
12784
+ opts.onCallerResume?.();
12785
+ }
12722
12786
  advanceReplayPaymentIndex(job);
12723
12787
  return { amount_msats: amountMsats };
12724
12788
  }
12725
- const pendingFiat = await pinAsk();
12789
+ const replayedPaymentRequest = job.replayHighSeq !== null && job.replayPaymentIndex >= job.messages.filter((m) => m.from === "requester" && m.type === "payment").length ? job.messages.findLast((m) => m.from === "provider" && m.type === "payment-request" && m.seq <= job.replayHighSeq && m.seq > (job.messages.findLast((message) => message.from === "requester" && message.type === "payment")?.seq ?? -1)) : void 0;
12790
+ const originalAsk = replayedPaymentRequest?.type === "payment-request" ? replayedPaymentRequest.content : void 0;
12791
+ if (originalAsk) amountMsats = originalAsk.amount_msats;
12792
+ const pendingFiat = originalAsk ? originalAsk.credit ? { amountMicro: originalAsk.credit.amount_micro, currency: originalAsk.credit.currency } : void 0 : await pinAsk();
12726
12793
  job.pendingPaymentMsats = amountMsats;
12727
12794
  job.pendingPaymentFiatMicro = pendingFiat?.amountMicro;
12728
12795
  job.pendingPaymentFiatCurrency = pendingFiat?.currency;
12729
- const paymentRequestContent = {
12730
- amount_msats: amountMsats,
12731
- reason,
12732
- ...pendingFiat && { credit: { amount_micro: pendingFiat.amountMicro, currency: pendingFiat.currency } },
12733
- ...fiatAmount && { fiat_amount: fiatAmount },
12734
- ...payOpts?.mints && { mints: payOpts.mints },
12735
- ...payOpts?.minLocktimeSeconds && { min_locktime_seconds: payOpts.minLocktimeSeconds },
12736
- ...!payOpts?.mints && opts.mints && { mints: opts.mints }
12737
- };
12738
- let paymentRate;
12739
- let x402Nonce;
12740
- let x402AmountUsdcMicro;
12741
- const hasExplicitX402 = Boolean(payOpts?.x402);
12742
- if ((opts.pricingCurrency ?? "usd") === "usd") {
12743
- if (payOpts?.x402) {
12744
- paymentRequestContent.x402 = payOpts.x402;
12745
- } else if (opts.paymentMethods?.includes("x402") && opts.x402 && (opts.x402FacilitatorHealth?.support().available ?? true)) {
12746
- paymentRequestContent.x402 = {
12747
- pay_to: opts.x402.payTo,
12748
- network: opts.x402.network,
12749
- asset: opts.x402.asset ?? "USDC",
12750
- ...opts.x402.facilitator && { facilitator: opts.x402.facilitator }
12751
- };
12752
- }
12753
- }
12754
- if (paymentRequestContent.x402) {
12755
- paymentRate = (await resolveServerBtcUsdRate(opts.fxFetcher)).rate;
12756
- x402Nonce = `0x${randomBytes3(32).toString("hex")}`;
12757
- x402AmountUsdcMicro = msatsToUsdc(amountMsats, paymentRate).toString();
12758
- const x402Request = paymentRequestContent.x402;
12759
- const challengeConfig = {
12760
- payTo: x402Request.pay_to,
12761
- network: x402Request.network,
12762
- ...x402Request.asset ? { asset: x402Request.asset } : {},
12763
- ...x402Request.facilitator ? { facilitator: x402Request.facilitator } : {}
12796
+ let paymentRequestContent;
12797
+ if (originalAsk) {
12798
+ paymentRequestContent = { ...originalAsk };
12799
+ job.pendingX402Nonce = originalAsk.x402?.nonce;
12800
+ job.pendingX402AmountUsdcMicro = originalAsk.x402?.required_usdc_micro;
12801
+ job.pendingMppChallengeIds = originalAsk.tempo?.challenges.map((challenge) => challenge.id);
12802
+ } else {
12803
+ paymentRequestContent = {
12804
+ amount_msats: amountMsats,
12805
+ reason,
12806
+ ...pendingFiat && { credit: { amount_micro: pendingFiat.amountMicro, currency: pendingFiat.currency } },
12807
+ ...fiatAmount && { fiat_amount: fiatAmount },
12808
+ ...payOpts?.mints && { mints: payOpts.mints },
12809
+ ...payOpts?.minLocktimeSeconds && { min_locktime_seconds: payOpts.minLocktimeSeconds },
12810
+ ...!payOpts?.mints && opts.mints && { mints: opts.mints }
12764
12811
  };
12765
- Object.assign(x402Request, {
12766
- nonce: x402Nonce,
12767
- required_usdc_micro: x402AmountUsdcMicro,
12768
- ...hasExplicitX402 || (opts.x402FacilitatorHealth?.support().exact.v2 ?? true) ? {
12769
- payment_required: buildPaymentRequiredV2({
12770
- x402Config: challengeConfig,
12771
- requiredUsdcMicro: BigInt(x402AmountUsdcMicro),
12772
- resource: `/v1/job/${job.id}/messages`,
12773
- description: reason
12774
- })
12775
- } : {}
12776
- });
12777
- }
12778
- job.pendingX402Nonce = x402Nonce;
12779
- job.pendingX402AmountUsdcMicro = x402AmountUsdcMicro;
12780
- let mppChallengeIds;
12781
- if (payOpts?.mpp) {
12782
- const challenges = payOpts.mpp.challenges.filter((c) => c.intent !== "session");
12783
- if (challenges.length > 0) {
12784
- paymentRequestContent.tempo = { challenges };
12785
- mppChallengeIds = challenges.map((c) => c.id);
12812
+ let paymentRate;
12813
+ let x402Nonce;
12814
+ let x402AmountUsdcMicro;
12815
+ const hasExplicitX402 = Boolean(payOpts?.x402);
12816
+ if ((opts.pricingCurrency ?? "usd") === "usd") {
12817
+ if (payOpts?.x402) {
12818
+ paymentRequestContent.x402 = payOpts.x402;
12819
+ } else if (opts.paymentMethods?.includes("x402") && opts.x402 && (opts.x402FacilitatorHealth?.support().available ?? true)) {
12820
+ paymentRequestContent.x402 = {
12821
+ pay_to: opts.x402.payTo,
12822
+ network: opts.x402.network,
12823
+ asset: opts.x402.asset ?? "USDC",
12824
+ ...opts.x402.facilitator && { facilitator: opts.x402.facilitator }
12825
+ };
12826
+ }
12786
12827
  }
12787
- } else if (opts.paymentMethods?.includes("tempo") && opts.mpp) {
12788
- const rate = paymentRate ?? (await resolveServerBtcUsdRate(opts.fxFetcher)).rate;
12789
- const challenges = [];
12790
- for (const m of opts.mpp.methods) {
12791
- const meta = m;
12792
- if (meta.intent === "session") continue;
12793
- const amount2 = pendingFiat?.currency === "usd" ? fiatMicroToMethodAmount(pendingFiat.amountMicro, meta.name) : msatsToMethodAmount(amountMsats, meta.name, rate);
12794
- const ch = await opts.mpp.issueChallenge(meta.name, meta.intent, {
12795
- amount: amount2,
12796
- description: reason
12828
+ if (paymentRequestContent.x402) {
12829
+ paymentRate = (await resolveServerBtcUsdRate(opts.fxFetcher)).rate;
12830
+ x402Nonce = `0x${randomBytes3(32).toString("hex")}`;
12831
+ x402AmountUsdcMicro = msatsToUsdc(amountMsats, paymentRate).toString();
12832
+ const x402Request = paymentRequestContent.x402;
12833
+ const challengeConfig = {
12834
+ payTo: x402Request.pay_to,
12835
+ network: x402Request.network,
12836
+ ...x402Request.asset ? { asset: x402Request.asset } : {},
12837
+ ...x402Request.facilitator ? { facilitator: x402Request.facilitator } : {}
12838
+ };
12839
+ Object.assign(x402Request, {
12840
+ nonce: x402Nonce,
12841
+ required_usdc_micro: x402AmountUsdcMicro,
12842
+ ...hasExplicitX402 || (opts.x402FacilitatorHealth?.support().exact.v2 ?? true) ? {
12843
+ payment_required: buildPaymentRequiredV2({
12844
+ x402Config: challengeConfig,
12845
+ requiredUsdcMicro: BigInt(x402AmountUsdcMicro),
12846
+ resource: `/v1/job/${job.id}/messages`,
12847
+ description: reason
12848
+ })
12849
+ } : {}
12797
12850
  });
12798
- challenges.push(ch);
12799
12851
  }
12800
- if (challenges.length > 0) {
12801
- paymentRequestContent.tempo = { challenges };
12802
- mppChallengeIds = challenges.map((c) => c.id);
12852
+ job.pendingX402Nonce = x402Nonce;
12853
+ job.pendingX402AmountUsdcMicro = x402AmountUsdcMicro;
12854
+ let mppChallengeIds;
12855
+ if (payOpts?.mpp) {
12856
+ const challenges = payOpts.mpp.challenges.filter((c) => c.intent !== "session");
12857
+ if (challenges.length > 0) {
12858
+ paymentRequestContent.tempo = { challenges };
12859
+ mppChallengeIds = challenges.map((c) => c.id);
12860
+ }
12861
+ } else if (opts.paymentMethods?.includes("tempo") && opts.mpp) {
12862
+ const rate = paymentRate ?? (await resolveServerBtcUsdRate(opts.fxFetcher)).rate;
12863
+ const challenges = [];
12864
+ for (const m of opts.mpp.methods) {
12865
+ const meta = m;
12866
+ if (meta.intent === "session") continue;
12867
+ const amount2 = pendingFiat?.currency === "usd" ? fiatMicroToMethodAmount(pendingFiat.amountMicro, meta.name) : msatsToMethodAmount(amountMsats, meta.name, rate);
12868
+ const ch = await opts.mpp.issueChallenge(meta.name, meta.intent, {
12869
+ amount: amount2,
12870
+ description: reason
12871
+ });
12872
+ challenges.push(ch);
12873
+ }
12874
+ if (challenges.length > 0) {
12875
+ paymentRequestContent.tempo = { challenges };
12876
+ mppChallengeIds = challenges.map((c) => c.id);
12877
+ }
12803
12878
  }
12879
+ job.pendingMppChallengeIds = mppChallengeIds;
12804
12880
  }
12805
- job.pendingMppChallengeIds = mppChallengeIds;
12806
12881
  if (job.replayHighSeq !== null) {
12807
- const payments = job.messages.filter((m) => m.type === "payment" && m.from === "requester");
12882
+ const payments = job.messages.filter((m) => m.type === "payment" && m.from === "requester" && m.seq <= job.replayHighSeq);
12808
12883
  if (job.replayPaymentIndex < payments.length) {
12809
12884
  const payment = payments[job.replayPaymentIndex];
12810
12885
  job.replayPaymentIndex++;
12811
12886
  const balance = payment.content.credit;
12812
12887
  const accepted = balance ? await opts.acceptedBalancePayment?.(payment.seq, pendingFiat?.amountMicro, pendingFiat?.currency) : job.reactivationPaymentApplied === true;
12813
12888
  if (!accepted) throw new Error("[internal_error] payment replay reached without credit");
12814
- if (balance) {
12815
- job.pendingPaymentMsats = void 0;
12816
- job.pendingPaymentFiatMicro = void 0;
12817
- job.pendingPaymentFiatCurrency = void 0;
12818
- job.pendingMppChallengeIds = void 0;
12819
- job.pendingX402Nonce = void 0;
12820
- job.pendingX402AmountUsdcMicro = void 0;
12821
- }
12822
- job.status = "processing";
12889
+ job.pendingPaymentMsats = void 0;
12890
+ job.pendingPaymentFiatMicro = void 0;
12891
+ job.pendingPaymentFiatCurrency = void 0;
12892
+ job.pendingMppChallengeIds = void 0;
12893
+ job.pendingX402Nonce = void 0;
12894
+ job.pendingX402AmountUsdcMicro = void 0;
12895
+ job.status = job.pendingPrompts.size > 0 ? "awaiting-input" : "processing";
12896
+ opts.onCallerResume?.();
12823
12897
  creditedToHandlerMsats += amountMsats;
12824
12898
  creditedToHandlerMicro += pendingFiat?.amountMicro ?? 0;
12825
12899
  return payment.content;
12826
12900
  }
12827
- job.replayHighSeq = null;
12828
- job.replayProviderSkip = 0;
12901
+ if (!originalAsk) {
12902
+ job.replayHighSeq = null;
12903
+ job.replayProviderSkip = 0;
12904
+ }
12829
12905
  }
12830
- const askedTotal = accumulateAskedTopUp(
12831
- {
12832
- micro: job.askedTopUpMicro,
12833
- currency: job.askedTopUpCurrency,
12834
- reason: job.topUpCapUnenforcedReason
12835
- },
12836
- { micro: pendingFiat?.amountMicro, currency: pendingFiat?.currency }
12837
- );
12838
- job.askedTopUpMicro = askedTotal.micro;
12839
- job.askedTopUpCurrency = askedTotal.currency;
12840
- if (askedTotal.reason && !job.topUpCapUnenforcedReason) {
12841
- job.topUpCapUnenforcedReason = askedTotal.reason;
12842
- loggers.isolate.warn("dvm.topup_cap_unenforceable", {
12843
- job_id: job.id,
12844
- reason: askedTotal.reason,
12845
- ask_currency: askCurrency,
12846
- ask_msats: amountMsats
12847
- });
12906
+ if (!originalAsk) {
12907
+ const askedTotal = accumulateAskedTopUp(
12908
+ {
12909
+ micro: job.askedTopUpMicro,
12910
+ currency: job.askedTopUpCurrency,
12911
+ reason: job.topUpCapUnenforcedReason
12912
+ },
12913
+ { micro: pendingFiat?.amountMicro, currency: pendingFiat?.currency }
12914
+ );
12915
+ job.askedTopUpMicro = askedTotal.micro;
12916
+ job.askedTopUpCurrency = askedTotal.currency;
12917
+ if (askedTotal.reason && !job.topUpCapUnenforcedReason) {
12918
+ job.topUpCapUnenforcedReason = askedTotal.reason;
12919
+ loggers.isolate.warn("dvm.topup_cap_unenforceable", {
12920
+ job_id: job.id,
12921
+ reason: askedTotal.reason,
12922
+ ask_currency: askCurrency,
12923
+ ask_msats: amountMsats
12924
+ });
12925
+ }
12848
12926
  }
12849
12927
  job.status = "awaiting-input";
12850
- providerMessage(job, "payment-request", paymentRequestContent);
12928
+ if (!originalAsk) providerMessage(job, "payment-request", paymentRequestContent);
12851
12929
  const paidMsatsAtYield = job.paidMsats;
12852
12930
  const p = new Promise((resolve, reject) => {
12853
12931
  let resolved = false;
@@ -12865,6 +12943,9 @@ function buildContext(job, opts) {
12865
12943
  if (resolved) return;
12866
12944
  job.pendingPayment = null;
12867
12945
  }
12946
+ job.replayHighSeq = null;
12947
+ job.replayProviderSkip = 0;
12948
+ opts.onCallerResume?.();
12868
12949
  resolved = true;
12869
12950
  cleanup();
12870
12951
  creditedToHandlerMsats += amountMsats;
@@ -12970,6 +13051,7 @@ function buildContext(job, opts) {
12970
13051
  cost: declareCost,
12971
13052
  step: async (id, fn) => {
12972
13053
  if (job.stepCache.has(id)) {
13054
+ await stepWrites.get(id);
12973
13055
  const record = job.stepCache.getRecord(id);
12974
13056
  for (const declared of record.costs ?? []) declareCost(declared);
12975
13057
  return record.value;
@@ -12982,6 +13064,15 @@ function buildContext(job, opts) {
12982
13064
  fn
12983
13065
  );
12984
13066
  job.stepCache.set(id, result, costs);
13067
+ if (opts.onStep) {
13068
+ const persistence = opts.onStep(job.stepCache.getRecord(id));
13069
+ stepWrites.set(id, persistence);
13070
+ try {
13071
+ await persistence;
13072
+ } finally {
13073
+ stepWrites.delete(id);
13074
+ }
13075
+ }
12985
13076
  return result;
12986
13077
  },
12987
13078
  state: job.state,
@@ -13008,7 +13099,7 @@ function warnPinLost(jobId, currency, amountMsats, reason, detail) {
13008
13099
  }
13009
13100
  function advanceReplayPaymentIndex(job) {
13010
13101
  if (job.replayHighSeq === null) return;
13011
- const payments = job.messages.filter((m) => m.type === "payment" && m.from === "requester");
13102
+ const payments = job.messages.filter((m) => m.type === "payment" && m.from === "requester" && m.seq <= job.replayHighSeq);
13012
13103
  if (job.replayPaymentIndex < payments.length) {
13013
13104
  job.replayPaymentIndex++;
13014
13105
  }
@@ -13421,6 +13512,7 @@ var JobManager = class {
13421
13512
  // Bind live resolvers and recovery scaffolds to the store-allocated ask identity.
13422
13513
  paymentAskSeqs = /* @__PURE__ */ new WeakMap();
13423
13514
  reactivationAskSeqs = /* @__PURE__ */ new WeakMap();
13515
+ pendingPaymentMessages = /* @__PURE__ */ new WeakMap();
13424
13516
  // Every local input resume holds this queue through its durable wait update.
13425
13517
  // A prompt response and a payment NOTIFY must not mistake each other for a
13426
13518
  // different machine's successful claim.
@@ -13746,6 +13838,7 @@ var JobManager = class {
13746
13838
  status: "processing",
13747
13839
  messages: [],
13748
13840
  seq: 0,
13841
+ upfrontPayment: payment.upfrontPayment ? structuredClone(payment.upfrontPayment) : void 0,
13749
13842
  paidMsats: payment.paidMsats,
13750
13843
  paymentMint: payment.paymentMint,
13751
13844
  paymentRail: payment.paymentRail,
@@ -13929,8 +14022,10 @@ var JobManager = class {
13929
14022
  job.pendingPrompts.delete(rc.prompt_id);
13930
14023
  job.status = job.pendingPrompts.size > 0 || job.pendingPayment ? "awaiting-input" : "processing";
13931
14024
  if (job.status === "awaiting-input") await this.persistJob(job);
14025
+ this.startIdleTimer(job);
13932
14026
  pending.resolve(rc);
13933
14027
  } else if (cap?.onResponse && ctx) {
14028
+ this.startIdleTimer(job);
13934
14029
  await cap.onResponse(ctx, rc);
13935
14030
  }
13936
14031
  } else if (type === "payment") {
@@ -13950,6 +14045,7 @@ var JobManager = class {
13950
14045
  await pp.resolve(pc);
13951
14046
  });
13952
14047
  } else if (cap?.onPayment && ctx) {
14048
+ this.startIdleTimer(job);
13953
14049
  await cap.onPayment(ctx, pc);
13954
14050
  }
13955
14051
  } else if (type === "approval" && cap?.onApproval && ctx) {
@@ -13994,7 +14090,7 @@ var JobManager = class {
13994
14090
  if (await this.adoptDurableTerminal(job)) return;
13995
14091
  const record = toJobRecord(job);
13996
14092
  if (isTerminal(job.status) && isStreamableJobStore(this.jobStore)) {
13997
- const durable = await this.jobStore.getMessages(job.id, 0);
14093
+ const durable = await this.jobStore.getMessages(job.id, 0, { includeHeld: true });
13998
14094
  if (durable.length >= record.messages.length) {
13999
14095
  record.messages = durable;
14000
14096
  const highSeq = durable.length ? durable[durable.length - 1].seq : record.seq;
@@ -14298,9 +14394,10 @@ var JobManager = class {
14298
14394
  job.messageAppenderTail = next;
14299
14395
  };
14300
14396
  }
14301
- startIdleTimer(job) {
14397
+ startIdleTimer(job, deadline = callerDeadline(job, this.descriptor.idleTimeout * 1e3)) {
14302
14398
  this.clearIdleTimer(job.id);
14303
- const timeoutMs = this.descriptor.idleTimeout * 1e3;
14399
+ if (deadline === void 0) return;
14400
+ const timeoutMs = Math.min(2147483647, Math.max(0, deadline - Date.now()));
14304
14401
  const timer = setTimeout(() => {
14305
14402
  void this.cancelJobIdle(job).catch(() => void 0);
14306
14403
  }, timeoutMs);
@@ -14316,7 +14413,7 @@ var JobManager = class {
14316
14413
  /** Cancel a job due to idle timeout. Exposed for the reactivation path's idle-expiry guard. */
14317
14414
  async cancelJobIdle(job) {
14318
14415
  if (isTerminal(job.status)) return;
14319
- const current = await this.jobStore.get(job.id).catch(() => void 0);
14416
+ const current = await this.jobStore.get(job.id);
14320
14417
  if (current && isTerminal(current.status)) {
14321
14418
  const reason = `Job reached terminal status "${current.status}" elsewhere`;
14322
14419
  for (const [, p] of job.pendingPrompts) p.reject(new Error(reason));
@@ -14328,11 +14425,32 @@ var JobManager = class {
14328
14425
  this.scheduleCleanup(job);
14329
14426
  return;
14330
14427
  }
14428
+ const waiting = current ?? job;
14429
+ if (current && isStreamableJobStore(this.jobStore)) {
14430
+ const messages = await this.jobStore.getMessages(job.id, 0);
14431
+ if (messages.length) waiting.messages = messages;
14432
+ }
14433
+ const deadline = callerDeadline(waiting, this.descriptor.idleTimeout * 1e3);
14434
+ if (deadline === void 0) {
14435
+ this.clearIdleTimer(job.id);
14436
+ return;
14437
+ }
14438
+ if (Date.now() < deadline) {
14439
+ this.startIdleTimer(job, deadline);
14440
+ return;
14441
+ }
14442
+ if (current && isStreamableJobStore(this.jobStore)) {
14443
+ const claimed = await this.jobStore.cancelStaleJob(job.id, current.lastActivityAt, "Job timed out (idle)", "cancelled");
14444
+ if (claimed) await this.finalizeStoredTerminal(job.id);
14445
+ await this.adoptDurableTerminal(job);
14446
+ if (!claimed && !isTerminal(job.status)) this.startIdleTimer(job, Date.now() + 1);
14447
+ return;
14448
+ }
14331
14449
  for (const [, p] of job.pendingPrompts) p.reject(new Error("Job timed out (idle)"));
14332
14450
  if (job.pendingPayment) job.pendingPayment.reject(new Error("Job timed out (idle)"));
14333
14451
  job.pendingPrompts.clear();
14334
14452
  job.pendingPayment = null;
14335
- job.endedBy = (current?.status ?? job.status) === "awaiting-input" ? "caller" : "provider";
14453
+ job.endedBy = "caller";
14336
14454
  job.status = "cancelled";
14337
14455
  abortJob(job, "Job timed out (idle)");
14338
14456
  providerMessage(job, "cancel", { reason: "Job timed out (idle)" });
@@ -14345,8 +14463,11 @@ var JobManager = class {
14345
14463
  * • `awaiting-input` past `staleJobTimeoutMs` → `cancelled`
14346
14464
  * (`stale_no_terminal_status`) — the caller never paid / responded.
14347
14465
  * • `processing`/`working` past `processingWatchdogMs` → `failed`
14348
- * (`worker_died_mid_job`) — the worker died mid-job. The heartbeat keeps
14349
- * live workers fresh, so a stale row here means a dead process.
14466
+ * (`worker_died_mid_job`) — the worker died mid-job. Before claiming a
14467
+ * processing row, skip and heartbeat it if this manager still holds a live
14468
+ * `processing`/`working` job. After a process pause, the sweep may run before
14469
+ * the heartbeat timer; stale activity alone does not prove a local worker
14470
+ * died. Superseded handlers leave `_activeJobs` and receive no protection.
14350
14471
  *
14351
14472
  * Fires on a timer (wired in the constructor) and also callable on demand
14352
14473
  * (tests, ops). The CAS in `cancelStaleJob` makes this safe to run
@@ -14354,6 +14475,8 @@ var JobManager = class {
14354
14475
  * JobManager owns so multi-mount hosts don't steal stuck rows from sibling
14355
14476
  * descriptors — the local activeJobs teardown only makes sense on the
14356
14477
  * JobManager that actually hosted the zombie handler.
14478
+ * Local liveness prevents this manager's claim; the activity CAS continues
14479
+ * to protect against other machines claiming a row refreshed after find.
14357
14480
  *
14358
14481
  * **Both cutoffs float off the wall clock.** Each is one
14359
14482
  * `Date.now()` sample compared against `last_activity_at`, stamped from a
@@ -14418,10 +14541,25 @@ var JobManager = class {
14418
14541
  );
14419
14542
  let swept = 0;
14420
14543
  for (const record of stale) {
14421
- const awaiting = record.status === "awaiting-input";
14544
+ const messages = await this.jobStore.getMessages(record.id, 0);
14545
+ const history = messages.length ? messages : record.messages;
14546
+ const deadline = callerDeadline({ ...record, messages: history });
14547
+ if (deadline !== void 0 && now < deadline) continue;
14548
+ const orphanedInput = record.status === "awaiting-input" && deadline === void 0 && hasCallerDeadline(history);
14549
+ const awaiting = deadline !== void 0 || record.status === "awaiting-input" && !orphanedInput;
14422
14550
  const terminalStatus = awaiting ? "cancelled" : "failed";
14423
14551
  const reason = awaiting ? "stale_no_terminal_status" : "worker_died_mid_job";
14424
- const expectedActivityBefore = awaiting ? awaitingThreshold : processingThreshold;
14552
+ const expectedActivityBefore = awaiting && record.status === "awaiting-input" ? awaitingThreshold : processingThreshold;
14553
+ if (expectedActivityBefore < 0 || record.lastActivityAt > expectedActivityBefore) continue;
14554
+ const local = this._activeJobs.get(record.id);
14555
+ if ((record.status === "processing" || record.status === "working") && local && (local.status === "processing" || local.status === "working")) {
14556
+ await this.jobStore.heartbeatActiveJobs([record.id], Date.now());
14557
+ diagnosticConsole.warn({
14558
+ level: "stale_job_local_live_skipped",
14559
+ ageMs: monoNow - record.lastActivityAt
14560
+ });
14561
+ continue;
14562
+ }
14425
14563
  const claimed = await this.jobStore.cancelStaleJob(
14426
14564
  record.id,
14427
14565
  expectedActivityBefore,
@@ -15041,8 +15179,9 @@ var JobManager = class {
15041
15179
  }
15042
15180
  }
15043
15181
  /**
15044
- * Record an inbound client message durably via Tx B and append
15045
- * it to the in-memory `job.messages` array. Returns the DB-allocated seq
15182
+ * Record an inbound client message durably via Tx B. Payments stay outside
15183
+ * visible history until processPayment accepts them; other messages append
15184
+ * to `job.messages` immediately. Returns the DB-allocated seq
15046
15185
  * (`null` when the store isn't streamable — non-Postgres test fallback).
15047
15186
  *
15048
15187
  * Status starts as `pending-verification`; a follow-up Tx C call
@@ -15061,13 +15200,24 @@ var JobManager = class {
15061
15200
  });
15062
15201
  }
15063
15202
  job.seq++;
15064
- job.messages.push({
15203
+ const message = {
15065
15204
  seq: job.seq,
15066
15205
  from: "requester",
15067
15206
  timestamp,
15068
15207
  type: msgType,
15069
15208
  content: msgContent
15070
- });
15209
+ };
15210
+ if (msgType === "payment") {
15211
+ let pending = this.pendingPaymentMessages.get(job);
15212
+ if (!pending) {
15213
+ pending = /* @__PURE__ */ new Map();
15214
+ this.pendingPaymentMessages.set(job, pending);
15215
+ }
15216
+ pending.set(txSeq ?? msgContent, message);
15217
+ holdJobHistory(job, message.seq);
15218
+ } else {
15219
+ job.messages.push(message);
15220
+ }
15071
15221
  job.lastActivityAt = Date.now();
15072
15222
  return txSeq;
15073
15223
  }
@@ -15176,6 +15326,22 @@ var JobManager = class {
15176
15326
  * 500. See `creditInbound`.
15177
15327
  */
15178
15328
  async processPayment(job, body, inboundSeq, extra) {
15329
+ const pending = this.pendingPaymentMessages.get(job);
15330
+ const key = inboundSeq ?? body.content;
15331
+ const message = pending?.get(key);
15332
+ let accepted;
15333
+ try {
15334
+ const outcome = await this.processPaymentAndCredit(job, body, inboundSeq, extra);
15335
+ if (!outcome.error && message && pending?.get(key) === message) {
15336
+ accepted = message;
15337
+ }
15338
+ return outcome;
15339
+ } finally {
15340
+ pending?.delete(key);
15341
+ if (message) releaseJobHistory(job, message.seq, accepted);
15342
+ }
15343
+ }
15344
+ async processPaymentAndCredit(job, body, inboundSeq, extra) {
15179
15345
  if (body.type !== "payment") return {};
15180
15346
  const devAuto = this.opts.devMode === true && body.content.dev_auto === true && !hasPaymentProof(body.content);
15181
15347
  if (devAuto || devModeSkipsPaymentVerification(this.opts)) {
@@ -15260,7 +15426,7 @@ var JobManager = class {
15260
15426
  };
15261
15427
  }
15262
15428
  const info = verifyResult.verified;
15263
- if (!info) return {};
15429
+ if (!info) return { error: { status: 402, body: { error: "payment_invalid", message: "Payment was not accepted" } } };
15264
15430
  const drawFundingReceipt = await this.drawFundingReceiptFor(info.drawFunding, info.funding?.currency);
15265
15431
  let unappliedCredit;
15266
15432
  const fullyUnapplied = info.unappliedMicro !== void 0 && info.funding !== void 0 && info.unappliedMicro >= info.funding.amountMicro;
@@ -15634,6 +15800,7 @@ var JobManager = class {
15634
15800
  */
15635
15801
  buildContextOpts(job) {
15636
15802
  const jobStore = this.jobStore;
15803
+ job.idleTimeoutMs = this.descriptor.idleTimeout * 1e3;
15637
15804
  return {
15638
15805
  mints: this.opts.mints,
15639
15806
  store: this.opts.store,
@@ -15644,7 +15811,25 @@ var JobManager = class {
15644
15811
  mpp: this.opts.mpp,
15645
15812
  fxFetcher: this.opts.fxFetcher,
15646
15813
  onTerminal: () => this.handleJobTerminal(job),
15647
- onYield: () => this.persistJob(job),
15814
+ onYield: () => {
15815
+ this.startIdleTimer(job);
15816
+ return this.persistJob(job);
15817
+ },
15818
+ onCallerResume: () => this.startIdleTimer(job),
15819
+ onStep: jobStore.saveStep ? async (record) => {
15820
+ for (let attempt = 0; ; attempt++) {
15821
+ try {
15822
+ await jobStore.saveStep(job.id, record);
15823
+ return;
15824
+ } catch {
15825
+ if (attempt >= 2) {
15826
+ diagnosticConsole.warn({ event: "step_persist_failed" });
15827
+ return;
15828
+ }
15829
+ await new Promise((resolve) => setTimeout(resolve, 50 * (attempt + 1)));
15830
+ }
15831
+ }
15832
+ } : void 0,
15648
15833
  onPaymentResume: () => retryJobSnapshot(() => this.persistJob(job)),
15649
15834
  subscribeNotifications: isStreamableJobStore(jobStore) ? (cb) => jobStore.subscribeNotifications(job.id, cb) : void 0,
15650
15835
  acceptedBalancePayment: this.opts.db && jobStore instanceof PostgresJobStore ? async (seq, amountMicro, currency) => {
@@ -20829,7 +21014,7 @@ async function createDVMServer(descriptor, opts) {
20829
21014
  }
20830
21015
  }
20831
21016
  if (!opts.onJobCompleted && platformToken && platformUrl && dvmId && opts.db) {
20832
- const { RevenueReporter } = await import("./revenue-reporter-HD4GMXGD.js");
21017
+ const { RevenueReporter } = await import("./revenue-reporter-RIEHBKRA.js");
20833
21018
  const reporter = new RevenueReporter(
20834
21019
  opts.db,
20835
21020
  platformUrl,
@@ -20839,7 +21024,7 @@ async function createDVMServer(descriptor, opts) {
20839
21024
  revenueReporter = reporter;
20840
21025
  await reporter.init();
20841
21026
  reporter.startRetryLoop();
20842
- const { MemoryX402BatchStore, PayoutReporter } = await import("./payout-reporter-NXWQVKXW.js");
21027
+ const { MemoryX402BatchStore, PayoutReporter } = await import("./payout-reporter-DDQ7FGRC.js");
20843
21028
  payoutReporter = new PayoutReporter({
20844
21029
  dvmId,
20845
21030
  db: opts.db,
@@ -23150,7 +23335,6 @@ var DVMServer = class {
23150
23335
  );
23151
23336
  }
23152
23337
  const sdkCtx = jm.buildAndAttachContext(job, parsedInput);
23153
- jm.startIdleTimer(job);
23154
23338
  jm.runHandler(job, sdkCtx);
23155
23339
  await new Promise((resolve) => {
23156
23340
  setTimeout(resolve, 0);
@@ -23494,14 +23678,21 @@ var DVMServer = class {
23494
23678
  const job = this.jobManager.activeJobs.get(id);
23495
23679
  const after = Number(c.req.query("after") ?? "0");
23496
23680
  const accept = c.req.header("Accept") ?? "";
23497
- if (!job) {
23498
- const store = this.jobManager.store;
23681
+ const store = this.jobManager.store;
23682
+ if (job && !isJobOwner(c, job)) return c.json({ error: "Job not found" }, 404);
23683
+ if (!job || isStreamableJobStore(store)) {
23684
+ if (job) await job.messageAppenderTail;
23499
23685
  const record = await store.get(id);
23500
23686
  if (!record) return c.json({ error: "Job not found" }, 404);
23501
23687
  if (!isJobOwner(c, record)) return c.json({ error: "Job not found" }, 404);
23502
- const filtered = record.messages.filter((m) => m.seq > after);
23688
+ const terminal = isTerminal(record.status) || job !== void 0 && isTerminal(job.status);
23689
+ let filtered = record.messages.filter((m) => m.seq > after);
23690
+ if (isStreamableJobStore(store)) {
23691
+ const durable = await store.getMessages(id, after, { includeHeld: terminal });
23692
+ if (durable.length > 0 || !terminal) filtered = durable;
23693
+ }
23503
23694
  if (accept.includes("text/event-stream")) {
23504
- if (isStreamableJobStore(store) && !isTerminal(record.status)) {
23695
+ if (isStreamableJobStore(store) && !terminal) {
23505
23696
  return streamSSE(c, async (stream) => {
23506
23697
  let unsubFn;
23507
23698
  await new Promise((resolve) => {
@@ -23564,10 +23755,6 @@ var DVMServer = class {
23564
23755
  }
23565
23756
  });
23566
23757
  }
23567
- if (isStreamableJobStore(store) && !isTerminal(record.status)) {
23568
- const msgs = await store.getMessages(id, after);
23569
- if (msgs.length > 0) return c.json(await this.messageFields(record, msgs));
23570
- }
23571
23758
  return c.json(await this.messageFields(record, filtered));
23572
23759
  }
23573
23760
  if (!isJobOwner(c, job)) return c.json({ error: "Job not found" }, 404);
@@ -23575,7 +23762,7 @@ var DVMServer = class {
23575
23762
  const fields = await this.contentFields(job);
23576
23763
  return streamSSE(c, async (stream) => {
23577
23764
  if (fields.content_available === false) return;
23578
- const existing = job.messages.filter((m) => m.seq > after);
23765
+ const existing = visibleJobMessages(job).filter((m) => m.seq > after);
23579
23766
  let yielded = false;
23580
23767
  for (const msg of existing) {
23581
23768
  await stream.writeSSE({ data: JSON.stringify(msg), event: "message" });
@@ -23603,7 +23790,7 @@ var DVMServer = class {
23603
23790
  });
23604
23791
  });
23605
23792
  }
23606
- return c.json(await this.messageFields(job, job.messages.filter((m) => m.seq > after)));
23793
+ return c.json(await this.messageFields(job, visibleJobMessages(job).filter((m) => m.seq > after)));
23607
23794
  }
23608
23795
  jobNotActiveResponse(c) {
23609
23796
  return c.json(
@@ -23697,7 +23884,6 @@ var DVMServer = class {
23697
23884
  } else {
23698
23885
  await jm.markInboundVerified(activeJob.id, inboundSeq2);
23699
23886
  }
23700
- jm.startIdleTimer(activeJob);
23701
23887
  try {
23702
23888
  await jm.dispatchMessage(activeJob, body2.type, body2.content);
23703
23889
  await this.persistMessageSnapshot(jm, activeJob, body2.type);
@@ -23794,11 +23980,13 @@ var DVMServer = class {
23794
23980
  ...unappliedCredit && { credit: unappliedCredit },
23795
23981
  ...drawFundingReceipt && { draw_funding_receipt: drawFundingReceipt }
23796
23982
  };
23797
- const elapsed = Date.now() - record.lastActivityAt;
23798
- if (elapsed >= this.descriptor.idleTimeout * 1e3) {
23983
+ const deadline = callerDeadline(record, this.descriptor.idleTimeout * 1e3);
23984
+ if (deadline !== void 0 && Date.now() >= deadline) {
23799
23985
  await jm.cancelJobIdle(job);
23800
- const lastMsg2 = job.messages[job.messages.length - 1];
23801
- return c.json({ seq: lastMsg2.seq, ...creditBlock }, 201);
23986
+ if (isTerminal(job.status)) {
23987
+ const lastMsg2 = job.messages[job.messages.length - 1];
23988
+ return c.json({ seq: lastMsg2.seq, ...creditBlock }, 201);
23989
+ }
23802
23990
  }
23803
23991
  jm.startIdleTimer(job);
23804
23992
  try {
@@ -23978,6 +24166,7 @@ function recordedDrawPayment(draw, opts) {
23978
24166
  ...rail === "cashu" ? { cashuFlow: "p2pk_accumulator" } : {},
23979
24167
  creditId: draw.creditId,
23980
24168
  drawId: draw.drawId,
24169
+ upfrontPayment: upfrontPaymentFromDraw(draw),
23981
24170
  drawAmountMicro: draw.amountMicro,
23982
24171
  ...draw.railValue.creditCurrency ? { creditCurrency: draw.railValue.creditCurrency } : {}
23983
24172
  };
@@ -24063,6 +24252,7 @@ function removeUnavailableContent(fields) {
24063
24252
  }
24064
24253
  function terminalResponseFields(job) {
24065
24254
  const fields = {};
24255
+ if (job.upfrontPayment !== void 0) fields.upfront_payment = job.upfrontPayment;
24066
24256
  if (job.status === "completed") {
24067
24257
  if (job.summary !== void 0) fields.summary = job.summary;
24068
24258
  const artifactCount = job.messages.filter((m) => m.type === "artifact").length;