@dvmkit/sdk 0.5.0-rc.1 → 0.7.0-rc.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.
- package/README.md +100 -0
- package/dist/{chunk-JH3SX3LY.js → chunk-6XF3RVVX.js} +1 -1
- package/dist/chunk-AFV77S4C.js +258 -0
- package/dist/{chunk-RLTSCKRI.js → chunk-BPYMEMQX.js} +50 -3
- package/dist/chunk-BQJIFRAZ.js +36 -0
- package/dist/{chunk-IWQHUSMO.js → chunk-CXCQLOOK.js} +143 -2
- package/dist/{chunk-PB4FYZET.js → chunk-EQLA4FFY.js} +54 -4
- package/dist/{chunk-LE74UEV7.js → chunk-EZXAJ6EI.js} +64 -53
- package/dist/{chunk-FI4CST2D.js → chunk-FWGWLPXF.js} +4 -6
- package/dist/{chunk-EYTHFFFU.js → chunk-GY66THYI.js} +179 -56
- package/dist/{chunk-TZ2WKCDD.js → chunk-HCLVYZUZ.js} +2 -2
- package/dist/{chunk-O5AT6S5T.js → chunk-IV6Q7NRW.js} +25 -195
- package/dist/{chunk-UOCZXO2Z.js → chunk-JEVLNAO3.js} +10 -5
- package/dist/{chunk-TUA7PYH7.js → chunk-KMED3H3A.js} +134 -130
- package/dist/{chunk-Z4KLECAC.js → chunk-N5RZ5HVB.js} +1 -1
- package/dist/{chunk-PAIM66YA.js → chunk-NFMOPYBA.js} +47 -37
- package/dist/{chunk-B2IROPQF.js → chunk-O2OTZ5GP.js} +40 -15
- package/dist/{chunk-FYRR2UEZ.js → chunk-OML32F6I.js} +1 -1
- package/dist/{chunk-6DOGTAHA.js → chunk-QR2EF2NI.js} +9 -43
- package/dist/{chunk-7ZUKL4IV.js → chunk-RA2WIX7G.js} +5 -5
- package/dist/{chunk-A4YFWZ7R.js → chunk-RCM3P2HE.js} +1 -1
- package/dist/{chunk-PLG4XER6.js → chunk-S3OY533W.js} +1 -1
- package/dist/{chunk-GFY5FRX3.js → chunk-SZVXOQ75.js} +5 -12
- package/dist/{chunk-MZRJR2CW.js → chunk-WBOKPC37.js} +22 -3
- package/dist/content-retention-DHyLdA6b.d.ts +78 -0
- package/dist/{credit-ledger-MI2H7NEC.js → credit-ledger-MCUXYKSJ.js} +2 -3
- package/dist/{credit-menu-CAfqe8IY.d.ts → credit-menu-DjnkeEUU.d.ts} +98 -5
- package/dist/{fx-D9nsN3n4.d.ts → fx-zMWFzfrU.d.ts} +1 -1
- package/dist/index.d.ts +5 -5
- package/dist/index.js +3 -3
- package/dist/internal/caller.d.ts +1369 -860
- package/dist/internal/caller.js +1079 -991
- package/dist/internal/index.js +1 -1
- package/dist/internal/server.d.ts +8 -99
- package/dist/internal/server.js +19 -18
- package/dist/{job-store-Bx-VFRjg.d.ts → job-store-DdLXBpol.d.ts} +1 -1
- package/dist/{lightning-backend-BAx2Y8pP.d.ts → lightning-backend-Bw-qtAbt.d.ts} +4 -2
- package/dist/{memory-credit-ledger-W2BAUYJP.js → memory-credit-ledger-SBSMGMDX.js} +3 -4
- package/dist/{postgres-consumed-credential-store-CUQJKRSL.js → postgres-consumed-credential-store-BUVCAPLS.js} +13 -0
- package/dist/{postgres-job-store-VLXLWXUQ.js → postgres-job-store-VHLQBJZC.js} +3 -1
- package/dist/server/index.d.ts +28 -10
- package/dist/server/index.js +34 -24
- package/dist/{step-cache-QG_h5-3G.d.ts → step-cache-2MkvenJn.d.ts} +3 -3
- package/dist/tempo-lifecycle-GJ77YKVY.js +15 -0
- package/dist/{tempo-wallet-B5XXV3YH.js → tempo-wallet-X2GGAZ6X.js} +4 -4
- package/dist/testing/index.d.ts +9 -2
- package/dist/testing/index.js +6 -3
- package/dist/{usd-EwRqCPF-.d.ts → usd-DI3d3gTK.d.ts} +1 -1
- package/dist/{x402-R6HPBY5D.js → x402-FT4S4JS4.js} +8 -10
- package/package.json +5 -3
- package/recipes/postgres/financial-export-v1.sql +137 -0
- package/dist/chunk-CZULM42S.js +0 -143
- package/dist/chunk-LIRAHP7B.js +0 -53
- package/dist/tempo-lifecycle-ZBBLFBMH.js +0 -15
package/README.md
CHANGED
|
@@ -96,6 +96,12 @@ For Bitcoin credit monitoring, `GET /admin/credit/bitcoin-liability` reports exa
|
|
|
96
96
|
|
|
97
97
|
The public [SDK guide](https://dvmkit.com/docs/sdk) covers signed requests, custom routes, rail configuration, credit reclaim, persistence, and deployment in depth. The [protocol guide](https://dvmkit.com/docs/protocol) specifies the HTTP contract.
|
|
98
98
|
|
|
99
|
+
## Diagnostic retention
|
|
100
|
+
|
|
101
|
+
The SDK advertises 730 days of retention for sanitized diagnostics in every existing span dataset: `platform`, `cashu`, `lightning`, `stripe`, `x402`, `tempo`, `isolate`, `audit`, and `cron`. Dataset names, producer permissions, and query semantics remain unchanged; only operator services may write to `audit`.
|
|
102
|
+
|
|
103
|
+
This metadata describes the retention schedule. The SDK does not automatically delete diagnostics or override platform destruction or case controls. The platform must enforce the schedule and those controls.
|
|
104
|
+
|
|
99
105
|
## Repository development
|
|
100
106
|
|
|
101
107
|
Use the Node.js release in `.nvmrc` (Node 22). Install with `npm ci`, then run the same gate CI runs with `npm run verify`. The gate checks repository metadata in the checkout, copies only tracked and unignored source into an isolated directory, installs its locked dependencies, and runs lint, build, typecheck (including the release scripts under `checkJs`), unit and database tests, API, package, consumer, packed-artifact safety, and release checks there. The clean source tree owns the complete build and test pass, so verification never depends on the checkout's `node_modules` or pre-existing `dist` and does not repeat the full suite; after every check passes, its verified `dist` replaces the checkout's build output for release packaging.
|
|
@@ -182,3 +188,97 @@ None of this claims exclusive rights in the phrase "digital vending machine" or
|
|
|
182
188
|
## License
|
|
183
189
|
|
|
184
190
|
Apache-2.0. See [LICENSE](./LICENSE) and [NOTICE](./NOTICE).
|
|
191
|
+
|
|
192
|
+
## Job content retention and case holds
|
|
193
|
+
|
|
194
|
+
`configureDVM` defaults `jobRetentionDays` to 730. Set a shorter or longer whole-day
|
|
195
|
+
window explicitly, or `0` to disable automatic age expiry. Only completed, failed,
|
|
196
|
+
and cancelled jobs are eligible. The terminal `lastActivityAt` is frozen: reads,
|
|
197
|
+
receipt delivery, late messages, and re-saving an outcome do not renew it. The
|
|
198
|
+
sweep removes input, params, summary, messages, state, caches, and bearer proofs;
|
|
199
|
+
it preserves durable status, signed receipts, payment amounts, and recovery linkage.
|
|
200
|
+
|
|
201
|
+
Authenticated job submit, request-result, status and JSON message responses expose
|
|
202
|
+
`content_expires_at` (epoch milliseconds or `null`) and `content_available` (boolean).
|
|
203
|
+
A null deadline means the job is active, expiry is disabled, or an active hold
|
|
204
|
+
suppresses it. A past deadline can still have available content until the sweep
|
|
205
|
+
runs. Missing fields on an older service or unsupported custom store mean unknown.
|
|
206
|
+
SSE messages retain their existing event format; read job status for expiry.
|
|
207
|
+
|
|
208
|
+
With durable service-admin configuration, use signed `Authorization: P2PK`
|
|
209
|
+
requests on the service-local routes:
|
|
210
|
+
|
|
211
|
+
| Method | Path | JSON body |
|
|
212
|
+
| --- | --- | --- |
|
|
213
|
+
| GET | `/admin/jobs/:jobId/content-holds` | none |
|
|
214
|
+
| POST | `/admin/jobs/:jobId/content-holds` | `purpose`, `reason`, `review_due_at` |
|
|
215
|
+
| POST | `/admin/jobs/:jobId/content-holds/:holdId/renew` | `reason`, `review_due_at` |
|
|
216
|
+
| POST | `/admin/jobs/:jobId/content-holds/:holdId/clear` | `reason` |
|
|
217
|
+
|
|
218
|
+
Purpose is `support` or `dispute`; reason is 1–1000 characters and review dates
|
|
219
|
+
are future epoch millisecond integers. Unknown body fields are rejected. Hold IDs,
|
|
220
|
+
actor public keys, and creation/clear times are server-derived. Authentication uses
|
|
221
|
+
the existing method/path/service-bound admin challenge, current and grace-period
|
|
222
|
+
keys, and atomic durable nonce consumption. A hold request does not grant access
|
|
223
|
+
to job content. Authentication failure returns 401 with a reason; an unsupported
|
|
224
|
+
store returns `content_holds_unavailable` (503), missing jobs return 404, and a
|
|
225
|
+
redacted job or inactive hold returns 409. Holds cannot restore redacted content.
|
|
226
|
+
|
|
227
|
+
Responses include `job_id`, caller content metadata, `baseline_content_expires_at`,
|
|
228
|
+
`holds`, and append-only `events`. Each hold contains `id`, `job_id`, `purpose`,
|
|
229
|
+
`reason`, original `actor`, `created_at`, `review_due_at`, `cleared_at`, and computed
|
|
230
|
+
`review_overdue`. Each event contains `id`, `hold_id`, `job_id`, `action`, `actor`,
|
|
231
|
+
`reason`, `at`, `review_due_at`. Renew and clear preserve the original actor and
|
|
232
|
+
creation date; their authenticated actors are in history. Any active hold suppresses
|
|
233
|
+
the effective deadline. Overdue review flags work; it does not release the hold.
|
|
234
|
+
The baseline remains visible for review. Clearing the last hold restores the
|
|
235
|
+
baseline and can make content immediately eligible for the next sweep.
|
|
236
|
+
|
|
237
|
+
`PostgresJobStore.init()` adds `job_content_holds` and `job_content_hold_events`
|
|
238
|
+
with job-scoped indexes; existing jobs need no expiry column or backfill. Placement
|
|
239
|
+
and sweep lock the same job row and check redaction/terminal activity/active holds
|
|
240
|
+
inside the transaction. Admin operations refuse jobs bound to another service;
|
|
241
|
+
legacy unbound rows belong to the local service database. A committed hold wins any later competing sweep; if
|
|
242
|
+
redaction wins first, placement fails. `MemoryJobStore` supplies the same contract
|
|
243
|
+
for development. Custom stores must implement `ContentHoldStore` and atomic
|
|
244
|
+
redaction fencing to support holds and truthful metadata.
|
|
245
|
+
|
|
246
|
+
Before explicitly destroying a service database, use your existing database
|
|
247
|
+
connection/proxy and the packaged `recipes/postgres/financial-export-v1.sql`:
|
|
248
|
+
|
|
249
|
+
```sh
|
|
250
|
+
psql "$DATABASE_URL" -X -q -t -A -v ON_ERROR_STOP=1 \
|
|
251
|
+
-f node_modules/@dvmkit/sdk/recipes/postgres/financial-export-v1.sql \
|
|
252
|
+
> financial-evidence.json
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
The versioned JSON export takes a consistent read-only snapshot and includes signed
|
|
256
|
+
receipt tuples, ledger balances/amounts/currencies, job/payment links, and recovery
|
|
257
|
+
outcomes. Funding lots preserve every deposit's original sats and credit basis,
|
|
258
|
+
including depleted lots. Draws retain both the original allocation and settled
|
|
259
|
+
amount. Refunds retain fulfilment costs and funding-lot linkage. Optional channel
|
|
260
|
+
projections preserve amounts and settlement links while excluding authorization
|
|
261
|
+
signatures and vouchers; Tempo bigint amounts are decimal strings.
|
|
262
|
+
Unsigned JSON blocks use typed financial projections: unsupported fields and
|
|
263
|
+
objects or arrays in scalar positions are excluded. Optional ledger tables absent from a service become empty arrays. It
|
|
264
|
+
excludes raw input/output, bearer tokens/proofs, payout descriptors, and operational
|
|
265
|
+
credentials. Signed receipts may retain their signed terminal reason and result
|
|
266
|
+
hash; supported version-1 job, funding, and drain receipts are returned unchanged.
|
|
267
|
+
Unsupported receipt versions, kinds, fields, or nested extensions fail the entire
|
|
268
|
+
export before any JSON is emitted. Update the recipe for that schema before
|
|
269
|
+
destruction; a failed export is not retained financial evidence. Verify receipt
|
|
270
|
+
signatures and check the file before destruction, then keep it securely with this recipe
|
|
271
|
+
and the service's public receipt-key attestation. The builder owns this export;
|
|
272
|
+
the platform retains its separate financial history. An export does not settle
|
|
273
|
+
caller balances or refunds: resolve liabilities through their existing lifecycle.
|
|
274
|
+
Case holds affect age expiry, not explicit erasure or contract/lifecycle destruction.
|
|
275
|
+
The active-service window does not extend any contractual maximum for copies after
|
|
276
|
+
explicit destruction, including a 90-day post-destruction limit where applicable.
|
|
277
|
+
|
|
278
|
+
The wire fields and routes are additive within protocol version 1. Existing explicit
|
|
279
|
+
retention settings retain their value. The new default also applies to existing
|
|
280
|
+
unredacted terminal rows; previously removed content stays unavailable. Upgrade
|
|
281
|
+
all replicas before relying on holds: older SDKs do not recognize the hold fence.
|
|
282
|
+
Do not downgrade or mix old replicas with active holds unless automatic sweeping
|
|
283
|
+
is disabled on those replicas. The SQL export format is versioned independently
|
|
284
|
+
of the package and is not a raw database backup or a content archive.
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
import {
|
|
2
|
+
StepCache
|
|
3
|
+
} from "./chunk-BQJIFRAZ.js";
|
|
4
|
+
|
|
5
|
+
// src/sdk/server/job.ts
|
|
6
|
+
function toJobRecord(job) {
|
|
7
|
+
return {
|
|
8
|
+
id: job.id,
|
|
9
|
+
tags: job.tags,
|
|
10
|
+
capability: job.capability,
|
|
11
|
+
input: job.input,
|
|
12
|
+
params: job.params,
|
|
13
|
+
requesterId: job.requesterId,
|
|
14
|
+
requesterTokenHash: job.requesterTokenHash,
|
|
15
|
+
requesterToken: job.requesterToken,
|
|
16
|
+
requestFingerprint: job.requestFingerprint,
|
|
17
|
+
requesterPubkey: job.requesterPubkey,
|
|
18
|
+
requestId: job.requestId,
|
|
19
|
+
authRequestPath: job.authRequestPath,
|
|
20
|
+
status: job.status,
|
|
21
|
+
summary: job.summary,
|
|
22
|
+
timingKnown: job.timingKnown,
|
|
23
|
+
endedBy: job.endedBy,
|
|
24
|
+
terminalAtMs: job.terminalAtMs,
|
|
25
|
+
callerWaitMs: job.callerWaitMs,
|
|
26
|
+
awaitingInputAtMs: job.awaitingInputAtMs,
|
|
27
|
+
messages: job.messages,
|
|
28
|
+
seq: job.seq,
|
|
29
|
+
paidMsats: job.paidMsats,
|
|
30
|
+
paymentMint: job.paymentMint,
|
|
31
|
+
paymentRail: job.paymentRail,
|
|
32
|
+
paymentTxHash: job.paymentTxHash,
|
|
33
|
+
paymentTransactionHash: job.paymentTransactionHash,
|
|
34
|
+
nativeAmount: job.nativeAmount,
|
|
35
|
+
nativeAsset: job.nativeAsset,
|
|
36
|
+
cashuFlow: job.cashuFlow,
|
|
37
|
+
costAmountMicro: job.costAmountMicro,
|
|
38
|
+
costCurrency: job.costCurrency,
|
|
39
|
+
costRevision: job.costRevision,
|
|
40
|
+
creditId: job.creditId,
|
|
41
|
+
drawId: job.drawId,
|
|
42
|
+
fundingReceipt: job.fundingReceipt,
|
|
43
|
+
fundingCredit: job.fundingCredit,
|
|
44
|
+
receivedProofs: job.receivedProofs,
|
|
45
|
+
pendingPaymentMsats: job.pendingPaymentMsats,
|
|
46
|
+
pendingMppChallengeIds: job.pendingMppChallengeIds,
|
|
47
|
+
pendingX402Nonce: job.pendingX402Nonce,
|
|
48
|
+
pendingX402AmountUsdcMicro: job.pendingX402AmountUsdcMicro,
|
|
49
|
+
pendingPaymentFiatMicro: job.pendingPaymentFiatMicro,
|
|
50
|
+
pendingPaymentFiatCurrency: job.pendingPaymentFiatCurrency,
|
|
51
|
+
askedTopUpMicro: job.askedTopUpMicro,
|
|
52
|
+
askedTopUpCurrency: job.askedTopUpCurrency,
|
|
53
|
+
topUpCapUnenforcedReason: job.topUpCapUnenforcedReason,
|
|
54
|
+
requiredMsats: job.requiredMsats,
|
|
55
|
+
receipt: job.receipt,
|
|
56
|
+
stepCache: job.stepCache.serialize(),
|
|
57
|
+
state: job.state,
|
|
58
|
+
createdAt: job.createdAt,
|
|
59
|
+
lastActivityAt: job.lastActivityAt
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
function fromJobRecord(record) {
|
|
63
|
+
return {
|
|
64
|
+
id: record.id,
|
|
65
|
+
tags: record.tags,
|
|
66
|
+
capability: record.capability,
|
|
67
|
+
input: record.input,
|
|
68
|
+
params: record.params,
|
|
69
|
+
requesterId: record.requesterId,
|
|
70
|
+
requesterTokenHash: record.requesterTokenHash,
|
|
71
|
+
status: record.status,
|
|
72
|
+
summary: record.summary,
|
|
73
|
+
timingKnown: record.timingKnown,
|
|
74
|
+
endedBy: record.endedBy,
|
|
75
|
+
terminalAtMs: record.terminalAtMs,
|
|
76
|
+
callerWaitMs: record.callerWaitMs,
|
|
77
|
+
awaitingInputAtMs: record.awaitingInputAtMs,
|
|
78
|
+
messages: [...record.messages],
|
|
79
|
+
seq: record.seq,
|
|
80
|
+
paidMsats: record.paidMsats,
|
|
81
|
+
paymentMint: record.paymentMint,
|
|
82
|
+
paymentRail: record.paymentRail,
|
|
83
|
+
paymentTxHash: record.paymentTxHash,
|
|
84
|
+
paymentTransactionHash: record.paymentTransactionHash,
|
|
85
|
+
nativeAmount: record.nativeAmount,
|
|
86
|
+
nativeAsset: record.nativeAsset,
|
|
87
|
+
cashuFlow: record.cashuFlow,
|
|
88
|
+
costAmountMicro: record.costAmountMicro,
|
|
89
|
+
costCurrency: record.costCurrency,
|
|
90
|
+
costRevision: record.costRevision,
|
|
91
|
+
creditId: record.creditId,
|
|
92
|
+
drawId: record.drawId,
|
|
93
|
+
fundingReceipt: record.fundingReceipt,
|
|
94
|
+
fundingCredit: record.fundingCredit,
|
|
95
|
+
receivedProofs: [...record.receivedProofs],
|
|
96
|
+
pendingPaymentMsats: record.pendingPaymentMsats,
|
|
97
|
+
pendingMppChallengeIds: record.pendingMppChallengeIds ? [...record.pendingMppChallengeIds] : void 0,
|
|
98
|
+
requesterToken: record.requesterToken,
|
|
99
|
+
requestFingerprint: record.requestFingerprint,
|
|
100
|
+
requesterPubkey: record.requesterPubkey,
|
|
101
|
+
requestId: record.requestId,
|
|
102
|
+
authRequestPath: record.authRequestPath,
|
|
103
|
+
pendingX402Nonce: record.pendingX402Nonce,
|
|
104
|
+
pendingX402AmountUsdcMicro: record.pendingX402AmountUsdcMicro,
|
|
105
|
+
pendingPaymentFiatMicro: record.pendingPaymentFiatMicro,
|
|
106
|
+
pendingPaymentFiatCurrency: record.pendingPaymentFiatCurrency,
|
|
107
|
+
askedTopUpMicro: record.askedTopUpMicro,
|
|
108
|
+
askedTopUpCurrency: record.askedTopUpCurrency,
|
|
109
|
+
topUpCapUnenforcedReason: record.topUpCapUnenforcedReason,
|
|
110
|
+
requiredMsats: record.requiredMsats,
|
|
111
|
+
receipt: record.receipt,
|
|
112
|
+
listeners: /* @__PURE__ */ new Set(),
|
|
113
|
+
abort: new AbortController(),
|
|
114
|
+
messageAppender: void 0,
|
|
115
|
+
messageAppenderTail: void 0,
|
|
116
|
+
stepCache: StepCache.deserialize(record.stepCache),
|
|
117
|
+
state: record.state,
|
|
118
|
+
pendingPrompts: /* @__PURE__ */ new Map(),
|
|
119
|
+
pendingPayment: null,
|
|
120
|
+
replayHighSeq: null,
|
|
121
|
+
replayProviderSkip: 0,
|
|
122
|
+
replayPaymentIndex: 0,
|
|
123
|
+
createdAt: record.createdAt,
|
|
124
|
+
lastActivityAt: record.lastActivityAt
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
function abortJob(job, reason) {
|
|
128
|
+
job.abort.abort(new JobCancelledError(reason));
|
|
129
|
+
}
|
|
130
|
+
var JobCancelledError = class extends Error {
|
|
131
|
+
constructor(message = "Job cancelled") {
|
|
132
|
+
super(message);
|
|
133
|
+
this.name = "JobCancelledError";
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
function providerMessage(job, type, content) {
|
|
137
|
+
if (job.replayProviderSkip > 0) {
|
|
138
|
+
job.replayProviderSkip--;
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
const msg = localMessage(job, "provider", type, content);
|
|
142
|
+
if (type === "payment-request") {
|
|
143
|
+
const paymentRequest = content;
|
|
144
|
+
const pendingPaymentDelta = typeof paymentRequest.amount_msats === "number" ? paymentRequest.amount_msats : void 0;
|
|
145
|
+
const pendingMppChallengeIds = paymentRequest.tempo?.challenges?.map((challenge) => challenge.id).filter((id) => typeof id === "string");
|
|
146
|
+
job.messageAppender?.(msg, {
|
|
147
|
+
awaitingInput: job.status === "awaiting-input",
|
|
148
|
+
pendingPaymentDelta,
|
|
149
|
+
pendingMppChallengeIds,
|
|
150
|
+
pendingX402Nonce: typeof paymentRequest.x402?.nonce === "string" ? paymentRequest.x402.nonce : void 0,
|
|
151
|
+
pendingX402AmountUsdcMicro: typeof paymentRequest.x402?.required_usdc_micro === "string" ? paymentRequest.x402.required_usdc_micro : void 0,
|
|
152
|
+
// read off the job rather than the content — the fiat envelope
|
|
153
|
+
// is ledger state, not part of the wire shape, and `requestPayment` has
|
|
154
|
+
// already pinned it on the job by the time this fires (same ordering the
|
|
155
|
+
// `pendingPaymentMsats` comment above describes).
|
|
156
|
+
pendingPaymentFiatMicro: job.pendingPaymentFiatMicro,
|
|
157
|
+
pendingPaymentFiatCurrency: job.pendingPaymentFiatCurrency
|
|
158
|
+
});
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
job.messageAppender?.(msg, type === "prompt" ? { awaitingInput: job.status === "awaiting-input" } : void 0);
|
|
162
|
+
}
|
|
163
|
+
function localMessage(job, from, type, content) {
|
|
164
|
+
job.seq++;
|
|
165
|
+
const msg = {
|
|
166
|
+
seq: job.seq,
|
|
167
|
+
from,
|
|
168
|
+
timestamp: Math.floor(Date.now() / 1e3),
|
|
169
|
+
type,
|
|
170
|
+
content
|
|
171
|
+
};
|
|
172
|
+
job.messages.push(msg);
|
|
173
|
+
for (const listener of job.listeners) {
|
|
174
|
+
listener(msg);
|
|
175
|
+
}
|
|
176
|
+
return msg;
|
|
177
|
+
}
|
|
178
|
+
function isTerminal(status) {
|
|
179
|
+
return status === "completed" || status === "failed" || status === "cancelled";
|
|
180
|
+
}
|
|
181
|
+
function isYieldMessage(msg) {
|
|
182
|
+
return msg.type === "prompt" || msg.type === "payment-request" || msg.type === "working" || msg.type === "complete" || msg.type === "cancel";
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// src/sdk/server/content-retention.ts
|
|
186
|
+
import { randomUUID } from "crypto";
|
|
187
|
+
var ContentHoldError = class extends Error {
|
|
188
|
+
constructor(code) {
|
|
189
|
+
super(code);
|
|
190
|
+
this.code = code;
|
|
191
|
+
}
|
|
192
|
+
code;
|
|
193
|
+
};
|
|
194
|
+
function isContentHoldStore(store) {
|
|
195
|
+
return "getContentSnapshot" in store && "mutateContentHold" in store;
|
|
196
|
+
}
|
|
197
|
+
function contentMetadata(job, retentionDays, holds = []) {
|
|
198
|
+
const baseline = baselineContentExpiry(job, retentionDays);
|
|
199
|
+
return {
|
|
200
|
+
content_expires_at: holds.some((hold) => hold.cleared_at === null) ? null : baseline,
|
|
201
|
+
content_available: job.redactedAt === void 0
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
function baselineContentExpiry(job, retentionDays) {
|
|
205
|
+
return retentionDays > 0 && isTerminal(job.status) ? job.lastActivityAt + retentionDays * 864e5 : null;
|
|
206
|
+
}
|
|
207
|
+
function applyContentHoldMutation(job, history, mutation) {
|
|
208
|
+
if (job.redactedAt !== void 0) throw new ContentHoldError("content_unavailable");
|
|
209
|
+
const result = structuredClone(history);
|
|
210
|
+
let hold;
|
|
211
|
+
if (mutation.action === "placed") {
|
|
212
|
+
hold = {
|
|
213
|
+
id: randomUUID(),
|
|
214
|
+
job_id: job.id,
|
|
215
|
+
purpose: mutation.purpose,
|
|
216
|
+
reason: mutation.reason,
|
|
217
|
+
actor: mutation.actor,
|
|
218
|
+
created_at: mutation.at,
|
|
219
|
+
review_due_at: mutation.review_due_at,
|
|
220
|
+
cleared_at: null
|
|
221
|
+
};
|
|
222
|
+
result.holds.push(hold);
|
|
223
|
+
} else {
|
|
224
|
+
const existing = result.holds.find((item) => item.id === mutation.hold_id && item.cleared_at === null);
|
|
225
|
+
if (!existing) throw new ContentHoldError("hold_not_active");
|
|
226
|
+
hold = existing;
|
|
227
|
+
if (mutation.action === "cleared") hold.cleared_at = mutation.at;
|
|
228
|
+
else hold.review_due_at = mutation.review_due_at;
|
|
229
|
+
hold.reason = mutation.reason;
|
|
230
|
+
}
|
|
231
|
+
result.events.push({
|
|
232
|
+
id: randomUUID(),
|
|
233
|
+
hold_id: hold.id,
|
|
234
|
+
job_id: job.id,
|
|
235
|
+
action: mutation.action,
|
|
236
|
+
actor: mutation.actor,
|
|
237
|
+
reason: mutation.reason,
|
|
238
|
+
at: mutation.at,
|
|
239
|
+
review_due_at: hold.review_due_at
|
|
240
|
+
});
|
|
241
|
+
return result;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
export {
|
|
245
|
+
toJobRecord,
|
|
246
|
+
fromJobRecord,
|
|
247
|
+
abortJob,
|
|
248
|
+
JobCancelledError,
|
|
249
|
+
providerMessage,
|
|
250
|
+
localMessage,
|
|
251
|
+
isTerminal,
|
|
252
|
+
isYieldMessage,
|
|
253
|
+
ContentHoldError,
|
|
254
|
+
isContentHoldStore,
|
|
255
|
+
contentMetadata,
|
|
256
|
+
baselineContentExpiry,
|
|
257
|
+
applyContentHoldMutation
|
|
258
|
+
};
|
|
@@ -1,6 +1,49 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
// src/lib/errors.ts
|
|
2
|
+
var DvmError = class extends Error {
|
|
3
|
+
constructor(code, message, hint, data, display, proseSources) {
|
|
4
|
+
super(message);
|
|
5
|
+
this.code = code;
|
|
6
|
+
this.hint = hint;
|
|
7
|
+
this.data = data;
|
|
8
|
+
this.display = display;
|
|
9
|
+
this.proseSources = proseSources;
|
|
10
|
+
this.name = "DvmError";
|
|
11
|
+
}
|
|
12
|
+
code;
|
|
13
|
+
hint;
|
|
14
|
+
data;
|
|
15
|
+
display;
|
|
16
|
+
proseSources;
|
|
17
|
+
};
|
|
18
|
+
function providerProseSources(...sources) {
|
|
19
|
+
const provenance = {};
|
|
20
|
+
for (const source of sources) {
|
|
21
|
+
if (!source) continue;
|
|
22
|
+
for (const field of ["message", "hint", "display"]) {
|
|
23
|
+
if (typeof source[field] === "string") provenance[field] = "provider";
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return Object.keys(provenance).length > 0 ? provenance : void 0;
|
|
27
|
+
}
|
|
28
|
+
function isAbortError(err) {
|
|
29
|
+
return err instanceof Error && (err.name === "AbortError" || err.name === "TimeoutError");
|
|
30
|
+
}
|
|
31
|
+
function transportErrorCode(err) {
|
|
32
|
+
let current = err;
|
|
33
|
+
const seen = /* @__PURE__ */ new Set();
|
|
34
|
+
for (let depth = 0; depth < 5 && isErrorLike(current) && !seen.has(current); depth++) {
|
|
35
|
+
seen.add(current);
|
|
36
|
+
const code = current.code;
|
|
37
|
+
if (typeof code === "string" && /^(?:E[A-Z0-9_]{1,63}|UND_ERR_[A-Z0-9_]{1,63})$/.test(code)) {
|
|
38
|
+
return code;
|
|
39
|
+
}
|
|
40
|
+
current = current.cause;
|
|
41
|
+
}
|
|
42
|
+
return void 0;
|
|
43
|
+
}
|
|
44
|
+
function isErrorLike(value) {
|
|
45
|
+
return (typeof value === "object" || typeof value === "function") && value !== null;
|
|
46
|
+
}
|
|
4
47
|
|
|
5
48
|
// src/lib/x402/networks.ts
|
|
6
49
|
var X402_NETWORKS = [
|
|
@@ -99,6 +142,10 @@ var BY_CAIP2 = new Map(X402_NETWORKS.map((n) => [n.caip2, n]));
|
|
|
99
142
|
var BY_SLUG = new Map(X402_NETWORKS.map((n) => [n.slug, n]));
|
|
100
143
|
|
|
101
144
|
export {
|
|
145
|
+
DvmError,
|
|
146
|
+
providerProseSources,
|
|
147
|
+
isAbortError,
|
|
148
|
+
transportErrorCode,
|
|
102
149
|
X402_NETWORKS,
|
|
103
150
|
x402NetworkByCaip2,
|
|
104
151
|
x402NetworkBySlug,
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// src/sdk/step-cache.ts
|
|
2
|
+
var StepCache = class _StepCache {
|
|
3
|
+
cache = /* @__PURE__ */ new Map();
|
|
4
|
+
has(id) {
|
|
5
|
+
return this.cache.has(id);
|
|
6
|
+
}
|
|
7
|
+
get(id) {
|
|
8
|
+
return this.getRecord(id).value;
|
|
9
|
+
}
|
|
10
|
+
getRecord(id) {
|
|
11
|
+
const record = this.cache.get(id);
|
|
12
|
+
if (record === void 0) throw new Error(`StepCache: no cached result for step "${id}"`);
|
|
13
|
+
return record;
|
|
14
|
+
}
|
|
15
|
+
set(id, value, costs) {
|
|
16
|
+
this.cache.set(id, {
|
|
17
|
+
id,
|
|
18
|
+
value,
|
|
19
|
+
...costs !== void 0 && costs.length > 0 ? { costs: costs.map(({ amount, currency }) => ({ amount, currency })) } : {}
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
serialize() {
|
|
23
|
+
return [...this.cache.values()];
|
|
24
|
+
}
|
|
25
|
+
static deserialize(records) {
|
|
26
|
+
const cache = new _StepCache();
|
|
27
|
+
for (const { id, value, costs } of records) {
|
|
28
|
+
cache.set(id, value, costs);
|
|
29
|
+
}
|
|
30
|
+
return cache;
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export {
|
|
35
|
+
StepCache
|
|
36
|
+
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
x402NetworkByCaip2,
|
|
3
3
|
x402NetworkBySlug
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-BPYMEMQX.js";
|
|
5
5
|
|
|
6
6
|
// src/lib/x402/constants.ts
|
|
7
7
|
var X402_DEFAULT_FACILITATOR = "https://x402.org/facilitator";
|
|
@@ -204,6 +204,118 @@ function isOptionalRecord(value) {
|
|
|
204
204
|
return value === void 0 || value === null || isRecord(value);
|
|
205
205
|
}
|
|
206
206
|
|
|
207
|
+
// src/defaults.ts
|
|
208
|
+
import { homedir } from "os";
|
|
209
|
+
import { join } from "path";
|
|
210
|
+
var CONFIG_DIR = process.env.DVM_CONFIG_DIR ?? join(homedir(), ".dvm");
|
|
211
|
+
var CONFIG_FILE = join(CONFIG_DIR, "config.json");
|
|
212
|
+
var CONFIG_BACKUP_FILE = join(CONFIG_DIR, "config.json.bak");
|
|
213
|
+
var CONFIG_AUDIT_FILE = join(CONFIG_DIR, "config-audit.jsonl");
|
|
214
|
+
var CONFIG_PINNED_FILE = join(CONFIG_DIR, "config.json.pinned");
|
|
215
|
+
var JOBS_FILE = join(CONFIG_DIR, "jobs.json");
|
|
216
|
+
var REQUEST_INTENTS_DIR = join(CONFIG_DIR, "request-intents");
|
|
217
|
+
var FAVORITES_FILE = join(CONFIG_DIR, "favorites.json");
|
|
218
|
+
var MESSAGES_DIR = join(CONFIG_DIR, "messages");
|
|
219
|
+
var RECEIPTS_FILE = join(CONFIG_DIR, "receipts.jsonl");
|
|
220
|
+
var CREDITS_FILE = join(CONFIG_DIR, "credits.json");
|
|
221
|
+
var X402_CHANNELS_FILE = join(CONFIG_DIR, "x402-channels.json");
|
|
222
|
+
var TEMPO_CHANNELS_FILE = join(CONFIG_DIR, "tempo-channels.json");
|
|
223
|
+
var FEEDBACK_NUDGES_FILE = join(CONFIG_DIR, "feedback-nudges.json");
|
|
224
|
+
var RECOVERY_MNEMONIC_FILE = join(CONFIG_DIR, "recovery.mnemonic");
|
|
225
|
+
var WALLET_FILE = join(CONFIG_DIR, "wallet.json");
|
|
226
|
+
var AGENT_MNEMONIC_FILE = join(CONFIG_DIR, "wallet.mnemonic");
|
|
227
|
+
var IDENTITIES_FILE = join(CONFIG_DIR, "identities.json");
|
|
228
|
+
var PAIRINGS_DIR = join(CONFIG_DIR, "pairings");
|
|
229
|
+
var BUDGET_FILE = join(CONFIG_DIR, "budget.json");
|
|
230
|
+
var BTC_RATE_FILE = join(CONFIG_DIR, "btc-rate.json");
|
|
231
|
+
var ADMIN_DIR = join(CONFIG_DIR, "admin");
|
|
232
|
+
var PROXY_STATE_FILE = join(ADMIN_DIR, "proxy.json");
|
|
233
|
+
var BUILDER_CONFIG_DIR = process.env.DVMKIT_CONFIG_DIR ?? join(homedir(), ".dvmkit");
|
|
234
|
+
var BUILDER_FILE = join(BUILDER_CONFIG_DIR, "builder.json");
|
|
235
|
+
var BUILDER_MNEMONIC_FILE = join(BUILDER_CONFIG_DIR, "recovery.mnemonic");
|
|
236
|
+
var BUILDER_KEY_FILE = join(BUILDER_CONFIG_DIR, "builder.key");
|
|
237
|
+
var MELT_PENDING_LOG_FILE = join(BUILDER_CONFIG_DIR, "melt-pending.log");
|
|
238
|
+
var DRAIN_PARK_LOG_FILE = join(BUILDER_CONFIG_DIR, "drain-park.log");
|
|
239
|
+
var DRAIN_HOLD_STATE_FILE = join(BUILDER_CONFIG_DIR, "drain-hold.json");
|
|
240
|
+
var MELT_SUCCESS_FILE = join(BUILDER_CONFIG_DIR, "melt-success.json");
|
|
241
|
+
|
|
242
|
+
// src/lib/config-dir.ts
|
|
243
|
+
import { chmodSync, mkdirSync, statSync } from "fs";
|
|
244
|
+
var CONFIG_DIR_MODE = 448;
|
|
245
|
+
function ensureConfigDir() {
|
|
246
|
+
ensureConfigSubdir(CONFIG_DIR);
|
|
247
|
+
}
|
|
248
|
+
function ensureConfigSubdir(path) {
|
|
249
|
+
mkdirSync(path, { recursive: true, mode: CONFIG_DIR_MODE });
|
|
250
|
+
}
|
|
251
|
+
function repairConfigDirMode() {
|
|
252
|
+
const base = {
|
|
253
|
+
path: CONFIG_DIR,
|
|
254
|
+
exists: false,
|
|
255
|
+
modeOctal: null,
|
|
256
|
+
repaired: false,
|
|
257
|
+
skippedNonPosix: process.platform === "win32"
|
|
258
|
+
};
|
|
259
|
+
if (base.skippedNonPosix) return base;
|
|
260
|
+
let mode;
|
|
261
|
+
try {
|
|
262
|
+
mode = statSync(CONFIG_DIR).mode & 511;
|
|
263
|
+
} catch (err) {
|
|
264
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
265
|
+
const missing = err.code === "ENOENT";
|
|
266
|
+
return missing ? base : { ...base, error: message };
|
|
267
|
+
}
|
|
268
|
+
if ((mode & 63) === 0) {
|
|
269
|
+
return { ...base, exists: true, modeOctal: toOctal(mode) };
|
|
270
|
+
}
|
|
271
|
+
try {
|
|
272
|
+
chmodSync(CONFIG_DIR, CONFIG_DIR_MODE);
|
|
273
|
+
} catch (err) {
|
|
274
|
+
return {
|
|
275
|
+
...base,
|
|
276
|
+
exists: true,
|
|
277
|
+
modeOctal: toOctal(mode),
|
|
278
|
+
error: err instanceof Error ? err.message : String(err)
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
return {
|
|
282
|
+
...base,
|
|
283
|
+
exists: true,
|
|
284
|
+
modeOctal: toOctal(CONFIG_DIR_MODE),
|
|
285
|
+
previousModeOctal: toOctal(mode),
|
|
286
|
+
repaired: true
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
function toOctal(mode) {
|
|
290
|
+
return "0" + mode.toString(8).padStart(3, "0");
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// src/lib/payment-rails/rpc.ts
|
|
294
|
+
function resolveRpcOverride(envValue, bundled) {
|
|
295
|
+
const override = envValue?.trim() ?? "";
|
|
296
|
+
return override.length > 0 ? { url: override, source: "override" } : { url: bundled, source: "bundled" };
|
|
297
|
+
}
|
|
298
|
+
function resolveRpcHttpTransportOptions(source, envValue) {
|
|
299
|
+
if (source !== "override" || !envValue?.trim()) return void 0;
|
|
300
|
+
let parsed;
|
|
301
|
+
try {
|
|
302
|
+
parsed = JSON.parse(envValue);
|
|
303
|
+
} catch {
|
|
304
|
+
throw new Error("RPC headers must be a JSON object of non-empty string values.");
|
|
305
|
+
}
|
|
306
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
|
|
307
|
+
throw new Error("RPC headers must be a JSON object of non-empty string values.");
|
|
308
|
+
const headers = {};
|
|
309
|
+
for (const [name, value] of Object.entries(parsed)) {
|
|
310
|
+
if (!/^[!#$%&'*+.^_`|~0-9A-Za-z-]+$/.test(name) || typeof value !== "string" || value.length === 0 || /[\r\n]/.test(value))
|
|
311
|
+
throw new Error("RPC headers must be a JSON object of non-empty string values.");
|
|
312
|
+
headers[name] = value;
|
|
313
|
+
}
|
|
314
|
+
if (Object.keys(headers).length === 0)
|
|
315
|
+
throw new Error("RPC headers must be a JSON object of non-empty string values.");
|
|
316
|
+
return { fetchOptions: { headers } };
|
|
317
|
+
}
|
|
318
|
+
|
|
207
319
|
export {
|
|
208
320
|
X402_DEFAULT_FACILITATOR,
|
|
209
321
|
X402_VERSION,
|
|
@@ -226,5 +338,34 @@ export {
|
|
|
226
338
|
encodePaymentRequiredHeader,
|
|
227
339
|
decodePaymentRequiredHeader,
|
|
228
340
|
exactEvmAuthorization,
|
|
229
|
-
encodeSettleResponseHeader
|
|
341
|
+
encodeSettleResponseHeader,
|
|
342
|
+
CONFIG_DIR,
|
|
343
|
+
CONFIG_FILE,
|
|
344
|
+
CONFIG_BACKUP_FILE,
|
|
345
|
+
CONFIG_AUDIT_FILE,
|
|
346
|
+
CONFIG_PINNED_FILE,
|
|
347
|
+
JOBS_FILE,
|
|
348
|
+
REQUEST_INTENTS_DIR,
|
|
349
|
+
FAVORITES_FILE,
|
|
350
|
+
MESSAGES_DIR,
|
|
351
|
+
RECEIPTS_FILE,
|
|
352
|
+
CREDITS_FILE,
|
|
353
|
+
X402_CHANNELS_FILE,
|
|
354
|
+
TEMPO_CHANNELS_FILE,
|
|
355
|
+
FEEDBACK_NUDGES_FILE,
|
|
356
|
+
RECOVERY_MNEMONIC_FILE,
|
|
357
|
+
WALLET_FILE,
|
|
358
|
+
AGENT_MNEMONIC_FILE,
|
|
359
|
+
IDENTITIES_FILE,
|
|
360
|
+
PAIRINGS_DIR,
|
|
361
|
+
BUDGET_FILE,
|
|
362
|
+
BTC_RATE_FILE,
|
|
363
|
+
BUILDER_CONFIG_DIR,
|
|
364
|
+
BUILDER_FILE,
|
|
365
|
+
BUILDER_KEY_FILE,
|
|
366
|
+
ensureConfigDir,
|
|
367
|
+
ensureConfigSubdir,
|
|
368
|
+
repairConfigDirMode,
|
|
369
|
+
resolveRpcOverride,
|
|
370
|
+
resolveRpcHttpTransportOptions
|
|
230
371
|
};
|