@haven_ai/signer 0.0.0-dev.202609250737.3bd5a51 → 0.0.0-dev.202609251409.98ed67a

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.d.cts CHANGED
@@ -1,4 +1,4 @@
1
- import { X402ExpectedAuth, X402PaymentRequired, X402PaymentOption, SweepAuthorization, SweepExpectedAuth } from '@haven_ai/sdk/edge';
1
+ import { X402ExpectedAuth, X402PaymentRequired, X402PaymentOption, SweepAuthorization, SweepExpectedAuth, HavenClientUpdate } from '@haven_ai/sdk/edge';
2
2
  export { deriveDelegateAccountAddress } from '@haven_ai/sdk/edge';
3
3
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
4
4
  import { z as z$1 } from 'zod/v3';
@@ -725,8 +725,10 @@ interface ToolFailure {
725
725
  * the direct `/payments/:id/sign-context` fetch since #3271.
726
726
  */
727
727
  http_status?: number;
728
- /** #3001: the backend's own `error_code` on `SIGN_CONTEXT_REFUSED` (`expired`, `already_executed`, `not_signable`, `sign_context_unavailable`). */
728
+ /** #3001: the backend's own `error_code` on `SIGN_CONTEXT_REFUSED` (`expired`, `already_executed`, `not_signable`, `sign_context_unavailable`, and since #3303 `client_outdated`). */
729
729
  backend_error_code?: string;
730
+ /** #3303: on `client_outdated`, the backend's update hint — `upgrade_command` is what updates this signer. */
731
+ client_update?: HavenClientUpdate;
730
732
  }
731
733
  type ToolPayload<T = unknown> = ToolSuccess<T> | ToolFailure;
