@haven_ai/signer 0.3.0-alpha.0 → 0.5.0-alpha.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/dist/index.js CHANGED
@@ -1,144 +1,17 @@
1
1
  import { randomUUID, createHash } from 'crypto';
2
2
  import { recoverTypedDataAddress, hashTypedData, hashMessage } from 'viem';
3
3
  import { privateKeyToAccount } from 'viem/accounts';
4
- import { exact } from 'x402/schemes';
5
- import { addressFromKey, HavenSigningError, buildSweepTypedData, selectStandardPaymentOption, HavenApiError, toStandardPaymentRequirements, decodeBase64Json, encodeBase64Json, x402V2PaymentEnvelope, buildSweepAuthorizationMessage, verifySignature, x402AuthorizationAmount, SignerRefusalCode, connectorRerunCommand, SIGNER_UPDATE_FALLBACK, HavenUnsupportedSignerVersionError, buildX402ExpectedMessage, HavenError, AgentPaymentFailureCode, AgentPaymentNextAction, isSupportedNodeVersion, unsupportedNodeVersionMessage, signHash } from '@haven_ai/sdk';
6
- import { readFile, stat, mkdir, appendFile, writeFile } from 'fs/promises';
4
+ import { createNextStepBuilder, addressFromKey, HavenSigningError, buildSweepTypedData, selectStandardPaymentOption, HavenApiError, toStandardPaymentRequirements, decodeBase64Json, encodeBase64Json, x402V2PaymentEnvelope, isSettlementChildTypedData, chainIdForNetwork, assertOwnSettlementChild, isPackedUserOperationTypedData, assertUserOpTypedDataBinding, assertBoundDirectPaymentUserOp, assertFundingLegPaysDelegate, HavenTypedDataRefusedError, buildSweepAuthorizationMessage, verifySignature, x402AuthorizationAmount, SignerRefusalCode, connectorRerunCommand, SIGNER_UPDATE_FALLBACK, HavenUnsupportedSignerVersionError, buildX402ExpectedMessage, DIRECT_SIGN_CONTEXT_VERSION, AgentPaymentNextAction, HavenError, AgentPaymentFailureCode, havenClientIdentity, isSupportedNodeVersion, unsupportedNodeVersionMessage, HavenUserOpBindingError, readClientUpdate, HAVEN_CLIENT_HEADER } from '@haven_ai/sdk/edge';
5
+ export { deriveDelegateAccountAddress } from '@haven_ai/sdk/edge';
6
+ import { stat, lstat, chmod, readFile, mkdir, rename, appendFile, writeFile } from 'fs/promises';
7
+ import { resolve, dirname, join } from 'path';
8
+ import { z } from 'zod';
7
9
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
8
10
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
9
11
  import { homedir } from 'os';
10
- import { resolve, dirname, join } from 'path';
11
- import { z } from 'zod/v3';
12
+ import { z as z$1 } from 'zod/v3';
12
13
 
13
14
  // src/core.ts
