@haven_ai/signer 0.6.0-alpha.0 → 0.8.0-alpha.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.
- package/README.md +25 -10
- package/dist/cli.cjs +287 -30
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +288 -31
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +288 -30
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +46 -4
- package/dist/index.d.ts +46 -4
- package/dist/index.js +289 -32
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -43,8 +43,12 @@ every "rerun the connector" hint it prints names the channel THIS build was
|
|
|
43
43
|
published under, so a build installed from a non-production channel tells you
|
|
44
44
|
to reinstall from that same channel rather than sending you to production.
|
|
45
45
|
|
|
46
|
-
|
|
47
|
-
backend's expected-context version
|
|
46
|
+
That command SETS UP an agent. To update a signer that has fallen behind the
|
|
47
|
+
backend's expected-context version on a machine that is already set up, run
|
|
48
|
+
the connector doctor instead — `npx -y @haven_ai/connect@<channel> --doctor` —
|
|
49
|
+
and then the `--repair` line it prints; a bare re-run stops at "Missing
|
|
50
|
+
--setup" (#3412). Every update hint this signer prints names that form. To run
|
|
51
|
+
the signer directly:
|
|
48
52
|
|
|
49
53
|
```sh
|
|
50
54
|
HAVEN_DELEGATE_KEY=0x... npx @haven_ai/signer@alpha
|
|
@@ -61,7 +65,7 @@ It exposes four stdio MCP tools, all sign-only:
|
|
|
61
65
|
|
|
62
66
|
| Tool | Does | Emits |
|
|
63
67
|
|---|---|---|
|
|
64
|
-
| `haven_sign` | Sign one payment, or (#3329) one task-budget open/close. Preferred form is `{ payment_id }` alone for a payment,
|
|
68
|
+
| `haven_sign` | Sign one payment, or (#3329) one task-budget open/close, or (#3330, #3506) one sub-budget open/close. Preferred form is `{ payment_id }` alone for a payment, `{ task_budget_id }` alone for a task-budget open/close, or `{ sub_budget_id }` alone for a sub-budget this agent delegates (its own parent-child and the grant to another agent of the same account) — exactly one of the three; the signer fetches the exact payload itself. Signs only Haven-prepared payloads (#3272, #3281, #3329): a direct-payment `PackedUserOperation` from this signer's own delegate account whose only call redeems a delegation made to that account EITHER directly OR through a self-delegated task-budget child under it (the two-link `[task child, budget]` chain), or — against a Haven-signed context, which it records and binds — an EIP-3009 funding leg of that same shape paying this signer's own delegate EOA, or an erc7710 settlement child from this signer's own account; a self-delegated task-budget child `Delegation` from this signer's own account (`assertOwnTaskChild`); a sub-budget child `Delegation` from this signer's own account narrowing its budget for another agent of the same account (`assertOwnSubBudgetChild`); or a task-budget or sub-budget close — a `disableDelegation` UserOp from this signer's own account revoking that child (`assertOwnTaskBudgetCloseUserOp`, `assertOwnSubBudgetCloseUserOp`); other typed data is refused (`TYPED_DATA_NOT_ALLOWED`) | `{ signature }`, `{ signature, x402_binding }`, `{ signature, task_budget_id, purpose }`, or `{ signature, sub_budget_id, purpose }` (then `haven_submit` with that id) |
|
|
65
69
|
| `haven_sign_x402` | One-shot x402: funding signature **and** the merchant header in a single local call (`haven_sign` + `haven_x402_sign_header`). `{ payment_id }` alone is the preferred call | `{ signature, x402_binding, payment_header, accepted }` |
|
|
66
70
|
| `haven_x402_sign_header` | Build + sign the EIP-3009 merchant payment header, only when the fresh merchant `payment_required` matches the recorded `x402_binding` | `{ payment_header, accepted }` |
|
|
67
71
|
| `haven_sign_sweep_delegate` | Sign a Haven-prepared gasless EIP-3009 sweep that recovers stranded funds from the delegate wallet back to your own account. Never broadcasts | `{ signature }` |
|
|
@@ -70,8 +74,9 @@ The `initialize` handshake advertises which binding versions this signer
|
|
|
70
74
|
understands, under `capabilities.experimental['haven/signer-compatibility']`
|
|
71
75
|
and in the MCP `instructions` string. Both are **derived** from
|
|
72
76
|
`SUPPORTED_X402_EXPECTED_VERSIONS` / `SUPPORTED_SWEEP_BINDING_VERSIONS` in
|
|
73
|
-
`src/core.ts` and `SUPPORTED_DIRECT_SIGN_CONTEXT_VERSIONS`
|
|
74
|
-
`
|
|
77
|
+
`src/core.ts` and `SUPPORTED_DIRECT_SIGN_CONTEXT_VERSIONS` (#3271),
|
|
78
|
+
`SUPPORTED_TASK_SIGN_CONTEXT_VERSIONS` (#3329) and
|
|
79
|
+
`SUPPORTED_SUB_BUDGET_SIGN_CONTEXT_VERSIONS` (#3506) in `src/sign-context.ts` — the same constants the signing path enforces — so this README
|
|
75
80
|
deliberately does not restate the numbers. Read them from the handshake, or
|
|
76
81
|
from those constants.
|
|
77
82
|
|
|
@@ -323,8 +328,14 @@ table.** When the x402 fetch answers `SIGN_CONTEXT_REFUSED` with
|
|
|
323
328
|
`GET /payments/:id/sign-context` instead, same auth header, timeout and
|
|
324
329
|
refusal structuring. That second fetch's own refusals reuse the codes in the
|
|
325
330
|
table below with direct-payment remedies: no quote to re-run, and the
|
|
326
|
-
`typed_data_b64` relay
|
|
327
|
-
|
|
331
|
+
`typed_data_b64` relay — reached by re-running whichever of `haven_send` /
|
|
332
|
+
`haven_pay` was called, with the SAME `idempotency_key` (echoed on its
|
|
333
|
+
result) plus `include_signing_payload: true` (#3495; superseding #3277's
|
|
334
|
+
"the relay fields stay") — is the fallback, EXCEPT the direct fetch's 404
|
|
335
|
+
(the table row below): that message names BOTH routes, since a 404 there is
|
|
336
|
+
ambiguous with a backend old enough to predate #3495 too, whose result
|
|
337
|
+
already carries the pair unconditionally with no opt-in to re-run.
|
|
338
|
+
A 409 `sign_context_unavailable` from the direct route too (an
|
|
328
339
|
x402 row the x402 route could not serve, or a direct row with no stored
|
|
329
340
|
signing payload) surfaces the x402 route's own refusal instead. `haven_sign_x402` never takes this branch: a direct payment
|
|
330
341
|
carries no x402 context to fund a merchant retry with, so it surfaces the
|
|
@@ -337,7 +348,7 @@ carries no x402 context to fund a merchant retry with, so it surfaces the
|
|
|
337
348
|
| `SIGN_CONTEXT_MALFORMED` | The response body was missing `sign_data.typed_data` / `x402_expected` (a pre-#1263 backend), or — on the direct-payment fetch — an unsupported `direct_sign_context_version` or a `signature_scheme` other than `eip712_userop` | `stop_and_tell_user` | `typed_data_b64` | — |
|
|
338
349
|
| `SIGN_CONTEXT_REFUSED` (x402: 410 or `expired`; direct: `expired` only) | x402: the quote's window closed. Direct: the payment's window closed — call `haven_send` / `haven_pay` again with the same `idempotency_key` | `payment_window_expired` | — | x402 only: `retry_with_new_quote: true`; both: `http_status`, and `backend_error_code: 'expired'` when the backend sent one |
|
|
339
350
|
| `SIGN_CONTEXT_REFUSED` (404, direct fetch) | The `payment_id` is not this agent's, or the backend predates #3271 and has no direct route | `stop_and_tell_user` | `typed_data_b64` | `http_status`, `backend_error_code` |
|
|
340
|
-
| `SIGN_CONTEXT_REFUSED` (426 `client_outdated`, #3303) | This signer is below the minimum version the Haven deployment has set (only when one is set). Nothing is signed; the prepared payment is left unsigned.
|
|
351
|
+
| `SIGN_CONTEXT_REFUSED` (426 `client_outdated`, #3303) | This signer is below the minimum version the Haven deployment has set (only when one is set). Nothing is signed; the prepared payment is left unsigned. Run `client_update.upgrade_command` and the repair line it prints, restart the agent runtime, then retry the same call | `stop_and_tell_user` | — | `http_status: 426`, `backend_error_code: 'client_outdated'`, `client_update` (the update command as data); carries `next_tool_omitted_reason`, not the status read |
|
|
341
352
|
| `SIGN_CONTEXT_REFUSED` (other) | Unknown `payment_id` (404, x402 fetch), `already_executed` / `not_signable` (409), a bare 410 retired-rail tombstone (direct fetch) — or `sign_context_unavailable` (409): always on `haven_sign_x402`; on `haven_sign` only when neither route can serve the row | `stop_and_tell_user` | — | `http_status`, `backend_error_code` |
|
|
342
353
|
| `USEROP_BINDING_MISMATCH` | A direct payment's `PackedUserOperation` typed data (from the direct fetch, or a tool argument) does not recompute to its own `payload_hash`, or a fetched direct context is not a `PackedUserOperation` — see [Two ways to use it](#two-ways-to-use-it) above | `stop_and_tell_user` | — | no `http_status` — this is a local recomputation, not a backend refusal |
|
|
343
354
|
| `TYPED_DATA_NOT_ALLOWED` | Typed data without an x402 context that is not a bound direct-payment `PackedUserOperation` (#3272): wrong chain, not this signer's own account, a call other than `execute` → DelegationManager → `redeemDelegations`, or a redemption whose delegations are empty, not made to this account, or in a non-default mode — see the allowlist paragraph above. Reachable by `payment_id` when Haven serves such a payload | `stop_and_tell_user` | — | no `http_status` — a local shape check, not a backend refusal |
|
|
@@ -349,8 +360,12 @@ no direct route, #3271). For x402 it is **not** in the default quote result
|
|
|
349
360
|
since #1272: obtain it by re-running the SAME quote tool with the SAME
|
|
350
361
|
`idempotency_key` plus `include_signing_payload: true`, then pass
|
|
351
362
|
`typed_data_b64` (plus `payload_hash` / `x402_expected`) instead of
|
|
352
|
-
`payment_id`. A direct payment's `haven_send` / `haven_pay` result
|
|
353
|
-
|
|
363
|
+
`payment_id`. A direct payment's `haven_send` / `haven_pay` result is compact
|
|
364
|
+
by default too (#3495) — `payload_hash` is on that result unconditionally (as
|
|
365
|
+
is `idempotency_key`, generated fresh when the caller passed none and always
|
|
366
|
+
echoed); only `typed_data` / `typed_data_b64` are withheld, reached the same
|
|
367
|
+
way — a `include_signing_payload: true` re-run on the SAME `idempotency_key`.
|
|
368
|
+
Any other backend REFUSAL
|
|
354
369
|
carries no fallback: an expired, executed or unsignable intent cannot be
|
|
355
370
|
rescued by re-signing its bytes — an expired x402 one is re-quoted (the same
|
|
356
371
|
`payment_window_expired` + `retry_with_new_quote` the signer emits for
|
package/dist/cli.cjs
CHANGED
|
@@ -14,7 +14,14 @@ var stdio_js = require('@modelcontextprotocol/sdk/server/stdio.js');
|
|
|
14
14
|
var accounts = require('viem/accounts');
|
|
15
15
|
|
|
16
16
|
var SIGNER_HOSTED_HANDOFF_SHAPES = {
|
|
17
|
-
haven_get_payment_status: { payment_id: zod.z.string().min(1) }
|
|
17
|
+
haven_get_payment_status: { payment_id: zod.z.string().min(1) },
|
|
18
|
+
// #3506: `haven_sign` hands a signed budget row to the hosted `haven_submit`
|
|
19
|
+
// (`signTaskBudget` / `signSubBudget` in tools.ts emit exactly these
|
|
20
|
+
// `next_arguments`). One hosted tool, two argument shapes, so each shape is
|
|
21
|
+
// its own entry keyed `<hosted tool>#<variant>`; the parity test reads the
|
|
22
|
+
// part before `#` as the hosted tool it checks the entry against.
|
|
23
|
+
"haven_submit#task_budget": { task_budget_id: zod.z.string().min(1), signature: zod.z.string().regex(/^0x[0-9a-fA-F]+$/) },
|
|
24
|
+
"haven_submit#sub_budget": { sub_budget_id: zod.z.string().min(1), signature: zod.z.string().regex(/^0x[0-9a-fA-F]+$/) }
|
|
18
25
|
};
|
|
19
26
|
function target(shape) {
|
|
20
27
|
const schema = zod.z.object(shape).strict();
|
|
@@ -161,7 +168,8 @@ function stableStringify(value) {
|
|
|
161
168
|
const object = value;
|
|
162
169
|
return `{${Object.keys(object).sort().map((key) => `${JSON.stringify(key)}:${stableStringify(object[key])}`).join(",")}}`;
|
|
163
170
|
}
|
|
164
|
-
var DIRECT_RELAY_FALLBACK = "call haven_sign with payload_hash and typed_data_b64 from
|
|
171
|
+
var DIRECT_RELAY_FALLBACK = "re-run whichever of haven_send / haven_pay you called, with the SAME idempotency_key (echoed on its result) plus include_signing_payload: true, then call haven_sign with payload_hash and typed_data_b64 from THAT re-run's result, passed through unchanged";
|
|
172
|
+
var DIRECT_404_FALLBACK = "either the payment_id is not from this agent\u2019s own POST /payments call, or this Haven backend predates #3271 (which also predates #3495, so its result already carries payload_hash and typed_data_b64 unconditionally): pass those fields from the ORIGINAL haven_send / haven_pay result, unchanged, if it already carries them \u2014 or, on a newer server, re-run haven_send / haven_pay with the SAME idempotency_key plus include_signing_payload: true and pass them from THAT result instead";
|
|
165
173
|
var HavenSignContextError = class extends edge.HavenSigningError {
|
|
166
174
|
/**
|
|
167
175
|
* #3001 / #3010 review: the recovery path, when signing OTHER bytes is
|
|
@@ -214,7 +222,7 @@ var HavenSignContextError = class extends edge.HavenSigningError {
|
|
|
214
222
|
/**
|
|
215
223
|
* #3303: on a 426 `client_outdated` refusal, the backend's update hint —
|
|
216
224
|
* this signer is below the minimum the deployment set, and
|
|
217
|
-
* `client_update.upgrade_command`
|
|
225
|
+
* `client_update.upgrade_command` starts the update (#3412: for the signer, the doctor, then its repair line).
|
|
218
226
|
*/
|
|
219
227
|
client_update;
|
|
220
228
|
constructor(message, code, refusal, paymentId, flow = "x402") {
|
|
@@ -231,7 +239,7 @@ var HavenSignContextError = class extends edge.HavenSigningError {
|
|
|
231
239
|
step = signerRefusalStep({
|
|
232
240
|
nextAction: edge.AgentPaymentNextAction.StopAndTellUser,
|
|
233
241
|
nextTool: null,
|
|
234
|
-
nextToolOmittedReason: `this signer is below the minimum version Haven accepts \u2014 tell the user to run ${refusal.clientUpdate?.upgrade_command ??
|
|
242
|
+
nextToolOmittedReason: `this signer is below the minimum version Haven accepts \u2014 tell the user to run ${refusal.clientUpdate?.upgrade_command ?? edge.connectorUpgradeCommand()} and the repair line it prints, restart the agent runtime, then retry the same haven_sign / haven_sign_x402 call`
|
|
235
243
|
});
|
|
236
244
|
} else if (flow === "direct" && refusal?.httpStatus === 404) {
|
|
237
245
|
this.fallback = "typed_data_b64";
|
|
@@ -239,7 +247,7 @@ var HavenSignContextError = class extends edge.HavenSigningError {
|
|
|
239
247
|
step = signerRefusalStep({
|
|
240
248
|
nextAction: edge.AgentPaymentNextAction.StopAndTellUser,
|
|
241
249
|
nextTool: null,
|
|
242
|
-
nextToolOmittedReason:
|
|
250
|
+
nextToolOmittedReason: DIRECT_404_FALLBACK
|
|
243
251
|
});
|
|
244
252
|
} else if (flow === "direct" && refusal?.errorCode === "expired") {
|
|
245
253
|
this.next_action = edge.AgentPaymentNextAction.PaymentWindowExpired;
|
|
@@ -394,7 +402,7 @@ async function fetchDirectSignContext(identity, paymentId, fetchImpl = fetch, ti
|
|
|
394
402
|
} catch (err) {
|
|
395
403
|
const timedOut = err instanceof Error && (err.name === "TimeoutError" || err.name === "AbortError");
|
|
396
404
|
throw new HavenSignContextError(
|
|
397
|
-
timedOut ? `Haven did not answer the direct-payment signing-context fetch for ${paymentId} within ${timeoutMs} ms. Retry, or pass typed_data_b64 from
|
|
405
|
+
timedOut ? `Haven did not answer the direct-payment signing-context fetch for ${paymentId} within ${timeoutMs} ms. Retry, or re-run haven_send / haven_pay with the same idempotency_key plus include_signing_payload: true and pass typed_data_b64 from that result instead.` : `Could not reach Haven to fetch the direct-payment signing context for ${paymentId}: ${err instanceof Error ? err.message : String(err)}. Retry, or re-run haven_send / haven_pay with the same idempotency_key plus include_signing_payload: true and pass typed_data_b64 from that result instead.`,
|
|
398
406
|
timedOut ? "SIGN_CONTEXT_TIMEOUT" : "SIGN_CONTEXT_UNREACHABLE",
|
|
399
407
|
void 0,
|
|
400
408
|
paymentId,
|
|
@@ -407,7 +415,7 @@ async function fetchDirectSignContext(identity, paymentId, fetchImpl = fetch, ti
|
|
|
407
415
|
} catch (err) {
|
|
408
416
|
if (err instanceof Error && (err.name === "TimeoutError" || err.name === "AbortError")) {
|
|
409
417
|
throw new HavenSignContextError(
|
|
410
|
-
`Haven did not finish sending the direct-payment signing context for ${paymentId} within ${timeoutMs} ms. Retry, or pass typed_data_b64 from
|
|
418
|
+
`Haven did not finish sending the direct-payment signing context for ${paymentId} within ${timeoutMs} ms. Retry, or re-run haven_send / haven_pay with the same idempotency_key plus include_signing_payload: true and pass typed_data_b64 from that result instead.`,
|
|
411
419
|
"SIGN_CONTEXT_TIMEOUT",
|
|
412
420
|
void 0,
|
|
413
421
|
paymentId,
|
|
@@ -419,7 +427,7 @@ async function fetchDirectSignContext(identity, paymentId, fetchImpl = fetch, ti
|
|
|
419
427
|
if (!response.ok) {
|
|
420
428
|
const detail = typeof body.error === "string" ? body.error : `HTTP ${response.status}`;
|
|
421
429
|
throw new HavenSignContextError(
|
|
422
|
-
`Haven refused the direct-payment signing-context fetch for ${paymentId}: ${detail}` + (response.status === 404 ? " \u2014 either the payment_id is not from this agent\u2019s own POST /payments call, or this Haven backend predates #3271. Pass payload_hash and typed_data_b64 from the
|
|
430
|
+
`Haven refused the direct-payment signing-context fetch for ${paymentId}: ${detail}` + (response.status === 404 ? " \u2014 either the payment_id is not from this agent\u2019s own POST /payments call, or this Haven backend predates #3271 (which also predates #3495, so its haven_send / haven_pay result already carries payload_hash and typed_data_b64 unconditionally). Pass payload_hash and typed_data_b64, unchanged, from the original result \u2014 or, on a newer server, from a same-key re-run with include_signing_payload: true." : body.error_code === "expired" ? " The payment window has expired; call haven_send / haven_pay again with the same idempotency_key." : ""),
|
|
423
431
|
"SIGN_CONTEXT_REFUSED",
|
|
424
432
|
refusalOf(response.status, body),
|
|
425
433
|
paymentId,
|
|
@@ -430,7 +438,7 @@ async function fetchDirectSignContext(identity, paymentId, fetchImpl = fetch, ti
|
|
|
430
438
|
const version = body.direct_sign_context_version;
|
|
431
439
|
if (!signData || typeof signData.hash !== "string" || !signData.typed_data || typeof signData.typed_data !== "object" || signData.signature_scheme !== "eip712_userop" || typeof version !== "number" || !SUPPORTED_DIRECT_SIGN_CONTEXT_VERSIONS.includes(version)) {
|
|
432
440
|
throw new HavenSignContextError(
|
|
433
|
-
typeof version === "number" && !SUPPORTED_DIRECT_SIGN_CONTEXT_VERSIONS.includes(version) ? `The Haven direct-payment sign-context response is version ${version}, which this signer does not support (supported: ${SUPPORTED_DIRECT_SIGN_CONTEXT_VERSIONS.join(", ")}). Update @haven_ai/signer.` : "The Haven direct-payment sign-context response is missing sign_data.typed_data, or its signature_scheme is not 'eip712_userop' \u2014 the backend may predate #3271.
|
|
441
|
+
typeof version === "number" && !SUPPORTED_DIRECT_SIGN_CONTEXT_VERSIONS.includes(version) ? `The Haven direct-payment sign-context response is version ${version}, which this signer does not support (supported: ${SUPPORTED_DIRECT_SIGN_CONTEXT_VERSIONS.join(", ")}). Update @haven_ai/signer.` : "The Haven direct-payment sign-context response is missing sign_data.typed_data, or its signature_scheme is not 'eip712_userop' \u2014 the backend may predate #3271. Re-run haven_send / haven_pay with the same idempotency_key plus include_signing_payload: true and pass typed_data_b64 from that result instead.",
|
|
434
442
|
"SIGN_CONTEXT_MALFORMED",
|
|
435
443
|
void 0,
|
|
436
444
|
paymentId,
|
|
@@ -446,7 +454,120 @@ async function fetchDirectSignContext(identity, paymentId, fetchImpl = fetch, ti
|
|
|
446
454
|
clientUpdate: edge.readClientUpdate(body.client_update)
|
|
447
455
|
};
|
|
448
456
|
}
|
|
449
|
-
var SUPPORTED_TASK_SIGN_CONTEXT_VERSIONS = [
|
|
457
|
+
var SUPPORTED_TASK_SIGN_CONTEXT_VERSIONS = [edge.TASK_SIGN_CONTEXT_VERSION];
|
|
458
|
+
var SUPPORTED_SUB_BUDGET_SIGN_CONTEXT_VERSIONS = [1];
|
|
459
|
+
async function fetchSubBudgetSignContext(identity, subBudgetId, fetchImpl = fetch, timeoutMs = SIGN_CONTEXT_TIMEOUT_MS, clientIdentity) {
|
|
460
|
+
let response;
|
|
461
|
+
try {
|
|
462
|
+
response = await fetchImpl(
|
|
463
|
+
`${identity.apiUrl}/sub-budgets/${encodeURIComponent(subBudgetId)}/sign-context`,
|
|
464
|
+
{
|
|
465
|
+
headers: signContextHeaders(identity, clientIdentity),
|
|
466
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
467
|
+
}
|
|
468
|
+
);
|
|
469
|
+
} catch (err) {
|
|
470
|
+
const timedOut = err instanceof Error && (err.name === "TimeoutError" || err.name === "AbortError");
|
|
471
|
+
throw new HavenSignContextError(
|
|
472
|
+
timedOut ? `Haven did not answer the sub-budget signing-context fetch for ${subBudgetId} within ${timeoutMs} ms. Retry.` : `Could not reach Haven to fetch the sub-budget signing context for ${subBudgetId}: ${err instanceof Error ? err.message : String(err)}.`,
|
|
473
|
+
timedOut ? "SIGN_CONTEXT_TIMEOUT" : "SIGN_CONTEXT_UNREACHABLE",
|
|
474
|
+
void 0,
|
|
475
|
+
subBudgetId,
|
|
476
|
+
"task"
|
|
477
|
+
);
|
|
478
|
+
}
|
|
479
|
+
let body;
|
|
480
|
+
try {
|
|
481
|
+
body = await response.json();
|
|
482
|
+
} catch (err) {
|
|
483
|
+
if (err instanceof Error && (err.name === "TimeoutError" || err.name === "AbortError")) {
|
|
484
|
+
throw new HavenSignContextError(
|
|
485
|
+
`Haven did not finish sending the sub-budget signing context for ${subBudgetId} within ${timeoutMs} ms. Retry.`,
|
|
486
|
+
"SIGN_CONTEXT_TIMEOUT",
|
|
487
|
+
void 0,
|
|
488
|
+
subBudgetId,
|
|
489
|
+
"task"
|
|
490
|
+
);
|
|
491
|
+
}
|
|
492
|
+
body = {};
|
|
493
|
+
}
|
|
494
|
+
if (!response.ok) {
|
|
495
|
+
const detail = typeof body.error === "string" ? body.error : `HTTP ${response.status}`;
|
|
496
|
+
throw new HavenSignContextError(
|
|
497
|
+
`Haven refused the sub-budget signing-context fetch for ${subBudgetId}: ${detail}`,
|
|
498
|
+
"SIGN_CONTEXT_REFUSED",
|
|
499
|
+
refusalOf(response.status, body),
|
|
500
|
+
subBudgetId,
|
|
501
|
+
"task"
|
|
502
|
+
);
|
|
503
|
+
}
|
|
504
|
+
const purpose = body.purpose;
|
|
505
|
+
const typedData = body.typed_data;
|
|
506
|
+
const version = body.sub_budget_sign_context_version;
|
|
507
|
+
const expected = body.expected;
|
|
508
|
+
const malformed = (detail) => {
|
|
509
|
+
throw new HavenSignContextError(
|
|
510
|
+
`The Haven sub-budget sign-context response for ${subBudgetId} is malformed: ${detail}.`,
|
|
511
|
+
"SIGN_CONTEXT_MALFORMED",
|
|
512
|
+
void 0,
|
|
513
|
+
subBudgetId,
|
|
514
|
+
"task"
|
|
515
|
+
);
|
|
516
|
+
};
|
|
517
|
+
if (typeof version !== "number" || !SUPPORTED_SUB_BUDGET_SIGN_CONTEXT_VERSIONS.includes(version)) {
|
|
518
|
+
throw new HavenSignContextError(
|
|
519
|
+
`The Haven sub-budget sign-context response is version ${String(version)}, which this signer does not support (supported: ${SUPPORTED_SUB_BUDGET_SIGN_CONTEXT_VERSIONS.join(", ")}). Update @haven_ai/signer.`,
|
|
520
|
+
"SIGN_CONTEXT_MALFORMED",
|
|
521
|
+
void 0,
|
|
522
|
+
subBudgetId,
|
|
523
|
+
"task"
|
|
524
|
+
);
|
|
525
|
+
}
|
|
526
|
+
if (!typedData || typeof typedData !== "object") return malformed("missing typed_data");
|
|
527
|
+
if (!expected || typeof expected !== "object") return malformed("missing expected");
|
|
528
|
+
const e = expected;
|
|
529
|
+
if (purpose === "open") {
|
|
530
|
+
if (typeof e.delegate_account !== "string" || typeof e.chain_id !== "number" || typeof e.token_address !== "string" || typeof e.period_amount_atomic !== "string" || typeof e.period_duration_seconds !== "number" || typeof e.start_date !== "number" || e.recipient_address !== null && typeof e.recipient_address !== "string" || typeof e.expires_at !== "number" || typeof e.parent_delegation_hash !== "string" || typeof e.child_delegate_account !== "string") {
|
|
531
|
+
return malformed("expected is missing a required field for purpose open");
|
|
532
|
+
}
|
|
533
|
+
return {
|
|
534
|
+
purpose: "open",
|
|
535
|
+
subBudgetId: String(body.sub_budget_id ?? subBudgetId),
|
|
536
|
+
typedData,
|
|
537
|
+
expected: {
|
|
538
|
+
delegateAccount: e.delegate_account,
|
|
539
|
+
chainId: e.chain_id,
|
|
540
|
+
tokenAddress: e.token_address,
|
|
541
|
+
periodAmountAtomic: e.period_amount_atomic,
|
|
542
|
+
periodDurationSeconds: e.period_duration_seconds,
|
|
543
|
+
startDate: e.start_date,
|
|
544
|
+
recipientAddress: e.recipient_address ?? null,
|
|
545
|
+
expiresAt: e.expires_at,
|
|
546
|
+
parentDelegationHash: e.parent_delegation_hash,
|
|
547
|
+
childDelegateAccount: e.child_delegate_account
|
|
548
|
+
}
|
|
549
|
+
};
|
|
550
|
+
}
|
|
551
|
+
if (purpose === "close") {
|
|
552
|
+
const userOpHash = body.user_op_hash;
|
|
553
|
+
if (typeof userOpHash !== "string") return malformed("missing user_op_hash for purpose close");
|
|
554
|
+
if (typeof e.delegate_account !== "string" || typeof e.chain_id !== "number" || typeof e.delegation_hash !== "string") {
|
|
555
|
+
return malformed("expected is missing a required field for purpose close");
|
|
556
|
+
}
|
|
557
|
+
return {
|
|
558
|
+
purpose: "close",
|
|
559
|
+
subBudgetId: String(body.sub_budget_id ?? subBudgetId),
|
|
560
|
+
typedData,
|
|
561
|
+
userOpHash,
|
|
562
|
+
expected: {
|
|
563
|
+
delegateAccount: e.delegate_account,
|
|
564
|
+
chainId: e.chain_id,
|
|
565
|
+
delegationHash: e.delegation_hash
|
|
566
|
+
}
|
|
567
|
+
};
|
|
568
|
+
}
|
|
569
|
+
return malformed(`unknown purpose '${String(purpose)}'`);
|
|
570
|
+
}
|
|
450
571
|
async function fetchTaskBudgetSignContext(identity, taskBudgetId, fetchImpl = fetch, timeoutMs = SIGN_CONTEXT_TIMEOUT_MS, clientIdentity) {
|
|
451
572
|
let response;
|
|
452
573
|
try {
|
|
@@ -558,6 +679,9 @@ async function fetchTaskBudgetSignContext(identity, taskBudgetId, fetchImpl = fe
|
|
|
558
679
|
}
|
|
559
680
|
|
|
560
681
|
// src/tools.ts
|
|
682
|
+
var SIGNER_NAME = "@haven_ai/signer";
|
|
683
|
+
var SIGNER_VERSION = "0.8.0-alpha.0";
|
|
684
|
+
var UNSUPPORTED_ARGUMENT_MARKER = "HavenSignerUnsupportedArgument:";
|
|
561
685
|
var USEROP_BINDING_MISMATCH = "USEROP_BINDING_MISMATCH";
|
|
562
686
|
var HavenUserOpBindingRefusedError = class extends edge.HavenSigningError {
|
|
563
687
|
next_action = edge.AgentPaymentNextAction.StopAndTellUser;
|
|
@@ -570,7 +694,7 @@ var HavenUserOpBindingRefusedError = class extends edge.HavenSigningError {
|
|
|
570
694
|
this.step = signerRefusalStep({
|
|
571
695
|
nextAction: edge.AgentPaymentNextAction.StopAndTellUser,
|
|
572
696
|
nextTool: null,
|
|
573
|
-
nextToolOmittedReason: "call haven_sign again with payment_id alone (preferred), or with typed_data copied unchanged from the payment result"
|
|
697
|
+
nextToolOmittedReason: "call haven_sign again with payment_id alone (preferred), or with typed_data copied unchanged from the payment result (or, on a compact direct-payment result, from a same-key re-run with include_signing_payload: true)"
|
|
574
698
|
});
|
|
575
699
|
}
|
|
576
700
|
};
|
|
@@ -685,7 +809,13 @@ var toolSchemas = {
|
|
|
685
809
|
// Mutually exclusive with payment_id / payload_hash (checked in the
|
|
686
810
|
// handler, since Zod's object-level refine on a raw shape used by
|
|
687
811
|
// several call sites is not the shape this schema is composed as).
|
|
688
|
-
task_budget_id: v3.z.string().min(1).optional()
|
|
812
|
+
task_budget_id: v3.z.string().min(1).optional(),
|
|
813
|
+
// #3330: sign a sub-budget's own child delegation or its early-close
|
|
814
|
+
// UserOp — same fetch-verify-sign channel as task_budget_id, against
|
|
815
|
+
// `GET /sub-budgets/:id/sign-context`. Mutually exclusive with
|
|
816
|
+
// payment_id / payload_hash AND with task_budget_id (checked in the
|
|
817
|
+
// handler).
|
|
818
|
+
sub_budget_id: v3.z.string().min(1).optional()
|
|
689
819
|
},
|
|
690
820
|
haven_x402_sign_header: {
|
|
691
821
|
// The parsed HTTP 402 PaymentRequired from the merchant. Typed as an object
|
|
@@ -966,9 +1096,76 @@ function createToolHandlers(signer, options = {}) {
|
|
|
966
1096
|
next_arguments: { task_budget_id: ctx.taskBudgetId, signature }
|
|
967
1097
|
};
|
|
968
1098
|
}
|
|
1099
|
+
async function signSubBudget(subBudgetId) {
|
|
1100
|
+
const identity = await options.signContext?.loadIdentity() ?? null;
|
|
1101
|
+
if (!identity) {
|
|
1102
|
+
throw new edge.HavenSigningError(
|
|
1103
|
+
`sub_budget_id signing needs the agent identity (identity.json next to the signer credentials), which this signer could not load. Re-run \`${edge.connectorRerunCommand()}\` to restore it.`
|
|
1104
|
+
);
|
|
1105
|
+
}
|
|
1106
|
+
const ctx = await fetchSubBudgetSignContext(
|
|
1107
|
+
identity,
|
|
1108
|
+
subBudgetId,
|
|
1109
|
+
options.signContext?.fetchImpl,
|
|
1110
|
+
void 0,
|
|
1111
|
+
options.signContext?.clientIdentity
|
|
1112
|
+
);
|
|
1113
|
+
let signature;
|
|
1114
|
+
if (ctx.purpose === "open") {
|
|
1115
|
+
try {
|
|
1116
|
+
edge.assertOwnSubBudgetChild(
|
|
1117
|
+
ctx.typedData,
|
|
1118
|
+
{
|
|
1119
|
+
chainId: ctx.expected.chainId,
|
|
1120
|
+
tokenAddress: ctx.expected.tokenAddress,
|
|
1121
|
+
periodAmountAtomic: ctx.expected.periodAmountAtomic,
|
|
1122
|
+
periodDurationSeconds: ctx.expected.periodDurationSeconds,
|
|
1123
|
+
startDate: ctx.expected.startDate,
|
|
1124
|
+
recipientAddress: ctx.expected.recipientAddress,
|
|
1125
|
+
expiresAt: ctx.expected.expiresAt,
|
|
1126
|
+
parentDelegationHash: ctx.expected.parentDelegationHash,
|
|
1127
|
+
delegateAccount: ctx.expected.delegateAccount,
|
|
1128
|
+
childDelegateAccount: ctx.expected.childDelegateAccount
|
|
1129
|
+
},
|
|
1130
|
+
signer.delegateAddress
|
|
1131
|
+
);
|
|
1132
|
+
} catch (err) {
|
|
1133
|
+
if (err instanceof edge.HavenTypedDataRefusedError) throw err;
|
|
1134
|
+
throw new edge.HavenTypedDataRefusedError(err instanceof Error ? err.message : String(err));
|
|
1135
|
+
}
|
|
1136
|
+
signature = await signer.signDelegationTypedData(ctx.typedData);
|
|
1137
|
+
} else {
|
|
1138
|
+
try {
|
|
1139
|
+
edge.assertUserOpTypedDataBinding(ctx.typedData, ctx.userOpHash);
|
|
1140
|
+
} catch (err) {
|
|
1141
|
+
if (err instanceof edge.HavenUserOpBindingError) throw new HavenUserOpBindingRefusedError(err.message);
|
|
1142
|
+
throw err;
|
|
1143
|
+
}
|
|
1144
|
+
try {
|
|
1145
|
+
edge.assertOwnSubBudgetCloseUserOp(
|
|
1146
|
+
ctx.typedData,
|
|
1147
|
+
{ delegationHash: ctx.expected.delegationHash },
|
|
1148
|
+
signer.delegateAddress
|
|
1149
|
+
);
|
|
1150
|
+
} catch (err) {
|
|
1151
|
+
if (err instanceof edge.HavenTypedDataRefusedError) throw err;
|
|
1152
|
+
throw new edge.HavenTypedDataRefusedError(err instanceof Error ? err.message : String(err));
|
|
1153
|
+
}
|
|
1154
|
+
signature = await signer.signDelegationTypedData(ctx.typedData);
|
|
1155
|
+
}
|
|
1156
|
+
await auditSigning("haven_sign", viem.hashTypedData(ctx.typedData));
|
|
1157
|
+
return {
|
|
1158
|
+
signature,
|
|
1159
|
+
sub_budget_id: ctx.subBudgetId,
|
|
1160
|
+
purpose: ctx.purpose,
|
|
1161
|
+
next_tool_name: "haven_submit",
|
|
1162
|
+
next_tool_server_role: "hosted",
|
|
1163
|
+
next_arguments: { sub_budget_id: ctx.subBudgetId, signature }
|
|
1164
|
+
};
|
|
1165
|
+
}
|
|
969
1166
|
return {
|
|
970
1167
|
haven_sign: async (input) => runTool(async () => {
|
|
971
|
-
const args =
|
|
1168
|
+
const args = parseStrictFor("haven_sign", coerceX402Expected(input));
|
|
972
1169
|
if (args.task_budget_id) {
|
|
973
1170
|
if (args.payment_id || args.payload_hash) {
|
|
974
1171
|
throw new v3.z.ZodError([
|
|
@@ -979,8 +1176,29 @@ function createToolHandlers(signer, options = {}) {
|
|
|
979
1176
|
}
|
|
980
1177
|
]);
|
|
981
1178
|
}
|
|
1179
|
+
if (args.sub_budget_id) {
|
|
1180
|
+
throw new v3.z.ZodError([
|
|
1181
|
+
{
|
|
1182
|
+
code: v3.z.ZodIssueCode.custom,
|
|
1183
|
+
path: ["sub_budget_id"],
|
|
1184
|
+
message: "task_budget_id is mutually exclusive with sub_budget_id \u2014 pass exactly one."
|
|
1185
|
+
}
|
|
1186
|
+
]);
|
|
1187
|
+
}
|
|
982
1188
|
return signTaskBudget(args.task_budget_id);
|
|
983
1189
|
}
|
|
1190
|
+
if (args.sub_budget_id) {
|
|
1191
|
+
if (args.payment_id || args.payload_hash) {
|
|
1192
|
+
throw new v3.z.ZodError([
|
|
1193
|
+
{
|
|
1194
|
+
code: v3.z.ZodIssueCode.custom,
|
|
1195
|
+
path: ["sub_budget_id"],
|
|
1196
|
+
message: "sub_budget_id is mutually exclusive with payment_id / payload_hash \u2014 pass exactly one."
|
|
1197
|
+
}
|
|
1198
|
+
]);
|
|
1199
|
+
}
|
|
1200
|
+
return signSubBudget(args.sub_budget_id);
|
|
1201
|
+
}
|
|
984
1202
|
const resolved = await resolveSignContext(args, { allowDirectFallback: true });
|
|
985
1203
|
const typedData = resolved?.ctx.typedData ?? resolveTypedData(args);
|
|
986
1204
|
const payloadHash = resolved?.ctx.payloadHash ?? args.payload_hash;
|
|
@@ -1023,7 +1241,7 @@ function createToolHandlers(signer, options = {}) {
|
|
|
1023
1241
|
return { signature: result.signature, x402_binding: result.x402Binding, ...clientUpdateField(resolved) };
|
|
1024
1242
|
}),
|
|
1025
1243
|
haven_x402_sign_header: async (input) => runTool(async () => {
|
|
1026
|
-
const args =
|
|
1244
|
+
const args = parseStrictFor("haven_x402_sign_header", coercePaymentRequired(input));
|
|
1027
1245
|
const result = await signer.buildX402PaymentHeader(
|
|
1028
1246
|
args.payment_required,
|
|
1029
1247
|
args.x402_binding
|
|
@@ -1035,7 +1253,7 @@ function createToolHandlers(signer, options = {}) {
|
|
|
1035
1253
|
return { payment_header: result.paymentHeader, accepted: result.accepted };
|
|
1036
1254
|
}),
|
|
1037
1255
|
haven_sign_x402: async (input) => runTool(async () => {
|
|
1038
|
-
const args =
|
|
1256
|
+
const args = parseStrictFor("haven_sign_x402", coerceX402Expected(coercePaymentRequired(input)));
|
|
1039
1257
|
const resolved = await resolveSignContext(args, { allowDirectFallback: false });
|
|
1040
1258
|
const fetched = resolved?.kind === "x402" ? resolved.ctx : void 0;
|
|
1041
1259
|
const payloadHash = fetched?.payloadHash ?? args.payload_hash;
|
|
@@ -1071,7 +1289,7 @@ function createToolHandlers(signer, options = {}) {
|
|
|
1071
1289
|
};
|
|
1072
1290
|
}),
|
|
1073
1291
|
haven_sign_sweep_delegate: async (input) => runTool(async () => {
|
|
1074
|
-
const args =
|
|
1292
|
+
const args = parseStrictFor("haven_sign_sweep_delegate", input);
|
|
1075
1293
|
const result = await signer.signSweepAuthorization({
|
|
1076
1294
|
authorization: args.authorization,
|
|
1077
1295
|
expectedAuth: args.expected_auth,
|
|
@@ -1104,8 +1322,18 @@ function createToolHandlers(signer, options = {}) {
|
|
|
1104
1322
|
}
|
|
1105
1323
|
}
|
|
1106
1324
|
}
|
|
1107
|
-
function
|
|
1108
|
-
|
|
1325
|
+
function parseStrictFor(name, input) {
|
|
1326
|
+
const result = v3.z.object(toolSchemas[name]).strict().safeParse(input ?? {});
|
|
1327
|
+
if (result.success) return result.data;
|
|
1328
|
+
const topLevelUnknown = result.error.issues.flatMap(
|
|
1329
|
+
(issue) => issue.code === "unrecognized_keys" && issue.path.length === 0 ? issue.keys : []
|
|
1330
|
+
);
|
|
1331
|
+
if (topLevelUnknown.length === 0) throw result.error;
|
|
1332
|
+
const unknown = [...new Set(topLevelUnknown)];
|
|
1333
|
+
const err = new Error(
|
|
1334
|
+
`${UNSUPPORTED_ARGUMENT_MARKER} ${name} does not accept ${unknown.map((k) => `"${k}"`).join(", ")} \u2014 this signer predates that argument form. Nothing was signed or fetched.`
|
|
1335
|
+
);
|
|
1336
|
+
throw err;
|
|
1109
1337
|
}
|
|
1110
1338
|
function coerceX402Expected(input) {
|
|
1111
1339
|
if (!input || typeof input !== "object") return input;
|
|
@@ -1144,6 +1372,25 @@ async function runTool(fn) {
|
|
|
1144
1372
|
}
|
|
1145
1373
|
}
|
|
1146
1374
|
function normalizeError(err) {
|
|
1375
|
+
if (err instanceof Error && err.message.startsWith(UNSUPPORTED_ARGUMENT_MARKER)) {
|
|
1376
|
+
const unknown = [...err.message.matchAll(/"([^"]+)"/g)].map((m) => m[1]);
|
|
1377
|
+
return {
|
|
1378
|
+
success: false,
|
|
1379
|
+
code: "UNSUPPORTED_ARGUMENT",
|
|
1380
|
+
message: err.message.replace(`${UNSUPPORTED_ARGUMENT_MARKER} `, ""),
|
|
1381
|
+
unknown_arguments: unknown,
|
|
1382
|
+
signer_version: SIGNER_VERSION,
|
|
1383
|
+
fallback: edge.connectorUpgradeCommand(),
|
|
1384
|
+
next_action: edge.AgentPaymentNextAction.StopAndTellUser,
|
|
1385
|
+
...nextStepWireFields(
|
|
1386
|
+
signerRefusalStep({
|
|
1387
|
+
nextAction: edge.AgentPaymentNextAction.StopAndTellUser,
|
|
1388
|
+
nextTool: null,
|
|
1389
|
+
nextToolOmittedReason: `update @haven_ai/signer: run ${edge.connectorUpgradeCommand()} and the repair line it prints, then repeat the same call`
|
|
1390
|
+
})
|
|
1391
|
+
)
|
|
1392
|
+
};
|
|
1393
|
+
}
|
|
1147
1394
|
if (err instanceof v3.z.ZodError) {
|
|
1148
1395
|
return {
|
|
1149
1396
|
success: false,
|
|
@@ -1165,7 +1412,9 @@ function normalizeError(err) {
|
|
|
1165
1412
|
...nextStepWireFields(signerRefusalStep({
|
|
1166
1413
|
nextAction: edge.AgentPaymentNextAction.StopAndTellUser,
|
|
1167
1414
|
nextTool: null,
|
|
1168
|
-
|
|
1415
|
+
// #3412: the upgrade command, not "re-run the connector" — a bare re-run
|
|
1416
|
+
// stops at "Missing --setup" on an existing install.
|
|
1417
|
+
nextToolOmittedReason: `update @haven_ai/signer: run ${edge.connectorUpgradeCommand()} and the repair line it prints, then repeat the same call`
|
|
1169
1418
|
}))
|
|
1170
1419
|
};
|
|
1171
1420
|
}
|
|
@@ -1251,7 +1500,7 @@ function normalizeError(err) {
|
|
|
1251
1500
|
|
|
1252
1501
|
// src/consent.ts
|
|
1253
1502
|
var toolSummaries = {
|
|
1254
|
-
haven_sign: "signs a Haven-prepared payment, task-budget
|
|
1503
|
+
haven_sign: "signs a Haven-prepared payment, task-budget or sub-budget open or close (a UserOp from your own account redeeming a delegation made to it, directly or via a task-budget child; a sub-budget child narrowing your budget for another agent of yours; or an x402 funding intent against a Haven-signed context)",
|
|
1255
1504
|
haven_x402_sign_header: "builds and signs the EIP-3009 merchant payment header for an x402 payment",
|
|
1256
1505
|
haven_sign_x402: "the two above in one call: funding payload + merchant header",
|
|
1257
1506
|
haven_sign_sweep_delegate: "signs a Haven-prepared gasless sweep of stranded funds back to your own account"
|
|
@@ -1682,7 +1931,7 @@ function assertSupportedBindingVersion(received, supported, context) {
|
|
|
1682
1931
|
const highest = Math.max(...supported);
|
|
1683
1932
|
const outOfDate = received > highest;
|
|
1684
1933
|
const code = context === "x402 expected context" ? edge.SignerRefusalCode.UnsupportedExpectedContextVersion : edge.SignerRefusalCode.UnsupportedSweepBindingVersion;
|
|
1685
|
-
const ceiling = outOfDate ? `This signer is out of date: it supports ${context} versions up to ${highest}, and Haven sent version ${received}. Update @haven_ai/signer \u2014
|
|
1934
|
+
const ceiling = outOfDate ? `This signer is out of date: it supports ${context} versions up to ${highest}, and Haven sent version ${received}. Update @haven_ai/signer \u2014 run \`${edge.connectorUpgradeCommand()}\`, then the repair line it prints, which reinstalls the pinned MCP runtime.` : `Unsupported ${context} version ${received}: this signer supports ${supported.join(", ")}.`;
|
|
1686
1935
|
const fallback = outOfDate ? edge.SIGNER_UPDATE_FALLBACK : `This ${context} version (${received}) is older than what this signer enforces (${supported.join(", ")}) \u2014 updating @haven_ai/signer will not restore it. Nothing was signed or spent; stop and tell the user rather than retrying.`;
|
|
1687
1936
|
throw new edge.HavenUnsupportedSignerVersionError(
|
|
1688
1937
|
`${ceiling} Nothing was signed. Do not rewrite the version field to a supported value: it is part of the Haven-signed binding message, so changing it invalidates the signature and would misrepresent what Haven declared.`,
|
|
@@ -1784,7 +2033,8 @@ function signerCompatibility() {
|
|
|
1784
2033
|
x402_expected_context_versions: [...SUPPORTED_X402_EXPECTED_VERSIONS],
|
|
1785
2034
|
sweep_binding_versions: [...SUPPORTED_SWEEP_BINDING_VERSIONS],
|
|
1786
2035
|
direct_sign_context_versions: [...SUPPORTED_DIRECT_SIGN_CONTEXT_VERSIONS],
|
|
1787
|
-
task_sign_context_versions: [...SUPPORTED_TASK_SIGN_CONTEXT_VERSIONS]
|
|
2036
|
+
task_sign_context_versions: [...SUPPORTED_TASK_SIGN_CONTEXT_VERSIONS],
|
|
2037
|
+
sub_budget_sign_context_versions: [...SUPPORTED_SUB_BUDGET_SIGN_CONTEXT_VERSIONS]
|
|
1788
2038
|
};
|
|
1789
2039
|
}
|
|
1790
2040
|
function signerCapabilityAdvertisement() {
|
|
@@ -1804,12 +2054,13 @@ function signerInstructions() {
|
|
|
1804
2054
|
`- sweep authorization binding versions supported: ${compatibility.sweep_binding_versions.join(", ")}`,
|
|
1805
2055
|
`- direct-payment (haven_send / haven_pay) sign-context versions supported: ${compatibility.direct_sign_context_versions.join(", ")} \u2014 pass payment_id alone to haven_sign; this signer fetches the exact bytes`,
|
|
1806
2056
|
`- task-budget sign-context versions supported: ${compatibility.task_sign_context_versions.join(", ")} \u2014 pass task_budget_id alone to haven_sign; this signer fetches the exact bytes`,
|
|
2057
|
+
`- sub-budget sign-context versions supported: ${compatibility.sub_budget_sign_context_versions.join(", ")} \u2014 pass sub_budget_id alone to haven_sign; this signer fetches the exact bytes (a sub-budget your owner issued narrows your own budget for another agent of your account)`,
|
|
1807
2058
|
"",
|
|
1808
2059
|
"Haven quote and prepare results report the expected-context version they will emit",
|
|
1809
2060
|
"(signer_compatibility.x402_expected_context_version). If that version is not in the list",
|
|
1810
2061
|
"above, this signer is out of date: STOP before signing, and tell the user to update",
|
|
1811
|
-
`@haven_ai/signer
|
|
1812
|
-
"MCP runtime. Do not edit the version field to a supported value \u2014 it is part of the",
|
|
2062
|
+
`@haven_ai/signer: run \`${edge.connectorUpgradeCommand()}\`, then the repair line it prints,`,
|
|
2063
|
+
"which reinstalls the pinned MCP runtime. Do not edit the version field to a supported value \u2014 it is part of the",
|
|
1813
2064
|
"Haven-signed binding message, so changing it invalidates the signature.",
|
|
1814
2065
|
"",
|
|
1815
2066
|
"A version-mismatch refusal from haven_sign / haven_sign_x402 / haven_sign_sweep_delegate is",
|
|
@@ -1817,7 +2068,13 @@ function signerInstructions() {
|
|
|
1817
2068
|
"fallback fields alongside the message, so you can branch on it directly. Every signer",
|
|
1818
2069
|
"refusal also carries the next-step family: next_tool_name + next_tool_server_role when a",
|
|
1819
2070
|
"hosted tool follows (resolve the role against your own server names), else",
|
|
1820
|
-
"next_tool_omitted_reason saying why not."
|
|
2071
|
+
"next_tool_omitted_reason saying why not.",
|
|
2072
|
+
"",
|
|
2073
|
+
"An undeclared top-level argument is refused, not stripped: haven_sign answers",
|
|
2074
|
+
"UNSUPPORTED_ARGUMENT with unknown_arguments, signer_version and fallback (the update",
|
|
2075
|
+
"command) instead of dropping the key and answering the generic signing error \u2014 if you see",
|
|
2076
|
+
"that refusal, this signer predates the argument form you sent. Update the signer; nothing",
|
|
2077
|
+
"was signed, fetched or audited."
|
|
1821
2078
|
].join("\n");
|
|
1822
2079
|
}
|
|
1823
2080
|
async function loadSignerCredentials(path = process.env.HAVEN_CREDENTIALS) {
|
|
@@ -1920,8 +2177,6 @@ async function warnIfCredentialFilePermissive(path, log = (message) => process.s
|
|
|
1920
2177
|
}
|
|
1921
2178
|
|
|
1922
2179
|
// src/server.ts
|
|
1923
|
-
var SIGNER_NAME = "@haven_ai/signer";
|
|
1924
|
-
var SIGNER_VERSION = "0.6.0-alpha.0";
|
|
1925
2180
|
async function resolveSignerRuntime(options = {}) {
|
|
1926
2181
|
assertSupportedNodeVersion(options.nodeVersion);
|
|
1927
2182
|
if (options.delegateKey) {
|
|
@@ -1972,12 +2227,14 @@ function buildSignerMcpServer(signer, options = {}) {
|
|
|
1972
2227
|
clientIdentity: edge.havenClientIdentity(SIGNER_NAME, SIGNER_VERSION)
|
|
1973
2228
|
}
|
|
1974
2229
|
});
|
|
1975
|
-
const registerTool = server.
|
|
2230
|
+
const registerTool = server.registerTool.bind(server);
|
|
1976
2231
|
for (const name of Object.keys(toolSchemas)) {
|
|
1977
2232
|
registerTool(
|
|
1978
2233
|
name,
|
|
1979
|
-
|
|
1980
|
-
|
|
2234
|
+
{
|
|
2235
|
+
description: toolDescriptions[name],
|
|
2236
|
+
inputSchema: zod.z.object(toolSchemas[name]).passthrough()
|
|
2237
|
+
},
|
|
1981
2238
|
async (args) => toMcpResult(await handlers[name](args))
|
|
1982
2239
|
);
|
|
1983
2240
|
}
|