732
734
  interface ToolHandlerOptions {
@@ -741,6 +743,8 @@ interface ToolHandlerOptions {
741
743
  signContext?: {
742
744
  loadIdentity: () => Promise<HavenIdentity | null>;
743
745
  fetchImpl?: typeof fetch;
746
+ /** #3303: `@haven_ai/signer/<version>`, sent as `X-Haven-Client` on every sign-context read. */
747
+ clientIdentity?: string;
744
748
  };
745
749
  }
746
750
  declare function createToolHandlers(signer: EdgeSigner, options?: ToolHandlerOptions): Record<SignerToolName, (input: unknown) => Promise<ToolPayload>>;
@@ -772,7 +776,7 @@ declare function renderSignerConsentBlock(input: SignerConsentInput, hash: strin
772
776
  declare function ensureSignerConsent(input: SignerConsentInput, options?: SignerConsentOptions): Promise<SignerConsentDecision>;
773
777
 
774
778
  declare const SIGNER_NAME = "@haven_ai/signer";
775
- declare const SIGNER_VERSION = "0.0.0-dev.202609250737.3bd5a51";
779
+ declare const SIGNER_VERSION = "0.0.0-dev.202609251409.98ed67a";
776
780
  interface SignerOptions {
777
781
  /** Path to a Haven credential JSON file (delegate_key is read from it). */
778
782
  credentialsPath?: string;
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { X402ExpectedAuth, X402PaymentRequired, X402PaymentOption, SweepAuthorization, SweepExpectedAuth } from '@haven_ai/sdk/edge';
1
+ import { X402ExpectedAuth, X402PaymentRequired, X402PaymentOption, SweepAuthorization, SweepExpectedAuth, HavenClientUpdate } from '@haven_ai/sdk/edge';
2
2
  export { deriveDelegateAccountAddress } from '@haven_ai/sdk/edge';
3
3
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
4
4
  import { z as z$1 } from 'zod/v3';
@@ -725,8 +725,10 @@ interface ToolFailure {
725
725
  * the direct `/payments/:id/sign-context` fetch since #3271.
726
726
  */
727
727
  http_status?: number;
728
- /** #3001: the backend's own `error_code` on `SIGN_CONTEXT_REFUSED` (`expired`, `already_executed`, `not_signable`, `sign_context_unavailable`). */
728
+ /** #3001: the backend's own `error_code` on `SIGN_CONTEXT_REFUSED` (`expired`, `already_executed`, `not_signable`, `sign_context_unavailable`, and since #3303 `client_outdated`). */
729
729
  backend_error_code?: string;
730
+ /** #3303: on `client_outdated`, the backend's update hint — `upgrade_command` is what updates this signer. */
731
+ client_update?: HavenClientUpdate;
730
732
  }
731
733
  type ToolPayload<T = unknown> = ToolSuccess<T> | ToolFailure;
732
734
  interface ToolHandlerOptions {
@@ -741,6 +743,8 @@ interface ToolHandlerOptions {
741
743
  signContext?: {
742
744
  loadIdentity: () => Promise<HavenIdentity | null>;
743
745
  fetchImpl?: typeof fetch;
746
+ /** #3303: `@haven_ai/signer/<version>`, sent as `X-Haven-Client` on every sign-context read. */
747
+ clientIdentity?: string;
744
748
  };
745
749
  }
746
750
  declare function createToolHandlers(signer: EdgeSigner, options?: ToolHandlerOptions): Record<SignerToolName, (input: unknown) => Promise<ToolPayload>>;
@@ -772,7 +776,7 @@ declare function renderSignerConsentBlock(input: SignerConsentInput, hash: strin
772
776
  declare function ensureSignerConsent(input: SignerConsentInput, options?: SignerConsentOptions): Promise<SignerConsentDecision>;
773
777
 
774
778
  declare const SIGNER_NAME = "@haven_ai/signer";
775
- declare const SIGNER_VERSION = "0.0.0-dev.202609250737.3bd5a51";
779
+ declare const SIGNER_VERSION = "0.0.0-dev.202609251409.98ed67a";
776
780
  interface SignerOptions {
777
781
  /** Path to a Haven credential JSON file (delegate_key is read from it). */
778
782
  credentialsPath?: string;
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { randomUUID, createHash } from 'crypto';
2
2
  import { recoverTypedDataAddress, hashTypedData, hashMessage } from 'viem';
3
3
  import { privateKeyToAccount } from 'viem/accounts';
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, isSupportedNodeVersion, unsupportedNodeVersionMessage, HavenUserOpBindingError } from '@haven_ai/sdk/edge';
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
5
  export { deriveDelegateAccountAddress } from '@haven_ai/sdk/edge';
6
6
  import { stat, lstat, chmod, readFile, mkdir, rename, appendFile, writeFile } from 'fs/promises';
7
7
  import { resolve, dirname, join } from 'path';
@@ -430,6 +430,12 @@ var HavenSignContextError = class extends HavenSigningError {
430
430
  http_status;
431
431
  /** Present only for `SIGN_CONTEXT_REFUSED`: the backend's own `error_code`. */
432
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;
433
439
  constructor(message, code, refusal, paymentId, flow = "x402") {
434
440
  super(message);
435
441
  this.code = code;
@@ -438,7 +444,15 @@ var HavenSignContextError = class extends HavenSigningError {
438
444
  if (code === "SIGN_CONTEXT_REFUSED") {
439
445
  this.http_status = refusal?.httpStatus;
440
446
  this.backend_error_code = refusal?.errorCode;
441
- if (flow === "direct" && refusal?.httpStatus === 404) {
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) {
442
456
  this.fallback = "typed_data_b64";
443
457
  this.next_action = AgentPaymentNextAction.StopAndTellUser;
444
458
  step = signerRefusalStep({
@@ -496,13 +510,26 @@ async function loadHavenIdentity(credentialsPath) {
496
510
  }
497
511
  }
498
512
  var SIGN_CONTEXT_TIMEOUT_MS = 15e3;
499
- async function fetchX402SignContext(identity, paymentId, fetchImpl = fetch, timeoutMs = SIGN_CONTEXT_TIMEOUT_MS) {
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) {
500
527
  let response;
501
528
  try {
502
529
  response = await fetchImpl(
503
530
  `${identity.apiUrl}/x402/${encodeURIComponent(paymentId)}/sign-context`,
504
531
  {
505
- headers: { Authorization: `Bearer ${identity.apiKey}` },
532
+ headers: signContextHeaders(identity, clientIdentity),
506
533
  signal: AbortSignal.timeout(timeoutMs)
507
534
  }
508
535
  );
@@ -534,7 +561,7 @@ async function fetchX402SignContext(identity, paymentId, fetchImpl = fetch, time
534
561
  throw new HavenSignContextError(
535
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." : ""),
536
563
  "SIGN_CONTEXT_REFUSED",
537
- { httpStatus: response.status, errorCode: typeof body.error_code === "string" ? body.error_code : void 0 },
564
+ refusalOf(response.status, body),
538
565
  paymentId
539
566
  );
540
567
  }
@@ -554,17 +581,18 @@ async function fetchX402SignContext(identity, paymentId, fetchImpl = fetch, time
554
581
  payloadHash: signData.hash,
555
582
  typedData: signData.typed_data,
556
583
  x402Expected,
557
- paymentRequired: paymentRequired && typeof paymentRequired === "object" && !Array.isArray(paymentRequired) ? paymentRequired : null
584
+ paymentRequired: paymentRequired && typeof paymentRequired === "object" && !Array.isArray(paymentRequired) ? paymentRequired : null,
585
+ clientUpdate: readClientUpdate(body.client_update)
558
586
  };
559
587
  }
560
588
  var SUPPORTED_DIRECT_SIGN_CONTEXT_VERSIONS = [DIRECT_SIGN_CONTEXT_VERSION];
561
- async function fetchDirectSignContext(identity, paymentId, fetchImpl = fetch, timeoutMs = SIGN_CONTEXT_TIMEOUT_MS) {
589
+ async function fetchDirectSignContext(identity, paymentId, fetchImpl = fetch, timeoutMs = SIGN_CONTEXT_TIMEOUT_MS, clientIdentity) {
562
590
  let response;
563
591
  try {
564
592
  response = await fetchImpl(
565
593
  `${identity.apiUrl}/payments/${encodeURIComponent(paymentId)}/sign-context`,
566
594
  {
567
- headers: { Authorization: `Bearer ${identity.apiKey}` },
595
+ headers: signContextHeaders(identity, clientIdentity),
568
596
  signal: AbortSignal.timeout(timeoutMs)
569
597
  }
570
598
  );
@@ -598,7 +626,7 @@ async function fetchDirectSignContext(identity, paymentId, fetchImpl = fetch, ti
598
626
  throw new HavenSignContextError(
599
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." : ""),
600
628
  "SIGN_CONTEXT_REFUSED",
601
- { httpStatus: response.status, errorCode: typeof body.error_code === "string" ? body.error_code : void 0 },
629
+ refusalOf(response.status, body),
602
630
  paymentId,
603
631
  "direct"
604
632
  );
@@ -619,7 +647,8 @@ async function fetchDirectSignContext(identity, paymentId, fetchImpl = fetch, ti
619
647
  payloadHash: signData.hash,
620
648
  typedData: signData.typed_data,
621
649
  status: typeof body.status === "string" ? body.status : "pending_signature",
622
- expiresAt: typeof body.expires_at === "string" ? body.expires_at : void 0
650
+ expiresAt: typeof body.expires_at === "string" ? body.expires_at : void 0,
651
+ clientUpdate: readClientUpdate(body.client_update)
623
652
  };
624
653
  }
625
654
 
@@ -1146,6 +1175,9 @@ function parseFetchedExpected(fetched) {
1146
1175
  }
1147
1176
  return parsed.data;
1148
1177
  }
1178
+ function clientUpdateField(resolved) {
1179
+ return resolved?.ctx.clientUpdate ? { client_update: resolved.ctx.clientUpdate } : {};
1180
+ }
1149
1181
  function resolveTypedData(args) {
1150
1182
  if (!args.typed_data_b64) return args.typed_data;
1151
1183
  try {
@@ -1179,14 +1211,26 @@ function createToolHandlers(signer, options = {}) {
1179
1211
  );
1180
1212
  }
1181
1213
  try {
1182
- const ctx = await fetchX402SignContext(identity, args.payment_id, options.signContext?.fetchImpl);
1214
+ const ctx = await fetchX402SignContext(
1215
+ identity,
1216
+ args.payment_id,
1217
+ options.signContext?.fetchImpl,
1218
+ void 0,
1219
+ options.signContext?.clientIdentity
1220
+ );
1183
1221
  checkPayloadHashMatch(args.payload_hash, ctx.payloadHash, args.payment_id);
1184
1222
  return { kind: "x402", ctx };
1185
1223
  } catch (err) {
1186
1224
  if (opts.allowDirectFallback && err instanceof HavenSignContextError && err.http_status === 409 && err.backend_error_code === "sign_context_unavailable") {
1187
1225
  let ctx;
1188
1226
  try {
1189
- ctx = await fetchDirectSignContext(identity, args.payment_id, options.signContext?.fetchImpl);
1227
+ ctx = await fetchDirectSignContext(
1228
+ identity,
1229
+ args.payment_id,
1230
+ options.signContext?.fetchImpl,
1231
+ void 0,
1232
+ options.signContext?.clientIdentity
1233
+ );
1190
1234
  } catch (directErr) {
1191
1235
  if (directErr instanceof HavenSignContextError && directErr.http_status === 409 && directErr.backend_error_code === "sign_context_unavailable") {
1192
1236
  throw err;
@@ -1236,12 +1280,12 @@ function createToolHandlers(signer, options = {}) {
1236
1280
  assertBoundDirectPaymentUserOp2(typedData, signer.delegateAddress);
1237
1281
  const signature = await signer.signDelegationTypedData(typedData);
1238
1282
  await auditSigning("haven_sign", hashTypedData(typedData));
1239
- return { signature };
1283
+ return { signature, ...clientUpdateField(resolved) };
1240
1284
  }
1241
1285
  throw new HavenBareHashRefusedError();
1242
1286
  }
1243
1287
  await auditSigning("haven_sign", hashTypedData(typedData));
1244
- return { signature: result.signature, x402_binding: result.x402Binding };
1288
+ return { signature: result.signature, x402_binding: result.x402Binding, ...clientUpdateField(resolved) };
1245
1289
  }),
1246
1290
  haven_x402_sign_header: async (input) => runTool(async () => {
1247
1291
  const args = parse("haven_x402_sign_header", coercePaymentRequired(input));
@@ -1287,7 +1331,8 @@ function createToolHandlers(signer, options = {}) {
1287
1331
  signature: funding.signature,
1288
1332
  x402_binding: funding.x402Binding,
1289
1333
  payment_header: header.paymentHeader,
1290
- accepted: header.accepted
1334
+ accepted: header.accepted,
1335
+ ...clientUpdateField(resolved)
1291
1336
  };
1292
1337
  }),
1293
1338
  haven_sign_sweep_delegate: async (input) => runTool(async () => {
@@ -1426,6 +1471,7 @@ function normalizeError(err) {
1426
1471
  ...err.retry_with_new_quote ? { retry_with_new_quote: true } : {},
1427
1472
  ...err.http_status !== void 0 ? { http_status: err.http_status } : {},
1428
1473
  ...err.backend_error_code !== void 0 ? { backend_error_code: err.backend_error_code } : {},
1474
+ ...err.client_update ? { client_update: err.client_update } : {},
1429
1475
  // #3103: the typed step the error decided beside its action.
1430
1476
  ...err.next_tool ? { next_tool: err.next_tool } : {},
1431
1477
  ...err.next_tool_server ? { next_tool_server: err.next_tool_server } : {},
@@ -1600,7 +1646,7 @@ async function writeAckFile(path, hash) {
1600
1646
  );
1601
1647
  }
1602
1648
  var SIGNER_NAME = "@haven_ai/signer";
1603
- var SIGNER_VERSION = "0.0.0-dev.202609250737.3bd5a51";
1649
+ var SIGNER_VERSION = "0.0.0-dev.202609251409.98ed67a";
1604
1650
  async function resolveEdgeSigner(options = {}) {
1605
1651
  const { signer } = await resolveSignerRuntime(options);
1606
1652
  return signer;
@@ -1649,7 +1695,10 @@ function buildSignerMcpServer(signer, options = {}) {
1649
1695
  // The signer CORE stays network-free; fetched bytes still pass the same
1650
1696
  // binding verification + digest re-derivation as tool-argument bytes.
1651
1697
  signContext: {
1652
- 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)
1653
1702
  }
1654
1703
  });
1655
1704
  const registerTool = server.tool.bind(server);