14
- var DELEGATION_MANAGER = "0xdb9B1e94B5b69Df7e401DDbedE43491141047dB3";
15
- var CAVEAT_ENFORCERS = {
16
- erc20TransferAmount: "0xf100b0819427117EcF76Ed94B358B1A5b5C6D2Fc",
17
- allowedCalldata: "0xc2b0d624c1c4319760C96503BA27C347F3260f55",
18
- timestamp: "0x1046bb45C8d673d4ea75321280DB34899413c069"};
19
- var MAX_SETTLEMENT_WINDOW_SECONDS = 600;
20
- function isSettlementChildTypedData(value) {
21
- const td = value;
22
- return !!td && td.primaryType === "Delegation" && typeof td.domain?.verifyingContract === "string" && Array.isArray(td.message?.caveats);
23
- }
24
- function same(a, b) {
25
- return !!a && !!b && a.toLowerCase() === b.toLowerCase();
26
- }
27
- function hex(value) {
28
- return value.startsWith("0x") ? value.slice(2).toLowerCase() : value.toLowerCase();
29
- }
30
- function slice(terms, start, end) {
31
- const body = hex(terms);
32
- return end === void 0 ? body.slice(start * 2) : body.slice(start * 2, end * 2);
33
- }
34
- function findCaveat(caveats, enforcer) {
35
- return caveats.find((c) => same(c.enforcer, enforcer));
36
- }
37
- function refuse(what, detail) {
38
- throw new HavenSigningError(
39
- `Refusing to sign the x402 settlement child: ${what}. ${detail} The signature on this child is what lets a merchant pull from the treasury, so it is verified locally against the Haven-signed expected context rather than trusted.`
40
- );
41
- }
42
- function chainIdForNetwork(network) {
43
- if (!network) return void 0;
44
- const caip = /^eip155:(\d+)$/.exec(network);
45
- if (caip) return Number(caip[1]);
46
- if (network === "base") return 8453;
47
- if (network === "base-sepolia") return 84532;
48
- return void 0;
49
- }
50
- function verifySettlementChild(typedData, expected, now = Date.now()) {
51
- if (typedData.primaryType !== "Delegation") {
52
- refuse("it is not a Delegation payload", `primaryType was '${typedData.primaryType}'.`);
53
- }
54
- if (!same(typedData.domain?.verifyingContract, DELEGATION_MANAGER)) {
55
- refuse(
56
- "the EIP-712 domain names an unknown DelegationManager",
57
- `Expected ${DELEGATION_MANAGER}, got ${typedData.domain?.verifyingContract}.`
58
- );
59
- }
60
- const domainChain = Number(typedData.domain?.chainId);
61
- if (!Number.isFinite(domainChain) || domainChain !== expected.chainId) {
62
- refuse(
63
- "it is scoped to the wrong chain",
64
- `The expected context says chain ${expected.chainId}; the child says ${typedData.domain?.chainId}.`
65
- );
66
- }
67
- const caveats = typedData.message?.caveats ?? [];
68
- if (caveats.length === 0) {
69
- refuse("it carries no caveats at all", "An unconstrained delegation is not a payment.");
70
- }
71
- const transfer = findCaveat(caveats, CAVEAT_ENFORCERS.erc20TransferAmount);
72
- if (!transfer) {
73
- refuse(
74
- "it has no ERC-20 transfer-amount caveat",
75
- "Without it the delegation is not bounded to an amount."
76
- );
77
- }
78
- const token = `0x${slice(transfer.terms, 0, 20)}`;
79
- if (!same(token, expected.asset)) {
80
- refuse("it spends a different token", `Expected ${expected.asset}, child pins ${token}.`);
81
- }
82
- const amount = BigInt(`0x${slice(transfer.terms, 20, 52)}`);
83
- if (amount !== BigInt(expected.amount)) {
84
- refuse(
85
- "the amount does not match",
86
- `The expected context says ${expected.amount}; the child allows ${amount.toString()}.`
87
- );
88
- }
89
- const calldata = findCaveat(caveats, CAVEAT_ENFORCERS.allowedCalldata);
90
- if (!calldata) {
91
- refuse(
92
- "it has no payee pin",
93
- "Without an allowed-calldata caveat the merchant could redeem it to any address."
94
- );
95
- }
96
- const startIndex = BigInt(`0x${slice(calldata.terms, 0, 32)}`);
97
- if (startIndex !== 4n) {
98
- refuse(
99
- "the payee pin points at the wrong calldata offset",
100
- `Expected offset 4 (the transfer's \`to\` word), got ${startIndex.toString()}.`
101
- );
102
- }
103
- const paddedPayee = slice(calldata.terms, 32, 64);
104
- if (paddedPayee.slice(0, 24) !== "0".repeat(24)) {
105
- refuse("the pinned payee is not a plain address", "Its 32-byte word is not a padded address.");
106
- }
107
- const payee = `0x${paddedPayee.slice(24)}`;
108
- if (!same(payee, expected.merchantTo)) {
109
- refuse(
110
- "it pays a different address than Haven declared",
111
- `The expected context says ${expected.merchantTo}; the child pins ${payee}.`
112
- );
113
- }
114
- const timestamp = findCaveat(caveats, CAVEAT_ENFORCERS.timestamp);
115
- if (!timestamp) {
116
- refuse("it never expires", "A settlement child without a timestamp caveat is open-ended.");
117
- }
118
- const beforeThreshold = Number(BigInt(`0x${slice(timestamp.terms, 16, 32)}`));
119
- if (beforeThreshold <= 0) {
120
- refuse("its expiry is unset", "The timestamp caveat has no upper bound.");
121
- }
122
- const nowSec = Math.floor(now / 1e3);
123
- if (beforeThreshold <= nowSec) {
124
- refuse("it has already expired", `Expiry ${beforeThreshold} is not in the future.`);
125
- }
126
- if (beforeThreshold > nowSec + MAX_SETTLEMENT_WINDOW_SECONDS) {
127
- refuse(
128
- "its window is longer than a settlement may live",
129
- `Expiry is ${beforeThreshold - nowSec}s out; the ceiling is ${MAX_SETTLEMENT_WINDOW_SECONDS}s.`
130
- );
131
- }
132
- if (expected.expiresAt) {
133
- const declared = Math.floor(Date.parse(expected.expiresAt) / 1e3);
134
- if (Number.isFinite(declared) && beforeThreshold > declared) {
135
- refuse(
136
- "it outlives the payment window Haven declared",
137
- `The expected context expires at ${expected.expiresAt}; the child lives to ${beforeThreshold}.`
138
- );
139
- }
140
- }
141
- }
142
15
  function createEdgeSigner(delegateKey, options = {}) {
143
16
  let delegateAddress;
144
17
  try {
@@ -158,41 +31,28 @@ function createEdgeSigner(delegateKey, options = {}) {
158
31
  if (!oldest.done) retiredX402Bindings.delete(oldest.value);
159
32
  }
160
33
  }
161
- function signAndVerify(hash) {
162
- const signature = signHash(delegateKey, hash);
163
- if (!verifySignature(hash, signature, delegateAddress)) {
164
- throw new HavenSigningError(
165
- "Local signature verification failed \u2014 recovered address does not match the delegate key."
166
- );
167
- }
168
- return signature;
169
- }
170
34
  return {
171
35
  delegateAddress,
172
- signPaymentHash(hash) {
173
- return signAndVerify(hash);
174
- },
175
36
  async signDelegationTypedData(typedData) {
176
37
  const account = privateKeyToAccount(delegateKey);
177
38
  return account.signTypedData(typedData);
178
39
  },
179
- signX402FundingHash(hash, expected) {
180
- assertExpectedBinding(hash, expected, options.x402BindingSigner, "hash");
181
- assertPayerMatchesDelegate(expected, delegateAddress, options.agentId);
182
- const signature = signAndVerify(hash);
183
- const x402Binding = randomUUID();
184
- x402Bindings.set(x402Binding, { ...expected });
185
- return { signature, x402Binding };
186
- },
187
40
  async signX402FundingTypedData(typedData, expected) {
188
- assertExpectedBinding(expected.payloadHash, expected, options.x402BindingSigner, "typed-data");
189
- assertPayerMatchesDelegate(expected, delegateAddress, options.agentId);
190
- const digest = hashTypedData(typedData);
191
- if (digest.toLowerCase() !== expected.typedDataHash?.toLowerCase()) {
41
+ if (expected?.auth) {
42
+ assertSupportedBindingVersion(
43
+ expected.auth.version,
44
+ SUPPORTED_X402_EXPECTED_VERSIONS,
45
+ "x402 expected context"
46
+ );
47
+ }
48
+ if (!typedData) {
192
49
  throw new HavenSigningError(
193
- "x402 typed data does not match the digest Haven committed to in the expected context. Refusing to sign \u2014 the payload was altered in transit or Haven declared a different one. The most common cause is the typed data being truncated or reshaped while being copied between tool calls (#1255): re-run the hosted quote and pass its typed_data_b64 string through UNCHANGED instead of re-emitting the nested JSON."
50
+ "This x402 funding intent is on the delegation rail: it commits to EIP-712 typed data, which was not supplied. Pass typed_data_b64 from haven_pay_mcp_tool / haven_pay_x402_quote through unchanged (or typed_data verbatim) \u2014 a bare payload_hash is not what the account validates."
194
51
  );
195
52
  }
53
+ const digest = hashTypedData(typedData);
54
+ assertExpectedBinding(digest, expected, options.x402BindingSigner);
55
+ assertPayerMatchesDelegate(expected, delegateAddress, options.agentId);
196
56
  if (isSettlementChildTypedData(typedData)) {
197
57
  const settlementChainId = chainIdForNetwork(expected.network);
198
58
  if (settlementChainId === void 0) {
@@ -200,7 +60,7 @@ function createEdgeSigner(delegateKey, options = {}) {
200
60
  `Refusing to sign the x402 settlement child: this signer cannot map the network '${expected.network}' to a chain id, so it cannot check which chain the child is scoped to. Update @haven_ai/signer.`
201
61
  );
202
62
  }
203
- verifySettlementChild(typedData, {
63
+ assertOwnSettlementChild(typedData, {
204
64
  merchantTo: expected.merchantTo,
205
65
  amount: expected.amount,
206
66
  asset: expected.asset,
@@ -210,7 +70,21 @@ function createEdgeSigner(delegateKey, options = {}) {
210
70
  // signed another" class this file exists to catch (#1455 review).
211
71
  chainId: settlementChainId,
212
72
  expiresAt: expected.expiresAt
73
+ }, delegateAddress);
74
+ } else if (isPackedUserOperationTypedData(typedData)) {
75
+ assertUserOpTypedDataBinding(typedData, expected.payloadHash);
76
+ assertBoundDirectPaymentUserOp(typedData, delegateAddress);
77
+ assertFundingLegPaysDelegate(typedData, {
78
+ delegateAddress,
79
+ asset: expected.asset,
80
+ amount: expected.amount
213
81
  });
82
+ } else {
83
+ throw new HavenTypedDataRefusedError(
84
+ `This signer's x402 arm signs only a funding-leg PackedUserOperation or an erc7710 settlement Delegation; this typed data is '${String(
85
+ typedData.primaryType
86
+ )}'. Refusing, even though its digest is bound by the expected context.`
87
+ );
214
88
  }
215
89
  const account = privateKeyToAccount(delegateKey);
216
90
  const signature = await account.signTypedData(
@@ -235,16 +109,19 @@ function createEdgeSigner(delegateKey, options = {}) {
235
109
  );
236
110
  }
237
111
  throw new HavenSigningError(
238
- "x402 funding binding is required before signing a merchant header. Sign the hosted funding hash with x402_expected first (haven_sign returns a binding this tool can use). A binding is also lost when the signer process restarts, since bindings live in memory only \u2014 re-sign to mint a fresh one."
112
+ "x402 funding binding is required before signing a merchant header. Sign the hosted funding payload with x402_expected first (haven_sign returns a binding this tool can use). A binding is also lost when the signer process restarts, since bindings live in memory only \u2014 re-sign to mint a fresh one."
239
113
  );
240
114
  }
241
- try {
242
- assertX402PaymentWindowOpen(expected);
243
- } catch (err) {
244
- x402Bindings.delete(x402Binding);
245
- retireX402Binding(x402Binding, "window_expired");
246
- throw err;
247
- }
115
+ const assertWindowOpenOrRetire = () => {
116
+ try {
117
+ assertX402PaymentWindowOpen(expected);
118
+ } catch (err) {
119
+ x402Bindings.delete(x402Binding);
120
+ retireX402Binding(x402Binding, "window_expired");
121
+ throw err;
122
+ }
123
+ };
124
+ assertWindowOpenOrRetire();
248
125
  const option = selectStandardPaymentOption(paymentRequired.accepts);
249
126
  if (!option) {
250
127
  throw new HavenApiError(
@@ -255,6 +132,8 @@ function createEdgeSigner(delegateKey, options = {}) {
255
132
  assertX402MatchesExpected(paymentRequired, option, expected);
256
133
  const account = privateKeyToAccount(delegateKey);
257
134
  const requirements = toStandardPaymentRequirements(paymentRequired, option);
135
+ const { exact } = await loadX402Schemes();
136
+ assertWindowOpenOrRetire();
258
137
  const header = await exact.evm.createPaymentHeader(
259
138
  account,
260
139
  paymentRequired.x402Version,
@@ -289,7 +168,7 @@ function createEdgeSigner(delegateKey, options = {}) {
289
168
  }
290
169
  if (expectedSafe && !sameAddress(authorization.to, expectedSafe)) {
291
170
  throw new HavenSigningError(
292
- "Sweep authorization `to` does not match the Safe in the local credential."
171
+ "Sweep authorization `to` does not match the agent's account (Haven wallet) in the local credential."
293
172
  );
294
173
  }
295
174
  const typedData = buildSweepTypedData(authorization);
@@ -368,7 +247,7 @@ function assertExpectedShape(expected) {
368
247
  throw new HavenSigningError("x402 expected funding context is required before signing a merchant header.");
369
248
  }
370
249
  }
371
- var SUPPORTED_X402_EXPECTED_VERSIONS = [1, 2, 3];
250
+ var SUPPORTED_X402_EXPECTED_VERSIONS = [2, 3];
372
251
  var SUPPORTED_SWEEP_BINDING_VERSIONS = [1];
373
252
  function assertSupportedBindingVersion(received, supported, context) {
374
253
  if (supported.includes(received)) return;
@@ -385,11 +264,11 @@ function assertSupportedBindingVersion(received, supported, context) {
385
264
  fallback
386
265
  );
387
266
  }
388
- function assertExpectedBinding(payloadHash, expected, trustedSigner, mode = "hash") {
267
+ function assertExpectedBinding(typedDataDigest, expected, trustedSigner) {
389
268
  assertExpectedShape(expected);
390
269
  if (!trustedSigner) {
391
270
  throw new HavenSigningError(
392
- "x402 expected-context verifier is not configured. Set HAVEN_X402_BINDING_SIGNER before signing x402 funding hashes."
271
+ "x402 expected-context verifier is not configured. Set HAVEN_X402_BINDING_SIGNER before signing x402 funding payloads."
393
272
  );
394
273
  }
395
274
  if (expected.auth) {
@@ -399,17 +278,14 @@ function assertExpectedBinding(payloadHash, expected, trustedSigner, mode = "has
399
278
  "x402 expected context"
400
279
  );
401
280
  }
402
- if (expected.payloadHash.toLowerCase() !== payloadHash.toLowerCase()) {
403
- throw new HavenSigningError("x402 expected context does not match the funding hash being signed.");
404
- }
405
- if (mode === "hash" && expected.typedDataHash) {
281
+ if (!expected.typedDataHash) {
406
282
  throw new HavenSigningError(
407
- "This x402 funding intent commits to EIP-712 typed data, so its bare hash must not be raw-signed \u2014 the account would reject that signature on-chain. Sign sign_data.typed_data instead."
283
+ "Refusing to sign typed data under an expected context that does not commit to it. Haven must return a v2/v3 x402 expected context (with typedDataHash) for a delegation-rail intent."
408
284
  );
409
285
  }
410
- if (mode === "typed-data" && !expected.typedDataHash) {
286
+ if (expected.typedDataHash.toLowerCase() !== typedDataDigest.toLowerCase()) {
411
287
  throw new HavenSigningError(
412
- "Refusing to sign typed data under an expected context that does not commit to it. Haven must return a v2 x402 expected context (with typedDataHash) for a delegation-rail intent."
288
+ "x402 typed data does not match the digest Haven committed to in the expected context. Refusing to sign \u2014 the payload was altered in transit or Haven declared a different one. The most common cause is the typed data being truncated or reshaped while being copied between tool calls (#1255): re-run the hosted quote and pass its typed_data_b64 string through UNCHANGED instead of re-emitting the nested JSON."
413
289
  );
414
290
  }
415
291
  const message = buildX402ExpectedMessage({
@@ -425,7 +301,7 @@ function assertExpectedBinding(payloadHash, expected, trustedSigner, mode = "has
425
301
  payerDelegate: expected.payerDelegate,
426
302
  payerAgentId: expected.payerAgentId
427
303
  });
428
- const expectedVersion = expected.payerDelegate ? 3 : expected.typedDataHash ? 2 : 1;
304
+ const expectedVersion = expected.payerDelegate ? 3 : 2;
429
305
  if (expected.auth?.version !== expectedVersion || expected.auth.message !== message) {
430
306
  throw new HavenSigningError("x402 expected context authentication message is invalid.");
431
307
  }
@@ -445,6 +321,15 @@ function assertPayerMatchesDelegate(expected, delegateAddress, localAgentId) {
445
321
  `This quote belongs to a DIFFERENT agent: the quote was created for agent ${quoteAgent} (delegate ${expected.payerDelegate}), but this signer holds agent ${localAgent} (delegate ${delegateAddress}). A long-lived host is holding stale credentials \u2014 its session authenticates as the old agent while the signer on disk belongs to the new one. Nothing was signed. Restart the host so it re-reads its wiring, then re-quote.`
446
322
  );
447
323
  }
324
+ async function loadX402Schemes() {
325
+ try {
326
+ return await import('x402/schemes');
327
+ } catch (err) {
328
+ throw new HavenSigningError(
329
+ `The x402 scheme module could not be loaded, so no merchant header was built (nothing was sent to the merchant): ${err instanceof Error ? err.message : String(err)}. The signer's runtime install is incomplete \u2014 run: npx @haven_ai/connect --doctor`
330
+ );
331
+ }
332
+ }
448
333
  function assertX402PaymentWindowOpen(expected) {
449
334
  if (!expected.expiresAt) return;
450
335
  const expiresAtMs = Date.parse(expected.expiresAt);
@@ -463,11 +348,317 @@ function assertX402PaymentWindowOpen(expected) {
463
348
  function sameAddress(a, b) {
464
349
  return a.toLowerCase() === b.toLowerCase();
465
350
  }
351
+ var SIGNER_HOSTED_HANDOFF_SHAPES = {
352
+ haven_get_payment_status: { payment_id: z.string().min(1) }
353
+ };
354
+ function target(shape) {
355
+ const schema = z.object(shape).strict();
356
+ return {
357
+ role: "hosted",
358
+ validate: (input) => {
359
+ const r = schema.safeParse(input ?? {});
360
+ return r.success ? null : r.error.errors.map((e) => `${e.path.join(".") || "(root)"}: ${e.message}`).join("; ");
361
+ }
362
+ };
363
+ }
364
+ var SIGNER_NEXT_STEP_TARGETS = {
365
+ haven_get_payment_status: target(SIGNER_HOSTED_HANDOFF_SHAPES.haven_get_payment_status)
366
+ };
367
+ var nextStep = createNextStepBuilder(SIGNER_NEXT_STEP_TARGETS);
368
+ function signerRefusalStep(input) {
369
+ return nextStep({ ...input, safeToContinue: false, reason: "" });
370
+ }
371
+ function nextStepWireFields(step) {
372
+ return {
373
+ ...step.next_tool ? { next_tool: step.next_tool } : {},
374
+ ...step.next_tool_server ? { next_tool_server: step.next_tool_server } : {},
375
+ ...step.next_tool_name ? { next_tool_name: step.next_tool_name } : {},
376
+ ...step.next_tool_server_role ? { next_tool_server_role: step.next_tool_server_role } : {},
377
+ ...step.next_arguments ? { next_arguments: step.next_arguments } : {},
378
+ ...step.next_tool_omitted_reason ? { next_tool_omitted_reason: step.next_tool_omitted_reason } : {}
379
+ };
380
+ }
381
+
382
+ // src/sign-context.ts
383
+ var DIRECT_RELAY_FALLBACK = "call haven_sign with payload_hash and typed_data_b64 from the haven_send / haven_pay result, passed through unchanged";
384
+ var HavenSignContextError = class extends HavenSigningError {
385
+ /**
386
+ * #3001 / #3010 review: the recovery path, when signing OTHER bytes is
387
+ * actually a remedy — a transport failure (timeout, unreachable) or a
388
+ * body this signer could not read. `typed_data_b64` is NOT in the default
389
+ * quote result since #1272: obtain it by re-running the SAME quote tool
390
+ * with the SAME idempotency_key plus `include_signing_payload: true`.
391
+ * Absent on a backend REFUSAL (an expired, executed or unsignable intent
392
+ * cannot be rescued by re-signing its bytes) — with one #3271 exception: a
393
+ * 404 from the DIRECT fetch (an older backend with no direct route), where
394
+ * the `haven_send` / `haven_pay` result's relay fields still work.
395
+ */
396
+ fallback;
397
+ /**
398
+ * #3001: `AgentPaymentNextAction` values the signer already emits — the
399
+ * version-mismatch refusal's `stop_and_tell_user` for the classes where
400
+ * retrying the same call cannot help, and `payment_window_expired` (with
401
+ * `retry_with_new_quote`) for the x402 fetch's 410 / `expired`, exactly as
402
+ * the plain `HavenError` branch already does for that code. On the direct
403
+ * fetch (#3271) only `error_code: 'expired'` means an expired window, with
404
+ * no `retry_with_new_quote`; a bare 410 there is a retired-rail tombstone.
405
+ */
406
+ next_action;
407
+ /**
408
+ * #3103 (epic #3105, decisions 1 and 3): the typed next step beside the
409
+ * action. A refusal Haven made (`SIGN_CONTEXT_REFUSED`, not expired) names
410
+ * the hosted status read with the payment id — except a 404 from the direct
411
+ * fetch (#3271), which names none and points at the `typed_data_b64`
412
+ * relay; a transport failure or a
413
+ * malformed body names no tool — the remedy is re-running the SAME quote
414
+ * tool with `include_signing_payload: true` (x402), or the payment
415
+ * result's `typed_data_b64` relay (direct, #3271) — and an expired window names
416
+ * none either (which quote tool depends on the flow). Never null: a step
417
+ * with no tool carries `next_tool_omitted_reason`. Additive: the class is
418
+ * not exported from the package index — the published surface is the
419
+ * `ToolFailure` envelope `tools.ts` builds from these fields — and every
420
+ * constructor call site is in this file.
421
+ */
422
+ next_tool;
423
+ next_tool_server;
424
+ next_tool_name;
425
+ next_tool_server_role;
426
+ next_arguments;
427
+ next_tool_omitted_reason;
428
+ retry_with_new_quote;
429
+ /** Present only for `SIGN_CONTEXT_REFUSED` (the backend's HTTP status). */
430
+ http_status;
431
+ /** Present only for `SIGN_CONTEXT_REFUSED`: the backend's own `error_code`. */
432
+ backend_error_code;
433
+ /**
434
+ * #3303: on a 426 `client_outdated` refusal, the backend's update hint —
435
+ * this signer is below the minimum the deployment set, and
436
+ * `client_update.upgrade_command` is what updates it.
437
+ */
438
+ client_update;
439
+ constructor(message, code, refusal, paymentId, flow = "x402") {
440
+ super(message);
441
+ this.code = code;
442
+ this.name = "HavenSignContextError";
443
+ let step;
444
+ if (code === "SIGN_CONTEXT_REFUSED") {
445
+ this.http_status = refusal?.httpStatus;
446
+ this.backend_error_code = refusal?.errorCode;
447
+ if (refusal?.errorCode === "client_outdated") {
448
+ this.client_update = refusal.clientUpdate;
449
+ this.next_action = AgentPaymentNextAction.StopAndTellUser;
450
+ step = signerRefusalStep({
451
+ nextAction: AgentPaymentNextAction.StopAndTellUser,
452
+ nextTool: null,
453
+ nextToolOmittedReason: `this signer is below the minimum version Haven accepts \u2014 tell the user to run ${refusal.clientUpdate?.upgrade_command ?? "the connector again"}, restart the agent runtime, then retry the same haven_sign / haven_sign_x402 call`
454
+ });
455
+ } else if (flow === "direct" && refusal?.httpStatus === 404) {
456
+ this.fallback = "typed_data_b64";
457
+ this.next_action = AgentPaymentNextAction.StopAndTellUser;
458
+ step = signerRefusalStep({
459
+ nextAction: AgentPaymentNextAction.StopAndTellUser,
460
+ nextTool: null,
461
+ nextToolOmittedReason: DIRECT_RELAY_FALLBACK
462
+ });
463
+ } else if (flow === "direct" && refusal?.errorCode === "expired") {
464
+ this.next_action = AgentPaymentNextAction.PaymentWindowExpired;
465
+ step = signerRefusalStep({
466
+ nextAction: AgentPaymentNextAction.PaymentWindowExpired,
467
+ nextTool: null,
468
+ nextToolOmittedReason: "call haven_send / haven_pay again with the same idempotency_key \u2014 the expired payment frees it"
469
+ });
470
+ } else if (flow === "x402" && (refusal?.httpStatus === 410 || refusal?.errorCode === "expired")) {
471
+ this.next_action = AgentPaymentNextAction.PaymentWindowExpired;
472
+ this.retry_with_new_quote = true;
473
+ step = signerRefusalStep({
474
+ nextAction: AgentPaymentNextAction.PaymentWindowExpired,
475
+ nextTool: null,
476
+ nextToolOmittedReason: "re-run the hosted quote tool you called with the same idempotency_key; which one depends on the flow"
477
+ });
478
+ } else {
479
+ this.next_action = AgentPaymentNextAction.StopAndTellUser;
480
+ step = signerRefusalStep({
481
+ nextAction: AgentPaymentNextAction.StopAndTellUser,
482
+ nextTool: "haven_get_payment_status",
483
+ nextArguments: { payment_id: paymentId }
484
+ });
485
+ }
486
+ } else {
487
+ this.fallback = "typed_data_b64";
488
+ this.next_action = AgentPaymentNextAction.StopAndTellUser;
489
+ step = signerRefusalStep({
490
+ nextAction: AgentPaymentNextAction.StopAndTellUser,
491
+ nextTool: null,
492
+ nextToolOmittedReason: flow === "direct" ? DIRECT_RELAY_FALLBACK : "re-run the SAME hosted quote tool with the same idempotency_key and include_signing_payload: true, then pass its typed_data_b64 to this signer"
493
+ });
494
+ }
495
+ Object.assign(this, nextStepWireFields(step));
496
+ }
497
+ };
498
+ async function loadHavenIdentity(credentialsPath) {
499
+ if (!credentialsPath) return null;
500
+ try {
501
+ const raw = JSON.parse(
502
+ await readFile(join(dirname(credentialsPath), "identity.json"), "utf8")
503
+ );
504
+ const apiKey = typeof raw.api_key === "string" ? raw.api_key : void 0;
505
+ const apiUrl = typeof raw.api_url === "string" ? raw.api_url : void 0;
506
+ if (!apiKey || !apiUrl) return null;
507
+ return { apiKey, apiUrl: apiUrl.replace(/\/+$/, "") };
508
+ } catch {
509
+ return null;
510
+ }
511
+ }
512
+ var SIGN_CONTEXT_TIMEOUT_MS = 15e3;
513
+ function signContextHeaders(identity, clientIdentity) {
514
+ return {
515
+ Authorization: `Bearer ${identity.apiKey}`,
516
+ ...clientIdentity ? { [HAVEN_CLIENT_HEADER]: clientIdentity } : {}
517
+ };
518
+ }
519
+ function refusalOf(status, body) {
520
+ return {
521
+ httpStatus: status,
522
+ errorCode: typeof body.error_code === "string" ? body.error_code : void 0,
523
+ clientUpdate: readClientUpdate(body.client_update)
524
+ };
525
+ }
526
+ async function fetchX402SignContext(identity, paymentId, fetchImpl = fetch, timeoutMs = SIGN_CONTEXT_TIMEOUT_MS, clientIdentity) {
527
+ let response;
528
+ try {
529
+ response = await fetchImpl(
530
+ `${identity.apiUrl}/x402/${encodeURIComponent(paymentId)}/sign-context`,
531
+ {
532
+ headers: signContextHeaders(identity, clientIdentity),
533
+ signal: AbortSignal.timeout(timeoutMs)
534
+ }
535
+ );
536
+ } catch (err) {
537
+ const timedOut = err instanceof Error && (err.name === "TimeoutError" || err.name === "AbortError");
538
+ throw new HavenSignContextError(
539
+ timedOut ? `Haven did not answer the signing-context fetch for ${paymentId} within ${timeoutMs} ms. Retry, or pass typed_data_b64 from the quote result instead.` : `Could not reach Haven to fetch the signing context for ${paymentId}: ${err instanceof Error ? err.message : String(err)}. Retry, or pass typed_data_b64 from the quote result instead.`,
540
+ timedOut ? "SIGN_CONTEXT_TIMEOUT" : "SIGN_CONTEXT_UNREACHABLE",
541
+ void 0,
542
+ paymentId
543
+ );
544
+ }
545
+ let body;
546
+ try {
547
+ body = await response.json();
548
+ } catch (err) {
549
+ if (err instanceof Error && (err.name === "TimeoutError" || err.name === "AbortError")) {
550
+ throw new HavenSignContextError(
551
+ `Haven did not finish sending the signing context for ${paymentId} within ${timeoutMs} ms. Retry, or pass typed_data_b64 from the quote result instead.`,
552
+ "SIGN_CONTEXT_TIMEOUT",
553
+ void 0,
554
+ paymentId
555
+ );
556
+ }
557
+ body = {};
558
+ }
559
+ if (!response.ok) {
560
+ const detail = typeof body.error === "string" ? body.error : `HTTP ${response.status}`;
561
+ throw new HavenSignContextError(
562
+ `Haven refused the signing-context fetch for ${paymentId}: ${detail}` + (response.status === 404 ? " \u2014 check the payment_id came from this agent\u2019s own quote." : response.status === 410 ? " Re-run the quote with the same idempotency key, then sign the fresh payment_id." : ""),
563
+ "SIGN_CONTEXT_REFUSED",
564
+ refusalOf(response.status, body),
565
+ paymentId
566
+ );
567
+ }
568
+ const signData = body.sign_data;
569
+ const x402Expected = body.x402_expected;
570
+ if (!signData || typeof signData.hash !== "string" || !signData.typed_data || typeof signData.typed_data !== "object" || !x402Expected) {
571
+ throw new HavenSignContextError(
572
+ "The Haven sign-context response is missing sign_data.typed_data or x402_expected \u2014 the backend may predate #1263. Pass typed_data_b64 from the quote result instead.",
573
+ "SIGN_CONTEXT_MALFORMED",
574
+ void 0,
575
+ paymentId
576
+ );
577
+ }
578
+ const paymentRequired = body.payment_required;
579
+ return {
580
+ paymentId: String(body.payment_id ?? paymentId),
581
+ payloadHash: signData.hash,
582
+ typedData: signData.typed_data,
583
+ x402Expected,
584
+ paymentRequired: paymentRequired && typeof paymentRequired === "object" && !Array.isArray(paymentRequired) ? paymentRequired : null,
585
+ clientUpdate: readClientUpdate(body.client_update)
586
+ };
587
+ }
588
+ var SUPPORTED_DIRECT_SIGN_CONTEXT_VERSIONS = [DIRECT_SIGN_CONTEXT_VERSION];
589
+ async function fetchDirectSignContext(identity, paymentId, fetchImpl = fetch, timeoutMs = SIGN_CONTEXT_TIMEOUT_MS, clientIdentity) {
590
+ let response;
591
+ try {
592
+ response = await fetchImpl(
593
+ `${identity.apiUrl}/payments/${encodeURIComponent(paymentId)}/sign-context`,
594
+ {
595
+ headers: signContextHeaders(identity, clientIdentity),
596
+ signal: AbortSignal.timeout(timeoutMs)
597
+ }
598
+ );
599
+ } catch (err) {
600
+ const timedOut = err instanceof Error && (err.name === "TimeoutError" || err.name === "AbortError");
601
+ throw new HavenSignContextError(
602
+ timedOut ? `Haven did not answer the direct-payment signing-context fetch for ${paymentId} within ${timeoutMs} ms. Retry, or pass typed_data_b64 from the payment result instead.` : `Could not reach Haven to fetch the direct-payment signing context for ${paymentId}: ${err instanceof Error ? err.message : String(err)}. Retry, or pass typed_data_b64 from the payment result instead.`,
603
+ timedOut ? "SIGN_CONTEXT_TIMEOUT" : "SIGN_CONTEXT_UNREACHABLE",
604
+ void 0,
605
+ paymentId,
606
+ "direct"
607
+ );
608
+ }
609
+ let body;
610
+ try {
611
+ body = await response.json();
612
+ } catch (err) {
613
+ if (err instanceof Error && (err.name === "TimeoutError" || err.name === "AbortError")) {
614
+ throw new HavenSignContextError(
615
+ `Haven did not finish sending the direct-payment signing context for ${paymentId} within ${timeoutMs} ms. Retry, or pass typed_data_b64 from the payment result instead.`,
616
+ "SIGN_CONTEXT_TIMEOUT",
617
+ void 0,
618
+ paymentId,
619
+ "direct"
620
+ );
621
+ }
622
+ body = {};
623
+ }
624
+ if (!response.ok) {
625
+ const detail = typeof body.error === "string" ? body.error : `HTTP ${response.status}`;
626
+ throw new HavenSignContextError(
627
+ `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 haven_send / haven_pay result, unchanged, instead." : body.error_code === "expired" ? " The payment window has expired; call haven_send / haven_pay again with the same idempotency_key." : ""),
628
+ "SIGN_CONTEXT_REFUSED",
629
+ refusalOf(response.status, body),
630
+ paymentId,
631
+ "direct"
632
+ );
633
+ }
634
+ const signData = body.sign_data;
635
+ const version = body.direct_sign_context_version;
636
+ 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)) {
637
+ throw new HavenSignContextError(
638
+ 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. Pass typed_data_b64 from the payment result instead.",
639
+ "SIGN_CONTEXT_MALFORMED",
640
+ void 0,
641
+ paymentId,
642
+ "direct"
643
+ );
644
+ }
645
+ return {
646
+ paymentId: String(body.payment_id ?? paymentId),
647
+ payloadHash: signData.hash,
648
+ typedData: signData.typed_data,
649
+ status: typeof body.status === "string" ? body.status : "pending_signature",
650
+ expiresAt: typeof body.expires_at === "string" ? body.expires_at : void 0,
651
+ clientUpdate: readClientUpdate(body.client_update)
652
+ };
653
+ }
654
+
655
+ // src/capabilities.ts
466
656
  var SIGNER_CAPABILITY_KEY = "haven/signer-compatibility";
467
657
  function signerCompatibility() {
468
658
  return {
469
659
  x402_expected_context_versions: [...SUPPORTED_X402_EXPECTED_VERSIONS],
470
- sweep_binding_versions: [...SUPPORTED_SWEEP_BINDING_VERSIONS]
660
+ sweep_binding_versions: [...SUPPORTED_SWEEP_BINDING_VERSIONS],
661
+ direct_sign_context_versions: [...SUPPORTED_DIRECT_SIGN_CONTEXT_VERSIONS]
471
662
  };
472
663
  }
473
664
  function signerCapabilityAdvertisement() {
@@ -478,12 +669,14 @@ function signerInstructions() {
478
669
  return [
479
670
  "Haven edge signer: sign-only tools bound to the local delegate key. It never emits the",
480
671
  "key. Its one network capability is an authenticated READ of a signing context from",
481
- "Haven by payment_id \u2014 pass payment_id to haven_sign / haven_sign_x402 (preferred for",
482
- "delegation-rail x402) instead of relaying bulky typed-data payloads yourself.",
672
+ "Haven by payment_id \u2014 pass payment_id to haven_sign (preferred for both a direct payment,",
673
+ "#3271, and delegation-rail x402) or haven_sign_x402 (x402 only) instead of relaying bulky",
674
+ "typed-data payloads yourself.",
483
675
  "",
484
676
  "Version compatibility (check this BEFORE signing, not after):",
485
677
  `- x402 expected-context versions supported: ${compatibility.x402_expected_context_versions.join(", ")}`,
486
678
  `- sweep authorization binding versions supported: ${compatibility.sweep_binding_versions.join(", ")}`,
679
+ `- 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`,
487
680
  "",
488
681
  "Haven quote and prepare results report the expected-context version they will emit",
489
682
  "(signer_compatibility.x402_expected_context_version). If that version is not in the list",
@@ -494,9 +687,68 @@ function signerInstructions() {
494
687
  "",
495
688
  "A version-mismatch refusal from haven_sign / haven_sign_x402 / haven_sign_sweep_delegate is",
496
689
  "machine-readable, not just prose: it carries code, supported_versions, received_version, and",
497
- "fallback fields alongside the message, so you can branch on it directly."
690
+ "fallback fields alongside the message, so you can branch on it directly. Every signer",
691
+ "refusal also carries the next-step family: next_tool_name + next_tool_server_role when a",
692
+ "hosted tool follows (resolve the role against your own server names), else",
693
+ "next_tool_omitted_reason saying why not."
498
694
  ].join("\n");
499
695
  }
696
+ var OWNER_ONLY_MODE = 384;
697
+ var defaultLog = (message) => process.stderr.write(`${message}
698
+ `);
699
+ async function permissiveMode(path, platform = process.platform, stats, follow = "nofollow") {
700
+ if (platform === "win32") return null;
701
+ let info;
702
+ try {
703
+ info = stats ?? (follow === "follow" ? await stat(path) : await lstat(path));
704
+ } catch {
705
+ return null;
706
+ }
707
+ if ((info.mode & 63) === 0) return null;
708
+ return { octal: (info.mode & 511).toString(8).padStart(4, "0"), regular: info.isFile() };
709
+ }
710
+ async function warnIfFilePermissive(kind, path, log = defaultLog, platform = process.platform) {
711
+ const found = await permissiveMode(path, platform, void 0, "follow");
712
+ if (found === null) return;
713
+ log(
714
+ `haven-signer: warning: ${kind} at ${path} is readable beyond the owner (mode ${found.octal}). Run: chmod 600 ${path}`
715
+ );
716
+ }
717
+ async function tightenIfFilePermissive(kind, path, log = defaultLog, platform = process.platform, stats) {
718
+ if (platform === "win32") return "skipped";
719
+ let info = stats ?? null;
720
+ if (!info) {
721
+ try {
722
+ info = await lstat(path);
723
+ } catch {
724
+ return "owner-only";
725
+ }
726
+ }
727
+ if (!info.isFile()) {
728
+ log(
729
+ `haven-signer: warning: ${kind} at ${path} is not a regular file (a symlink or similar); audit rows are being written through it and it was not tightened. Remove it: rm ${path}`
730
+ );
731
+ return "warned";
732
+ }
733
+ const found = await permissiveMode(path, platform, info);
734
+ if (found === null) return "owner-only";
735
+ {
736
+ try {
737
+ await chmod(path, OWNER_ONLY_MODE);
738
+ log(
739
+ `haven-signer: ${kind} at ${path} was readable beyond the owner (mode ${found.octal}); tightened to 0600.`
740
+ );
741
+ return "tightened";
742
+ } catch {
743
+ }
744
+ }
745
+ log(
746
+ `haven-signer: warning: ${kind} at ${path} is readable beyond the owner (mode ${found.octal}) and could not be tightened. Run: chmod 600 ${path}`
747
+ );
748
+ return "warned";
749
+ }
750
+
751
+ // src/credentials.ts
500
752
  async function loadSignerCredentials(path = process.env.HAVEN_CREDENTIALS) {
501
753
  if (path) return loadFromFile(path);
502
754
  const envKey = stringField(process.env.HAVEN_DELEGATE_KEY);
@@ -593,28 +845,26 @@ function invalidChainIdError(label) {
593
845
  }
594
846
  async function warnIfCredentialFilePermissive(path, log = (message) => process.stderr.write(`${message}
595
847
  `), platform = process.platform) {
596
- if (platform === "win32") return;
597
- let mode;
598
- try {
599
- mode = (await stat(path)).mode;
600
- } catch {
601
- return;
602
- }
603
- if ((mode & 63) !== 0) {
604
- const octal = (mode & 511).toString(8).padStart(4, "0");
605
- log(
606
- `haven-signer: warning: credential file at ${path} is readable beyond the owner (mode ${octal}). Run: chmod 600 ${path}`
607
- );
608
- }
848
+ await warnIfFilePermissive("credential file", path, log, platform);
609
849
  }
850
+ var AUDIT_ROTATE_BYTES = 8 * 1024 * 1024;
610
851
  function defaultSigningAuditPath(credentialsPath) {
611
852
  if (credentialsPath) return resolve(`${credentialsPath}.signer-audit.jsonl`);
612
853
  return resolve(homedir(), ".haven", "signer-audit.jsonl");
613
854
  }
614
- async function appendSigningAuditEntry(entry, path) {
855
+ async function appendSigningAuditEntry(entry, path, options = {}) {
615
856
  await mkdir(dirname(path), { recursive: true });
857
+ const rotateAt = options.rotateAtBytes ?? AUDIT_ROTATE_BYTES;
858
+ const existing = await lstat(path).catch(() => null);
859
+ if (existing) {
860
+ await tightenIfFilePermissive("audit sidecar", path, options.log, options.platform, existing);
861
+ if (existing.size >= rotateAt) {
862
+ await rename(path, `${path}.1`).catch(() => {
863
+ });
864
+ }
865
+ }
616
866
  await appendFile(path, `${JSON.stringify(entry)}
617
- `, "utf8");
867
+ `, { encoding: "utf8", mode: OWNER_ONLY_MODE });
618
868
  }
619
869
  function createSigningAuditEntry(tool, payloadHash, context, now = /* @__PURE__ */ new Date()) {
620
870
  const entry = {
@@ -640,168 +890,112 @@ function stableStringify(value) {
640
890
  const object = value;
641
891
  return `{${Object.keys(object).sort().map((key) => `${JSON.stringify(key)}:${stableStringify(object[key])}`).join(",")}}`;
642
892
  }
643
- var HavenSignContextError = class extends HavenSigningError {
644
- /**
645
- * #3001 / #3010 review: the recovery path, when signing OTHER bytes is
646
- * actually a remedy — a transport failure (timeout, unreachable) or a
647
- * body this signer could not read. `typed_data_b64` is NOT in the default
648
- * quote result since #1272: obtain it by re-running the SAME quote tool
649
- * with the SAME idempotency_key plus `include_signing_payload: true`.
650
- * Absent on a backend REFUSAL: an expired, executed or unsignable intent
651
- * cannot be rescued by re-signing its bytes.
652
- */
653
- fallback;
654
- /**
655
- * #3001: `AgentPaymentNextAction` values the signer already emits — the
656
- * version-mismatch refusal's `stop_and_tell_user` for the classes where
657
- * retrying the same call cannot help, and `payment_window_expired` (with
658
- * `retry_with_new_quote`) for the backend's 410 `expired`, exactly as the
659
- * plain `HavenError` branch already does for that code.
660
- */
661
- next_action;
662
- retry_with_new_quote;
663
- /** Present only for `SIGN_CONTEXT_REFUSED` (the backend's HTTP status). */
664
- http_status;
665
- /** Present only for `SIGN_CONTEXT_REFUSED`: the backend's own `error_code`. */
666
- backend_error_code;
667
- constructor(message, code, refusal) {
668
- super(message);
669
- this.code = code;
670
- this.name = "HavenSignContextError";
671
- if (code === "SIGN_CONTEXT_REFUSED") {
672
- this.http_status = refusal?.httpStatus;
673
- this.backend_error_code = refusal?.errorCode;
674
- if (refusal?.httpStatus === 410 || refusal?.errorCode === "expired") {
675
- this.next_action = AgentPaymentNextAction.PaymentWindowExpired;
676
- this.retry_with_new_quote = true;
677
- } else {
678
- this.next_action = AgentPaymentNextAction.StopAndTellUser;
679
- }
680
- } else {
681
- this.fallback = "typed_data_b64";
682
- this.next_action = AgentPaymentNextAction.StopAndTellUser;
683
- }
893
+ var BARE_HASH_REFUSED = "BARE_HASH_REFUSED";
894
+ var HavenBareHashRefusedError = class extends HavenSigningError {
895
+ next_action = AgentPaymentNextAction.StopAndTellUser;
896
+ step;
897
+ constructor() {
898
+ super(
899
+ "Refusing to sign a bare payload_hash. A hash carries no shape, amount, merchant or binding, so this signer cannot tell a Haven payment from a transfer out of the delegate wallet or a delegation of its authority \u2014 and the only rail that ever validated a raw hash signature is retired. Call this tool with { payment_id } (the signer fetches and verifies the Haven-signed context itself), or with typed_data / typed_data_b64 (the account validates that EIP-712 payload), or with x402_expected for an x402 funding leg."
900
+ );
901
+ this.code = BARE_HASH_REFUSED;
902
+ this.name = "HavenBareHashRefusedError";
903
+ this.step = signerRefusalStep({
904
+ nextAction: AgentPaymentNextAction.StopAndTellUser,
905
+ nextTool: null,
906
+ nextToolOmittedReason: "call haven_sign again with payment_id (preferred), typed_data / typed_data_b64, or x402_expected \u2014 a bare payload_hash is never signed"
907
+ });
684
908
  }
685
909
  };
686
- async function loadHavenIdentity(credentialsPath) {
687
- if (!credentialsPath) return null;
688
- try {
689
- const raw = JSON.parse(
690
- await readFile(join(dirname(credentialsPath), "identity.json"), "utf8")
691
- );
692
- const apiKey = typeof raw.api_key === "string" ? raw.api_key : void 0;
693
- const apiUrl = typeof raw.api_url === "string" ? raw.api_url : void 0;
694
- if (!apiKey || !apiUrl) return null;
695
- return { apiKey, apiUrl: apiUrl.replace(/\/+$/, "") };
696
- } catch {
697
- return null;
910
+
911
+ // src/tools.ts
912
+ var USEROP_BINDING_MISMATCH = "USEROP_BINDING_MISMATCH";
913
+ var HavenUserOpBindingRefusedError = class extends HavenSigningError {
914
+ next_action = AgentPaymentNextAction.StopAndTellUser;
915
+ step;
916
+ /** `message`: the SDK's `HavenUserOpBindingError.message` verbatim — it already names the mismatch and the remedy. */
917
+ constructor(message) {
918
+ super(message);
919
+ this.code = USEROP_BINDING_MISMATCH;
920
+ this.name = "HavenUserOpBindingRefusedError";
921
+ this.step = signerRefusalStep({
922
+ nextAction: AgentPaymentNextAction.StopAndTellUser,
923
+ nextTool: null,
924
+ nextToolOmittedReason: "call haven_sign again with payment_id alone (preferred), or with typed_data copied unchanged from the payment result"
925
+ });
698
926
  }
699
- }
700
- var SIGN_CONTEXT_TIMEOUT_MS = 15e3;
701
- async function fetchX402SignContext(identity, paymentId, fetchImpl = fetch, timeoutMs = SIGN_CONTEXT_TIMEOUT_MS) {
702
- let response;
703
- try {
704
- response = await fetchImpl(
705
- `${identity.apiUrl}/x402/${encodeURIComponent(paymentId)}/sign-context`,
706
- {
707
- headers: { Authorization: `Bearer ${identity.apiKey}` },
708
- signal: AbortSignal.timeout(timeoutMs)
709
- }
710
- );
711
- } catch (err) {
712
- const timedOut = err instanceof Error && (err.name === "TimeoutError" || err.name === "AbortError");
713
- throw new HavenSignContextError(
714
- timedOut ? `Haven did not answer the signing-context fetch for ${paymentId} within ${timeoutMs} ms. Retry, or pass typed_data_b64 from the quote result instead.` : `Could not reach Haven to fetch the signing context for ${paymentId}: ${err instanceof Error ? err.message : String(err)}. Retry, or pass typed_data_b64 from the quote result instead.`,
715
- timedOut ? "SIGN_CONTEXT_TIMEOUT" : "SIGN_CONTEXT_UNREACHABLE"
716
- );
927
+ };
928
+ var HavenTypedDataNotAllowedError = class extends HavenTypedDataRefusedError {
929
+ next_action = AgentPaymentNextAction.StopAndTellUser;
930
+ step;
931
+ constructor(message) {
932
+ super(message);
933
+ this.name = "HavenTypedDataNotAllowedError";
934
+ this.step = signerRefusalStep({
935
+ nextAction: AgentPaymentNextAction.StopAndTellUser,
936
+ nextTool: null,
937
+ nextToolOmittedReason: "sign only Haven-prepared payloads: call haven_sign with payment_id (preferred), or pass typed_data / typed_data_b64 copied unchanged from a haven_send / haven_pay result, or x402_expected from a Haven quote"
938
+ });
717
939
  }
718
- let body;
940
+ };
941
+ function assertBoundDirectPaymentUserOp2(typedData, delegateAddress) {
719
942
  try {
720
- body = await response.json();
943
+ assertBoundDirectPaymentUserOp(typedData, delegateAddress);
721
944
  } catch (err) {
722
- if (err instanceof Error && (err.name === "TimeoutError" || err.name === "AbortError")) {
723
- throw new HavenSignContextError(
724
- `Haven did not finish sending the signing context for ${paymentId} within ${timeoutMs} ms. Retry, or pass typed_data_b64 from the quote result instead.`,
725
- "SIGN_CONTEXT_TIMEOUT"
726
- );
945
+ if (err instanceof HavenTypedDataRefusedError && !(err instanceof HavenTypedDataNotAllowedError)) {
946
+ throw new HavenTypedDataNotAllowedError(err.message);
727
947
  }
728
- body = {};
948
+ throw err;
729
949
  }
730
- if (!response.ok) {
731
- const detail = typeof body.error === "string" ? body.error : `HTTP ${response.status}`;
732
- throw new HavenSignContextError(
733
- `Haven refused the signing-context fetch for ${paymentId}: ${detail}` + (response.status === 404 ? " \u2014 check the payment_id came from this agent\u2019s own quote." : response.status === 410 ? " Re-run the quote with the same idempotency key, then sign the fresh payment_id." : ""),
734
- "SIGN_CONTEXT_REFUSED",
735
- { httpStatus: response.status, errorCode: typeof body.error_code === "string" ? body.error_code : void 0 }
736
- );
737
- }
738
- const signData = body.sign_data;
739
- const x402Expected = body.x402_expected;
740
- if (!signData || typeof signData.hash !== "string" || !signData.typed_data || typeof signData.typed_data !== "object" || !x402Expected) {
741
- throw new HavenSignContextError(
742
- "The Haven sign-context response is missing sign_data.typed_data or x402_expected \u2014 the backend may predate #1263. Pass typed_data_b64 from the quote result instead.",
743
- "SIGN_CONTEXT_MALFORMED"
744
- );
745
- }
746
- const paymentRequired = body.payment_required;
747
- return {
748
- paymentId: String(body.payment_id ?? paymentId),
749
- payloadHash: signData.hash,
750
- typedData: signData.typed_data,
751
- x402Expected,
752
- paymentRequired: paymentRequired && typeof paymentRequired === "object" && !Array.isArray(paymentRequired) ? paymentRequired : null
753
- };
754
950
  }
755
-
756
- // src/tools.ts
757
- var sweepAuthorizationSchema = z.object({
758
- from: z.string().regex(/^0x[0-9a-fA-F]{40}$/, "from must be a 0x address"),
759
- to: z.string().regex(/^0x[0-9a-fA-F]{40}$/, "to must be a 0x address"),
760
- value: z.string().regex(/^[0-9]+$/, "value must be a decimal atomic amount"),
761
- validAfter: z.string().regex(/^[0-9]+$/, "validAfter must be a decimal unix time"),
762
- validBefore: z.string().regex(/^[0-9]+$/, "validBefore must be a decimal unix time"),
763
- nonce: z.string().regex(/^0x[0-9a-fA-F]{64}$/, "nonce must be a 0x-prefixed 32-byte hex string"),
764
- token: z.string().regex(/^0x[0-9a-fA-F]{40}$/, "token must be a 0x address"),
765
- chainId: z.number().int().positive()
951
+ var sweepAuthorizationSchema = z$1.object({
952
+ from: z$1.string().regex(/^0x[0-9a-fA-F]{40}$/, "from must be a 0x address"),
953
+ to: z$1.string().regex(/^0x[0-9a-fA-F]{40}$/, "to must be a 0x address"),
954
+ value: z$1.string().regex(/^[0-9]+$/, "value must be a decimal atomic amount"),
955
+ validAfter: z$1.string().regex(/^[0-9]+$/, "validAfter must be a decimal unix time"),
956
+ validBefore: z$1.string().regex(/^[0-9]+$/, "validBefore must be a decimal unix time"),
957
+ nonce: z$1.string().regex(/^0x[0-9a-fA-F]{64}$/, "nonce must be a 0x-prefixed 32-byte hex string"),
958
+ token: z$1.string().regex(/^0x[0-9a-fA-F]{40}$/, "token must be a 0x address"),
959
+ chainId: z$1.number().int().positive()
766
960
  });
767
- var bindingVersionSchema = z.number().int().positive();
768
- var sweepExpectedAuthSchema = z.object({
961
+ var bindingVersionSchema = z$1.number().int().positive();
962
+ var sweepExpectedAuthSchema = z$1.object({
769
963
  version: bindingVersionSchema,
770
- message: z.string().min(1),
771
- signature: z.string().regex(/^0x[0-9a-fA-F]+$/, "signature must be a 0x-prefixed hex string"),
772
- signer: z.string().regex(/^0x[0-9a-fA-F]{40}$/, "signer must be a 0x address")
964
+ message: z$1.string().min(1),
965
+ signature: z$1.string().regex(/^0x[0-9a-fA-F]+$/, "signature must be a 0x-prefixed hex string"),
966
+ signer: z$1.string().regex(/^0x[0-9a-fA-F]{40}$/, "signer must be a 0x address")
773
967
  });
774
968
  var x402ExpectedShape = {
775
- payment_id: z.string().min(1),
776
- payload_hash: z.string().regex(/^0x[0-9a-fA-F]+$/, "payload_hash must be a 0x-prefixed hex string"),
777
- resource_url: z.string().url(),
778
- merchant_to: z.string().min(1),
779
- amount: z.string().min(1),
780
- asset: z.string().min(1),
781
- network: z.string().min(1),
969
+ payment_id: z$1.string().min(1),
970
+ payload_hash: z$1.string().regex(/^0x[0-9a-fA-F]{64}$/, "payload_hash must be a 0x-prefixed 32-byte hex string"),
971
+ resource_url: z$1.string().url(),
972
+ merchant_to: z$1.string().min(1),
973
+ amount: z$1.string().min(1),
974
+ asset: z$1.string().min(1),
975
+ network: z$1.string().min(1),
782
976
  // Required: expires_at is folded into the Haven-signed binding message, so the
783
977
  // signer must receive the exact same value to reconstruct a matching message.
784
978
  // Omitting it used to fail downstream with a cryptic "authentication message is
785
979
  // invalid" — making it required surfaces a clear INVALID_INPUT at the boundary.
786
- expires_at: z.string().min(1),
980
+ expires_at: z$1.string().min(1),
787
981
  // #1138: present on a delegation-rail (v2) context. It commits to the EIP-712
788
982
  // typed data the account validates; the signer refuses to raw-sign the bare
789
983
  // hash when it is present, and refuses to sign typed data when it is absent.
790
- typed_data_hash: z.string().regex(/^0x[0-9a-fA-F]+$/, "typed_data_hash must be a 0x-prefixed hex string").optional(),
984
+ typed_data_hash: z$1.string().regex(/^0x[0-9a-fA-F]{64}$/, "typed_data_hash must be a 0x-prefixed 32-byte hex string").optional(),
791
985
  // #1690: present on a v3 context. The delegate this quote was created FOR —
792
986
  // the signer refuses to sign when it is not its own. Inside the Haven-signed
793
987
  // message, so stripping it here would only break the binding signature.
794
- payer_delegate: z.string().regex(/^0x[0-9a-fA-F]{40}$/, "payer_delegate must be a 0x-prefixed address").optional(),
795
- payer_agent_id: z.string().min(1).optional(),
796
- auth: z.object({
988
+ payer_delegate: z$1.string().regex(/^0x[0-9a-fA-F]{40}$/, "payer_delegate must be a 0x-prefixed address").optional(),
989
+ payer_agent_id: z$1.string().min(1).optional(),
990
+ auth: z$1.object({
797
991
  // Open at the boundary, enforced in the signer — see bindingVersionSchema.
798
992
  version: bindingVersionSchema,
799
- message: z.string().min(1),
800
- signature: z.string().regex(/^0x[0-9a-fA-F]+$/, "signature must be a 0x-prefixed hex string"),
801
- signer: z.string().min(1)
993
+ message: z$1.string().min(1),
994
+ signature: z$1.string().regex(/^0x[0-9a-fA-F]+$/, "signature must be a 0x-prefixed hex string"),
995
+ signer: z$1.string().min(1)
802
996
  })
803
997
  };
804
- var x402ExpectedSchema = z.object(x402ExpectedShape);
998
+ var x402ExpectedSchema = z$1.object(x402ExpectedShape);
805
999
  var toolSchemas = {
806
1000
  haven_sign_sweep_delegate: {
807
1001
  // The authorization fields prepared by Haven's POST /sweep/prepare. Passed
@@ -816,17 +1010,17 @@ var toolSchemas = {
816
1010
  // payload + expected context from Haven itself (authenticated with the
817
1011
  // locally-stored agent credential), so no bulky bytes ever cross the
818
1012
  // model. Pass payment_id ALONE for a delegation-rail x402 funding intent.
819
- payment_id: z.string().min(1).optional(),
1013
+ payment_id: z$1.string().min(1).optional(),
820
1014
  // The unsigned hash from haven_pay / haven_pay_x402_quote (payload_hash).
821
1015
  // Optional when payment_id is supplied (the fetch carries it).
822
- payload_hash: z.string().regex(/^0x[0-9a-fA-F]+$/, "payload_hash must be a 0x-prefixed hex string").optional(),
1016
+ payload_hash: z$1.string().regex(/^0x[0-9a-fA-F]{64}$/, "payload_hash must be a 0x-prefixed 32-byte hex string").optional(),
823
1017
  // Pass x402.expected from hosted haven_pay_x402_quote when this hash funds
824
1018
  // a standard x402 merchant retry. The signer records it locally and returns
825
1019
  // an opaque x402_binding for the later header-signing step.
826
1020
  x402_expected: x402ExpectedSchema.optional(),
827
1021
  // #1138: delegation-rail intents sign THIS, not payload_hash. Object-typed
828
1022
  // (not z.unknown()) so MCP clients embed it as JSON rather than a string.
829
- typed_data: z.record(z.string(), z.unknown()).optional(),
1023
+ typed_data: z$1.record(z$1.string(), z$1.unknown()).optional(),
830
1024
  // #1255: the same payload as ONE opaque base64 string, exactly as returned
831
1025
  // by the hosted tools. Preferred over typed_data when both are present —
832
1026
  // an agent re-emitting multi-KB nested JSON between tool calls is the
@@ -835,48 +1029,57 @@ var toolSchemas = {
835
1029
  // Bounded: a realistic redemption payload is ~10KB encoded; 256KB is
836
1030
  // generous headroom while keeping the offline signer from materializing
837
1031
  // arbitrarily large caller input.
838
- typed_data_b64: z.string().min(1).max(262144).optional()
1032
+ typed_data_b64: z$1.string().min(1).max(262144).optional()
839
1033
  },
840
1034
  haven_x402_sign_header: {
841
1035
  // The parsed HTTP 402 PaymentRequired from the merchant. Typed as an object
842
1036
  // (not z.unknown(), which becomes empty JSON Schema `{}`) so MCP clients
843
1037
  // embed it as JSON rather than serialising the object to a string.
844
- payment_required: z.record(z.string(), z.unknown()),
1038
+ payment_required: z$1.record(z$1.string(), z$1.unknown()),
845
1039
  // Opaque binding returned by haven_sign when x402_expected was supplied.
846
- x402_binding: z.string().min(1)
1040
+ x402_binding: z$1.string().min(1)
847
1041
  },
848
1042
  haven_sign_x402: {
849
1043
  // #1263: preferred — pass payment_id (plus payment_required) and the
850
1044
  // signer fetches the exact signing payload + expected context itself.
851
- payment_id: z.string().min(1).optional(),
1045
+ payment_id: z$1.string().min(1).optional(),
852
1046
  // One-shot x402 signing: funding hash + merchant header in one local call.
853
1047
  // Both optional when payment_id is supplied (the fetch carries them).
854
- payload_hash: z.string().regex(/^0x[0-9a-fA-F]+$/, "payload_hash must be a 0x-prefixed hex string").optional(),
1048
+ payload_hash: z$1.string().regex(/^0x[0-9a-fA-F]{64}$/, "payload_hash must be a 0x-prefixed 32-byte hex string").optional(),
855
1049
  x402_expected: x402ExpectedSchema.optional(),
856
1050
  // #1355: optional when payment_id is supplied — the signer's context fetch
857
1051
  // carries the stored 402 PaymentRequired, so `{ payment_id }` alone is the
858
1052
  // preferred call. Still required (via the runtime check in the handler)
859
1053
  // for the quote-based fallback path without a payment_id.
860
- payment_required: z.record(z.string(), z.unknown()).optional(),
1054
+ payment_required: z$1.record(z$1.string(), z$1.unknown()).optional(),
861
1055
  // #1138: delegation-rail intents sign THIS, not payload_hash. Object-typed
862
1056
  // (not z.unknown()) so MCP clients embed it as JSON rather than a string.
863
- typed_data: z.record(z.string(), z.unknown()).optional(),
1057
+ typed_data: z$1.record(z$1.string(), z$1.unknown()).optional(),
864
1058
  // #1255: see haven_sign.typed_data_b64 — the copy-through-safe form.
865
- typed_data_b64: z.string().min(1).max(262144).optional()
1059
+ typed_data_b64: z$1.string().min(1).max(262144).optional()
866
1060
  }
1061
+ // #3101: keys survive on the type (see the hosted server's contracts.ts).
867
1062
  };
868
1063
  var SIGN_DESCRIPTION = [
869
- "Sign an unsigned Haven payment hash with the local delegate key. The delegate key never leaves",
870
- "this process. Pass the payload_hash returned by haven_pay or haven_pay_x402_quote.",
1064
+ "Sign an unsigned Haven payment with the local delegate key. The delegate key never leaves",
1065
+ "this process. Pass payment_id (preferred), or the payload_hash from haven_pay / haven_pay_x402_quote",
1066
+ "TOGETHER WITH typed_data / typed_data_b64 or x402_expected \u2014 never a payload_hash alone.",
871
1067
  "For x402, also pass x402_expected from haven_pay_x402_quote; the signer records it locally",
872
1068
  "and returns { signature, x402_binding }. x402_expected includes expires_at; sign before that",
873
1069
  "window closes. DELEGATION-RAIL x402 accounts (#1263): pass payment_id ALONE (preferred) \u2014 this",
874
1070
  "signer fetches the exact signing payload and expected context from Haven itself, so nothing",
875
- "bulky ever crosses your context. Fallback: pass typed_data_b64 through UNCHANGED (never re-type",
876
- "the nested typed_data JSON); the account validates that EIP-712 payload, not payload_hash.",
1071
+ "bulky ever crosses your context. DIRECT payments (#3271, haven_send / haven_pay): payment_id",
1072
+ "ALONE also works here \u2014 the signer fetches the exact PackedUserOperation typed data and its",
1073
+ "payload_hash from Haven and checks them against each other before signing, so a payload corrupted",
1074
+ "in transit is refused (USEROP_BINDING_MISMATCH) instead of producing a bad signature. Fallback for",
1075
+ "either flow: pass typed_data_b64 through UNCHANGED (never re-type the nested typed_data JSON); the",
1076
+ "account validates that EIP-712 payload, not payload_hash. On a direct payment the same binding",
1077
+ "check runs on the relayed payload too. This tool signs ONLY Haven-prepared payloads (#3272): a",
1078
+ "direct-payment UserOp from your own account that redeems a delegation granted to it, or an x402",
1079
+ "intent against a Haven-signed context \u2014 never an arbitrary typed_data payload, however it is shaped.",
877
1080
  "Next: call mcp__haven__haven_submit with signature, then pass x402_binding",
878
- "to mcp__haven-signer__haven_x402_sign_header. For plain SafeTransfer payments, just pass payload_hash and",
879
- "relay the returned signature via mcp__haven__haven_submit."
1081
+ "to mcp__haven-signer__haven_x402_sign_header. A bare payload_hash with no payment_id, typed_data",
1082
+ "or x402_expected is REFUSED (BARE_HASH_REFUSED): a hash carries nothing this signer can verify."
880
1083
  ].join(" ");
881
1084
  var X402_SIGN_HEADER_DESCRIPTION = [
882
1085
  "Build and sign the EIP-3009 merchant payment header for the merchant leg of an x402 payment.",
@@ -894,7 +1097,7 @@ var X402_SIGN_HEADER_DESCRIPTION = [
894
1097
  "the funding tx has a confirmed status). Next for paid MCP tools: call mcp__haven__haven_complete_mcp_tool."
895
1098
  ].join(" ");
896
1099
  var SIGN_X402_DESCRIPTION = [
897
- "One-shot x402 signing for the fast 3-call flow: sign the funding hash AND build the EIP-3009",
1100
+ "One-shot x402 signing for the fast 3-call flow: sign the funding payload AND build the EIP-3009",
898
1101
  "merchant payment header in a single local call (equivalent to haven_sign followed by",
899
1102
  "haven_x402_sign_header). The delegate key never leaves this process. From the haven_pay_mcp_tool",
900
1103
  "result pass JUST payment_id \u2014 PREFERRED (#1263, #1355): this signer fetches the exact signing",
@@ -920,12 +1123,13 @@ var SIGN_X402_DESCRIPTION = [
920
1123
  ].join(" ");
921
1124
  var SIGN_SWEEP_DELEGATE_DESCRIPTION = [
922
1125
  "Sign a Haven-prepared gasless USDC sweep that recovers stranded funds from the delegate",
923
- "wallet back to your Haven wallet. The delegate key never leaves this process and this tool",
924
- "never broadcasts \u2014 it returns only an EIP-3009 signature that Haven's relayer submits and",
1126
+ "wallet back to the agent's account (Haven wallet). The delegate key never leaves this process and this",
1127
+ "tool never broadcasts \u2014 it returns only an EIP-3009 signature that Haven's relayer submits and",
925
1128
  "pays gas for. Pass the authorization and expected_auth returned by the hosted",
926
- "haven_sweep_delegate tool. The signer verifies Haven authored the authorization and that it",
927
- "pays out to your own Safe before signing, then returns { signature } to hand back to",
928
- "mcp__haven__haven_sweep_delegate to complete recovery."
1129
+ "haven_sweep_delegate tool. The signer verifies Haven authored the authorization; the",
1130
+ "destination is checked against the agent's account (Haven wallet) from the local credential",
1131
+ "when the credential carries one, and otherwise rests on Haven's binding signature. Then it",
1132
+ "returns { signature } to hand back to mcp__haven__haven_sweep_delegate to complete recovery."
929
1133
  ].join(" ");
930
1134
  var toolDescriptions = {
931
1135
  haven_sign: SIGN_DESCRIPTION,
@@ -933,16 +1137,18 @@ var toolDescriptions = {
933
1137
  haven_sign_x402: SIGN_X402_DESCRIPTION,
934
1138
  haven_sign_sweep_delegate: SIGN_SWEEP_DELEGATE_DESCRIPTION
935
1139
  };
936
- async function signFundingLeg(signer, expected, payloadHash, typedData) {
937
- if (!expected.typedDataHash) {
938
- return signer.signX402FundingHash(payloadHash, expected);
939
- }
940
- if (!typedData) {
941
- throw new HavenSigningError(
942
- "This x402 funding intent is on the delegation rail: it commits to EIP-712 typed data, which was not supplied. Pass typed_data_b64 from haven_pay_mcp_tool / haven_pay_x402_quote through unchanged (or typed_data verbatim) \u2014 the bare payload_hash is not what the account validates."
943
- );
1140
+ async function signFundingLeg(signer, expected, typedData) {
1141
+ try {
1142
+ return await signer.signX402FundingTypedData(typedData, expected);
1143
+ } catch (err) {
1144
+ if (err instanceof HavenTypedDataRefusedError && !(err instanceof HavenTypedDataNotAllowedError)) {
1145
+ throw new HavenTypedDataNotAllowedError(err.message);
1146
+ }
1147
+ if (err instanceof HavenUserOpBindingError) {
1148
+ throw new HavenUserOpBindingRefusedError(err.message);
1149
+ }
1150
+ throw err;
944
1151
  }
945
- return signer.signX402FundingTypedData(typedData, expected);
946
1152
  }
947
1153
  function toExpectedX402(raw) {
948
1154
  return {
@@ -961,7 +1167,7 @@ function toExpectedX402(raw) {
961
1167
  };
962
1168
  }
963
1169
  function parseFetchedExpected(fetched) {
964
- const parsed = z.object(x402ExpectedShape).safeParse(fetched.x402Expected);
1170
+ const parsed = z$1.object(x402ExpectedShape).safeParse(fetched.x402Expected);
965
1171
  if (!parsed.success) {
966
1172
  throw new HavenSigningError(
967
1173
  `The Haven sign-context response carried a malformed x402_expected \u2014 the backend may predate #1263. Pass payload_hash + x402_expected from the quote result instead. Underlying: ${parsed.error.issues[0]?.message ?? "schema mismatch"}`
@@ -969,6 +1175,9 @@ function parseFetchedExpected(fetched) {
969
1175
  }
970
1176
  return parsed.data;
971
1177
  }
1178
+ function clientUpdateField(resolved) {
1179
+ return resolved?.ctx.clientUpdate ? { client_update: resolved.ctx.clientUpdate } : {};
1180
+ }
972
1181
  function resolveTypedData(args) {
973
1182
  if (!args.typed_data_b64) return args.typed_data;
974
1183
  try {
@@ -986,7 +1195,14 @@ function resolveTypedData(args) {
986
1195
  }
987
1196
  }
988
1197
  function createToolHandlers(signer, options = {}) {
989
- async function resolveSignContext(args) {
1198
+ function checkPayloadHashMatch(suppliedHash, fetchedHash, paymentId) {
1199
+ if (suppliedHash && suppliedHash.toLowerCase() !== fetchedHash.toLowerCase()) {
1200
+ throw new HavenSigningError(
1201
+ `The supplied payload_hash does not match the signing context Haven serves for payment ${paymentId}. Pass payment_id alone, or check which quote the hash came from.`
1202
+ );
1203
+ }
1204
+ }
1205
+ async function resolveSignContext(args, opts) {
990
1206
  if (!args.payment_id) return null;
991
1207
  const identity = await options.signContext?.loadIdentity() ?? null;
992
1208
  if (!identity) {
@@ -994,32 +1210,53 @@ function createToolHandlers(signer, options = {}) {
994
1210
  `payment_id signing needs the agent identity (identity.json next to the signer credentials), which this signer could not load. Re-run \`${connectorRerunCommand()}\` to restore it, or pass typed_data_b64 from the quote result instead.`
995
1211
  );
996
1212
  }
997
- const ctx = await fetchX402SignContext(
998
- identity,
999
- args.payment_id,
1000
- options.signContext?.fetchImpl
1001
- );
1002
- if (args.payload_hash && args.payload_hash.toLowerCase() !== ctx.payloadHash.toLowerCase()) {
1003
- throw new HavenSigningError(
1004
- `The supplied payload_hash does not match the signing context Haven serves for payment ${args.payment_id}. Pass payment_id alone, or check which quote the hash came from.`
1213
+ try {
1214
+ const ctx = await fetchX402SignContext(
1215
+ identity,
1216
+ args.payment_id,
1217
+ options.signContext?.fetchImpl,
1218
+ void 0,
1219
+ options.signContext?.clientIdentity
1005
1220
  );
1221
+ checkPayloadHashMatch(args.payload_hash, ctx.payloadHash, args.payment_id);
1222
+ return { kind: "x402", ctx };
1223
+ } catch (err) {
1224
+ if (opts.allowDirectFallback && err instanceof HavenSignContextError && err.http_status === 409 && err.backend_error_code === "sign_context_unavailable") {
1225
+ let ctx;
1226
+ try {
1227
+ ctx = await fetchDirectSignContext(
1228
+ identity,
1229
+ args.payment_id,
1230
+ options.signContext?.fetchImpl,
1231
+ void 0,
1232
+ options.signContext?.clientIdentity
1233
+ );
1234
+ } catch (directErr) {
1235
+ if (directErr instanceof HavenSignContextError && directErr.http_status === 409 && directErr.backend_error_code === "sign_context_unavailable") {
1236
+ throw err;
1237
+ }
1238
+ throw directErr;
1239
+ }
1240
+ checkPayloadHashMatch(args.payload_hash, ctx.payloadHash, args.payment_id);
1241
+ return { kind: "direct", ctx };
1242
+ }
1243
+ throw err;
1006
1244
  }
1007
- return ctx;
1008
1245
  }
1009
1246
  return {
1010
1247
  haven_sign: async (input) => runTool(async () => {
1011
1248
  const args = parse("haven_sign", coerceX402Expected(input));
1012
- const fetched = await resolveSignContext(args);
1013
- const typedData = fetched?.typedData ?? resolveTypedData(args);
1014
- const payloadHash = fetched?.payloadHash ?? args.payload_hash;
1249
+ const resolved = await resolveSignContext(args, { allowDirectFallback: true });
1250
+ const typedData = resolved?.ctx.typedData ?? resolveTypedData(args);
1251
+ const payloadHash = resolved?.ctx.payloadHash ?? args.payload_hash;
1015
1252
  if (!payloadHash) {
1016
1253
  throw new HavenSigningError(
1017
- "Pass payment_id (preferred for delegation-rail x402) or payload_hash."
1254
+ "Pass payment_id (preferred for delegation-rail x402 or direct payments) or payload_hash."
1018
1255
  );
1019
1256
  }
1020
- const expectedRaw = fetched ? parseFetchedExpected(fetched) : args.x402_expected;
1257
+ const expectedRaw = resolved?.kind === "x402" ? parseFetchedExpected(resolved.ctx) : resolved?.kind === "direct" ? null : args.x402_expected;
1021
1258
  const x402Expected = expectedRaw ? toExpectedX402(expectedRaw) : null;
1022
- const result = x402Expected ? await signFundingLeg(signer, x402Expected, payloadHash, typedData) : null;
1259
+ const result = x402Expected ? await signFundingLeg(signer, x402Expected, typedData) : null;
1023
1260
  if (!result) {
1024
1261
  if (typedData) {
1025
1262
  if (isSettlementChildTypedData(typedData)) {
@@ -1027,16 +1264,28 @@ function createToolHandlers(signer, options = {}) {
1027
1264
  "Refusing to sign a delegation payload with no expected context. This typed data is a DELEGATION \u2014 signing it grants a third party authority to move funds \u2014 so it is only ever signed against a Haven-signed context that the caveats are verified against. Call this tool with { payment_id } instead (the signer then fetches and verifies the context itself), or use haven_sign_x402."
1028
1265
  );
1029
1266
  }
1030
- const signature2 = await signer.signDelegationTypedData(typedData);
1031
- await auditSigning("haven_sign", payloadHash);
1032
- return { signature: signature2 };
1267
+ if (resolved?.kind !== "direct" && !isPackedUserOperationTypedData(typedData)) {
1268
+ throw new HavenTypedDataNotAllowedError(
1269
+ "This signer signs only Haven-prepared payloads: a direct-payment PackedUserOperation from your own account that redeems a delegation granted to it, or a Haven-signed x402 context. Call haven_sign with payment_id (preferred), or pass x402_expected for an x402 funding leg."
1270
+ );
1271
+ }
1272
+ try {
1273
+ assertUserOpTypedDataBinding(typedData, payloadHash);
1274
+ } catch (err) {
1275
+ if (err instanceof HavenUserOpBindingError) {
1276
+ throw new HavenUserOpBindingRefusedError(err.message);
1277
+ }
1278
+ throw err;
1279
+ }
1280
+ assertBoundDirectPaymentUserOp2(typedData, signer.delegateAddress);
1281
+ const signature = await signer.signDelegationTypedData(typedData);
1282
+ await auditSigning("haven_sign", hashTypedData(typedData));
1283
+ return { signature, ...clientUpdateField(resolved) };
1033
1284
  }
1034
- const signature = signer.signPaymentHash(payloadHash);
1035
- await auditSigning("haven_sign", payloadHash);
1036
- return { signature };
1285
+ throw new HavenBareHashRefusedError();
1037
1286
  }
1038
- await auditSigning("haven_sign", payloadHash);
1039
- return { signature: result.signature, x402_binding: result.x402Binding };
1287
+ await auditSigning("haven_sign", hashTypedData(typedData));
1288
+ return { signature: result.signature, x402_binding: result.x402Binding, ...clientUpdateField(resolved) };
1040
1289
  }),
1041
1290
  haven_x402_sign_header: async (input) => runTool(async () => {
1042
1291
  const args = parse("haven_x402_sign_header", coercePaymentRequired(input));
@@ -1052,7 +1301,8 @@ function createToolHandlers(signer, options = {}) {
1052
1301
  }),
1053
1302
  haven_sign_x402: async (input) => runTool(async () => {
1054
1303
  const args = parse("haven_sign_x402", coerceX402Expected(coercePaymentRequired(input)));
1055
- const fetched = await resolveSignContext(args);
1304
+ const resolved = await resolveSignContext(args, { allowDirectFallback: false });
1305
+ const fetched = resolved?.kind === "x402" ? resolved.ctx : void 0;
1056
1306
  const payloadHash = fetched?.payloadHash ?? args.payload_hash;
1057
1307
  const expectedRaw = fetched ? parseFetchedExpected(fetched) : args.x402_expected;
1058
1308
  if (!payloadHash || !expectedRaw) {
@@ -1060,12 +1310,8 @@ function createToolHandlers(signer, options = {}) {
1060
1310
  "Pass payment_id (preferred \u2014 the signer fetches the signing context itself) or payload_hash + x402_expected from the quote result."
1061
1311
  );
1062
1312
  }
1063
- const funding = await signFundingLeg(
1064
- signer,
1065
- toExpectedX402(expectedRaw),
1066
- payloadHash,
1067
- fetched?.typedData ?? resolveTypedData(args)
1068
- );
1313
+ const fundingTypedData = fetched?.typedData ?? resolveTypedData(args);
1314
+ const funding = await signFundingLeg(signer, toExpectedX402(expectedRaw), fundingTypedData);
1069
1315
  const paymentRequired = fetched?.paymentRequired ?? args.payment_required;
1070
1316
  if (!paymentRequired) {
1071
1317
  throw new HavenSigningError(
@@ -1076,13 +1322,17 @@ function createToolHandlers(signer, options = {}) {
1076
1322
  paymentRequired,
1077
1323
  funding.x402Binding
1078
1324
  );
1079
- await auditSigning("haven_sign_x402", payloadHash);
1325
+ await auditSigning(
1326
+ "haven_sign_x402",
1327
+ hashTypedData(fundingTypedData)
1328
+ );
1080
1329
  await auditSigning("haven_sign_x402", hashPayloadForAudit(paymentRequired));
1081
1330
  return {
1082
1331
  signature: funding.signature,
1083
1332
  x402_binding: funding.x402Binding,
1084
1333
  payment_header: header.paymentHeader,
1085
- accepted: header.accepted
1334
+ accepted: header.accepted,
1335
+ ...clientUpdateField(resolved)
1086
1336
  };
1087
1337
  }),
1088
1338
  haven_sign_sweep_delegate: async (input) => runTool(async () => {
@@ -1090,7 +1340,8 @@ function createToolHandlers(signer, options = {}) {
1090
1340
  const result = await signer.signSweepAuthorization({
1091
1341
  authorization: args.authorization,
1092
1342
  expectedAuth: args.expected_auth,
1093
- // Cross-check `to` against the Safe in the local credential when present.
1343
+ // Cross-check `to` against the agent's account (Haven wallet) from the
1344
+ // local credential, when the credential carries one.
1094
1345
  expectedSafe: options.audit?.accountAddress
1095
1346
  });
1096
1347
  await auditSigning(
@@ -1103,17 +1354,23 @@ function createToolHandlers(signer, options = {}) {
1103
1354
  async function auditSigning(tool, payloadHash) {
1104
1355
  if (!options.audit) return;
1105
1356
  const { auditPath, ...context } = options.audit;
1106
- await appendSigningAuditEntry(
1107
- createSigningAuditEntry(tool, payloadHash, {
1108
- ...context,
1109
- delegateAddress: signer.delegateAddress
1110
- }),
1111
- auditPath
1112
- );
1357
+ try {
1358
+ await appendSigningAuditEntry(
1359
+ createSigningAuditEntry(tool, payloadHash, {
1360
+ ...context,
1361
+ delegateAddress: signer.delegateAddress
1362
+ }),
1363
+ auditPath
1364
+ );
1365
+ } catch (err) {
1366
+ const message = err instanceof Error ? err.message : String(err);
1367
+ process.stderr.write(`haven-signer: warning: audit entry for ${tool} could not be written to ${auditPath}: ${message}
1368
+ `);
1369
+ }
1113
1370
  }
1114
1371
  }
1115
1372
  function parse(name, input) {
1116
- return z.object(toolSchemas[name]).parse(input ?? {});
1373
+ return z$1.object(toolSchemas[name]).parse(input ?? {});
1117
1374
  }
1118
1375
  function coerceX402Expected(input) {
1119
1376
  if (!input || typeof input !== "object") return input;
@@ -1152,7 +1409,7 @@ async function runTool(fn) {
1152
1409
  }
1153
1410
  }
1154
1411
  function normalizeError(err) {
1155
- if (err instanceof z.ZodError) {
1412
+ if (err instanceof z$1.ZodError) {
1156
1413
  return {
1157
1414
  success: false,
1158
1415
  code: "INVALID_INPUT",
@@ -1168,7 +1425,40 @@ function normalizeError(err) {
1168
1425
  supported_versions: [...err.supportedVersions],
1169
1426
  received_version: err.receivedVersion,
1170
1427
  fallback: err.fallback,
1171
- next_action: AgentPaymentNextAction.StopAndTellUser
1428
+ next_action: AgentPaymentNextAction.StopAndTellUser,
1429
+ // #3103: no tool can fix a version skew from inside the call.
1430
+ ...nextStepWireFields(signerRefusalStep({
1431
+ nextAction: AgentPaymentNextAction.StopAndTellUser,
1432
+ nextTool: null,
1433
+ nextToolOmittedReason: "update @haven_ai/signer by re-running the connector, then repeat the same call"
1434
+ }))
1435
+ };
1436
+ }
1437
+ if (err instanceof HavenBareHashRefusedError) {
1438
+ return {
1439
+ success: false,
1440
+ code: err.code,
1441
+ message: err.message,
1442
+ next_action: err.next_action,
1443
+ ...nextStepWireFields(err.step)
1444
+ };
1445
+ }
1446
+ if (err instanceof HavenUserOpBindingRefusedError) {
1447
+ return {
1448
+ success: false,
1449
+ code: err.code,
1450
+ message: err.message,
1451
+ next_action: err.next_action,
1452
+ ...nextStepWireFields(err.step)
1453
+ };
1454
+ }
1455
+ if (err instanceof HavenTypedDataNotAllowedError) {
1456
+ return {
1457
+ success: false,
1458
+ code: err.code,
1459
+ message: err.message,
1460
+ next_action: err.next_action,
1461
+ ...nextStepWireFields(err.step)
1172
1462
  };
1173
1463
  }
1174
1464
  if (err instanceof HavenSignContextError) {
@@ -1180,7 +1470,15 @@ function normalizeError(err) {
1180
1470
  ...err.fallback !== void 0 ? { fallback: err.fallback } : {},
1181
1471
  ...err.retry_with_new_quote ? { retry_with_new_quote: true } : {},
1182
1472
  ...err.http_status !== void 0 ? { http_status: err.http_status } : {},
1183
- ...err.backend_error_code !== void 0 ? { backend_error_code: err.backend_error_code } : {}
1473
+ ...err.backend_error_code !== void 0 ? { backend_error_code: err.backend_error_code } : {},
1474
+ ...err.client_update ? { client_update: err.client_update } : {},
1475
+ // #3103: the typed step the error decided beside its action.
1476
+ ...err.next_tool ? { next_tool: err.next_tool } : {},
1477
+ ...err.next_tool_server ? { next_tool_server: err.next_tool_server } : {},
1478
+ ...err.next_tool_name ? { next_tool_name: err.next_tool_name } : {},
1479
+ ...err.next_tool_server_role ? { next_tool_server_role: err.next_tool_server_role } : {},
1480
+ ...err.next_arguments ? { next_arguments: err.next_arguments } : {},
1481
+ ...err.next_tool_omitted_reason ? { next_tool_omitted_reason: err.next_tool_omitted_reason } : {}
1184
1482
  };
1185
1483
  }
1186
1484
  if (err instanceof HavenSigningError) {
@@ -1199,7 +1497,13 @@ function normalizeError(err) {
1199
1497
  ...err.code === AgentPaymentFailureCode.PaymentWindowExpired ? {
1200
1498
  next_action: AgentPaymentNextAction.PaymentWindowExpired,
1201
1499
  retry_with_new_quote: true,
1202
- suggested_tool: "haven_pay_mcp_tool"
1500
+ suggested_tool: "haven_pay_mcp_tool",
1501
+ // #3103: same omission as the hosted window-expired helper.
1502
+ ...nextStepWireFields(signerRefusalStep({
1503
+ nextAction: AgentPaymentNextAction.PaymentWindowExpired,
1504
+ nextTool: null,
1505
+ nextToolOmittedReason: "re-run the hosted quote tool you called with the same idempotency_key; which one depends on the flow (suggested_tool names the MCP one)"
1506
+ }))
1203
1507
  } : {}
1204
1508
  };
1205
1509
  }
@@ -1211,6 +1515,17 @@ function normalizeError(err) {
1211
1515
  }
1212
1516
 
1213
1517
  // src/consent.ts
1518
+ var toolSummaries = {
1519
+ haven_sign: "signs one Haven-prepared payment (a direct-payment UserOp from your own account whose only call redeems a delegation made to it, or an x402 funding intent against a Haven-signed context)",
1520
+ haven_x402_sign_header: "builds and signs the EIP-3009 merchant payment header for an x402 payment",
1521
+ haven_sign_x402: "the two above in one call: funding payload + merchant header",
1522
+ haven_sign_sweep_delegate: "signs a Haven-prepared gasless sweep of stranded funds back to your own account"
1523
+ };
1524
+ var CONNECTOR_DOCTOR_HINT = [
1525
+ "Wired by the Haven connector (Claude Desktop, Codex, ...)? Diagnose and repair with: npx @haven_ai/connect --doctor",
1526
+ " (the doctor shows this as a failed 'Signer stdio handshake' check; the connector's setup outcome reports it as",
1527
+ " local_signer_ack_required; the repair is the connector's --ack-local-tools)."
1528
+ ].join("\n");
1214
1529
  var SIGNER_ACK_ENV = "HAVEN_SIGNER_ACK";
1215
1530
  var SIGNER_CONSENT_SURFACE_VERSION = 2;
1216
1531
  function computeSignerConsentHash(input) {
@@ -1246,13 +1561,13 @@ function renderSignerConsentBlock(input, hash) {
1246
1561
  lines.push("payload from Haven, authenticated with the agent credential stored next to");
1247
1562
  lines.push("this signer's key file - it never sends the key, a signature, or anything");
1248
1563
  lines.push("else outbound, and it cannot show a live allowance summary.");
1249
- lines.push("On-chain Safe rules remain the real spend gate, and the wallet owner can");
1250
- lines.push("pause or revoke agent authority outside this signer.");
1564
+ lines.push("The agent's signed budget delegation is the real spend gate, and the wallet");
1565
+ lines.push("owner can pause or revoke agent authority outside this signer.");
1251
1566
  lines.push("");
1252
- lines.push("Tools this signer will expose to your agent runtime:");
1567
+ lines.push("Tools this signer will expose to your agent runtime (one line each; the");
1568
+ lines.push("full agent-facing descriptions are what the runtime sees):");
1253
1569
  for (const name of input.toolNames) {
1254
- lines.push(` - ${name}`);
1255
- lines.push(` ${toolDescriptions[name]}`);
1570
+ lines.push(` - ${name}: ${toolSummaries[name]}`);
1256
1571
  }
1257
1572
  lines.push("");
1258
1573
  lines.push("A local audit entry is appended for every signing operation. Audit entries");
@@ -1266,6 +1581,8 @@ function renderSignerConsentBlock(input, hash) {
1266
1581
  lines.push(" - re-run with --ack to write the acknowledgement next to your");
1267
1582
  lines.push(" credential file (sidecar <credentials>.signer-ack.json).");
1268
1583
  lines.push("");
1584
+ lines.push(CONNECTOR_DOCTOR_HINT);
1585
+ lines.push("");
1269
1586
  lines.push("------------------------------------------------------------");
1270
1587
  lines.push("");
1271
1588
  return lines.join("\n");
@@ -1329,7 +1646,7 @@ async function writeAckFile(path, hash) {
1329
1646
  );
1330
1647
  }
1331
1648
  var SIGNER_NAME = "@haven_ai/signer";
1332
- var SIGNER_VERSION = "0.3.0-alpha.0";
1649
+ var SIGNER_VERSION = "0.5.0-alpha.1";
1333
1650
  async function resolveEdgeSigner(options = {}) {
1334
1651
  const { signer } = await resolveSignerRuntime(options);
1335
1652
  return signer;
@@ -1371,13 +1688,17 @@ function buildSignerMcpServer(signer, options = {}) {
1371
1688
  accountAddress: options.credentials?.accountAddress,
1372
1689
  chainId: options.credentials?.chainId
1373
1690
  },
1374
- // #1263: the payment_id signing path — the ONLY network call this server
1375
- // can make, an authenticated read of a signing context from Haven, using
1691
+ // #1263: the payment_id signing path — the ONLY network path this server
1692
+ // has: up to two authenticated reads of a signing context from Haven (x402,
1693
+ // then direct for haven_sign, #3271), using
1376
1694
  // the agent identity the connector stores next to the signer credential.
1377
1695
  // The signer CORE stays network-free; fetched bytes still pass the same
1378
1696
  // binding verification + digest re-derivation as tool-argument bytes.
1379
1697
  signContext: {
1380
- loadIdentity: () => loadHavenIdentity(credentialsPath)
1698
+ loadIdentity: () => loadHavenIdentity(credentialsPath),
1699
+ // #3303: the backend refuses a signer below a minimum it has set, at
1700
+ // sign-context — the one place this signer meets it.
1701
+ clientIdentity: havenClientIdentity(SIGNER_NAME, SIGNER_VERSION)
1381
1702
  }
1382
1703
  });
1383
1704
  const registerTool = server.tool.bind(server);
@@ -1405,7 +1726,7 @@ async function runSignerStdioServer(options = {}) {
1405
1726
  const decision = await runSignerConsentGate(signer, credentials, options);
1406
1727
  if (!decision.ok) {
1407
1728
  const err = new Error(
1408
- decision.reason === "env_var_mismatch" ? "Haven edge signer consent acknowledgement does not match the current configuration." : "Haven edge signer requires a one-time consent acknowledgement before starting."
1729
+ (decision.reason === "env_var_mismatch" ? "Haven edge signer consent acknowledgement does not match the current configuration." : "Haven edge signer requires a one-time consent acknowledgement before starting.") + " See the consent block above; if the Haven connector wired this signer, run: npx @haven_ai/connect --doctor"
1409
1730
  );
1410
1731
  err.code = "HAVEN_SIGNER_NO_CONSENT";
1411
1732
  throw err;
@@ -1439,6 +1760,6 @@ function toMcpResult(payload) {
1439
1760
  };
1440
1761
  }
1441
1762
 
1442
- export { SIGNER_ACK_ENV, SIGNER_CAPABILITY_KEY, SIGNER_NAME, SIGNER_VERSION, SUPPORTED_SWEEP_BINDING_VERSIONS, SUPPORTED_X402_EXPECTED_VERSIONS, appendSigningAuditEntry, assertSupportedBindingVersion, assertSupportedNodeVersion, assertX402MatchesExpected, buildSignerMcpServer, computeSignerConsentHash, createEdgeSigner, createSigningAuditEntry, createToolHandlers, defaultSigningAuditPath, ensureSignerConsent, hashPayloadForAudit, loadSignerCredentials, readAccountAddressEnv, readAccountAddressField, renderSignerConsentBlock, resolveEdgeSigner, resolveSignerRuntime, runSignerConsentGate, runSignerStdioServer, signerCapabilityAdvertisement, signerCompatibility, signerInstructions, toolDescriptions, toolSchemas, warnIfCredentialFilePermissive };
1763
+ export { AUDIT_ROTATE_BYTES, OWNER_ONLY_MODE, SIGNER_ACK_ENV, SIGNER_CAPABILITY_KEY, SIGNER_HOSTED_HANDOFF_SHAPES, SIGNER_NAME, SIGNER_VERSION, SUPPORTED_SWEEP_BINDING_VERSIONS, SUPPORTED_X402_EXPECTED_VERSIONS, appendSigningAuditEntry, assertSupportedBindingVersion, assertSupportedNodeVersion, assertX402MatchesExpected, buildSignerMcpServer, computeSignerConsentHash, createEdgeSigner, createSigningAuditEntry, createToolHandlers, defaultSigningAuditPath, ensureSignerConsent, hashPayloadForAudit, loadSignerCredentials, permissiveMode, readAccountAddressEnv, readAccountAddressField, renderSignerConsentBlock, resolveEdgeSigner, resolveSignerRuntime, runSignerConsentGate, runSignerStdioServer, signerCapabilityAdvertisement, signerCompatibility, signerInstructions, tightenIfFilePermissive, toolDescriptions, toolSchemas, warnIfCredentialFilePermissive, warnIfFilePermissive };
1443
1764
  //# sourceMappingURL=index.js.map
1444
1765
  //# sourceMappingURL=index.js.map