@haven_ai/signer 0.3.0-alpha.0 → 0.4.0-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -11,6 +11,7 @@ var stdio_js = require('@modelcontextprotocol/sdk/server/stdio.js');
11
11
  var os = require('os');
12
12
  var path = require('path');
13
13
  var v3 = require('zod/v3');
14
+ var zod = require('zod');
14
15
 
15
16
  // src/core.ts
16
17
  var DELEGATION_MANAGER = "0xdb9B1e94B5b69Df7e401DDbedE43491141047dB3";
@@ -496,7 +497,10 @@ function signerInstructions() {
496
497
  "",
497
498
  "A version-mismatch refusal from haven_sign / haven_sign_x402 / haven_sign_sweep_delegate is",
498
499
  "machine-readable, not just prose: it carries code, supported_versions, received_version, and",
499
- "fallback fields alongside the message, so you can branch on it directly."
500
+ "fallback fields alongside the message, so you can branch on it directly. Every signer",
501
+ "refusal also carries the next-step family: next_tool_name + next_tool_server_role when a",
502
+ "hosted tool follows (resolve the role against your own server names), else",
503
+ "next_tool_omitted_reason saying why not."
500
504
  ].join("\n");
501
505
  }
502
506
  async function loadSignerCredentials(path = process.env.HAVEN_CREDENTIALS) {
@@ -642,6 +646,38 @@ function stableStringify(value) {
642
646
  const object = value;
643
647
  return `{${Object.keys(object).sort().map((key) => `${JSON.stringify(key)}:${stableStringify(object[key])}`).join(",")}}`;
644
648
  }
649
+ var SIGNER_HOSTED_HANDOFF_SHAPES = {
650
+ haven_get_payment_status: { payment_id: zod.z.string().min(1) }
651
+ };
652
+ function target(shape) {
653
+ const schema = zod.z.object(shape).strict();
654
+ return {
655
+ role: "hosted",
656
+ validate: (input) => {
657
+ const r = schema.safeParse(input ?? {});
658
+ return r.success ? null : r.error.errors.map((e) => `${e.path.join(".") || "(root)"}: ${e.message}`).join("; ");
659
+ }
660
+ };
661
+ }
662
+ var SIGNER_NEXT_STEP_TARGETS = {
663
+ haven_get_payment_status: target(SIGNER_HOSTED_HANDOFF_SHAPES.haven_get_payment_status)
664
+ };
665
+ var nextStep = sdk.createNextStepBuilder(SIGNER_NEXT_STEP_TARGETS);
666
+ function signerRefusalStep(input) {
667
+ return nextStep({ ...input, safeToContinue: false, reason: "" });
668
+ }
669
+ function nextStepWireFields(step) {
670
+ return {
671
+ ...step.next_tool ? { next_tool: step.next_tool } : {},
672
+ ...step.next_tool_server ? { next_tool_server: step.next_tool_server } : {},
673
+ ...step.next_tool_name ? { next_tool_name: step.next_tool_name } : {},
674
+ ...step.next_tool_server_role ? { next_tool_server_role: step.next_tool_server_role } : {},
675
+ ...step.next_arguments ? { next_arguments: step.next_arguments } : {},
676
+ ...step.next_tool_omitted_reason ? { next_tool_omitted_reason: step.next_tool_omitted_reason } : {}
677
+ };
678
+ }
679
+
680
+ // src/sign-context.ts
645
681
  var HavenSignContextError = class extends sdk.HavenSigningError {
646
682
  /**
647
683
  * #3001 / #3010 review: the recovery path, when signing OTHER bytes is
@@ -661,28 +697,63 @@ var HavenSignContextError = class extends sdk.HavenSigningError {
661
697
  * plain `HavenError` branch already does for that code.
662
698
  */
663
699
  next_action;
700
+ /**
701
+ * #3103 (epic #3105, decisions 1 and 3): the typed next step beside the
702
+ * action. A refusal Haven made (`SIGN_CONTEXT_REFUSED`, not expired) names
703
+ * the hosted status read with the payment id; a transport failure or a
704
+ * malformed body names no tool — the remedy is re-running the SAME quote
705
+ * tool with `include_signing_payload: true` — and an expired window names
706
+ * none either (which quote tool depends on the flow). Never null: a step
707
+ * with no tool carries `next_tool_omitted_reason`. Additive: the class is
708
+ * not exported from the package index — the published surface is the
709
+ * `ToolFailure` envelope `tools.ts` builds from these fields — and every
710
+ * constructor call site is in this file.
711
+ */
712
+ next_tool;
713
+ next_tool_server;
714
+ next_tool_name;
715
+ next_tool_server_role;
716
+ next_arguments;
717
+ next_tool_omitted_reason;
664
718
  retry_with_new_quote;
665
719
  /** Present only for `SIGN_CONTEXT_REFUSED` (the backend's HTTP status). */
666
720
  http_status;
667
721
  /** Present only for `SIGN_CONTEXT_REFUSED`: the backend's own `error_code`. */
668
722
  backend_error_code;
669
- constructor(message, code, refusal) {
723
+ constructor(message, code, refusal, paymentId) {
670
724
  super(message);
671
725
  this.code = code;
672
726
  this.name = "HavenSignContextError";
727
+ let step;
673
728
  if (code === "SIGN_CONTEXT_REFUSED") {
674
729
  this.http_status = refusal?.httpStatus;
675
730
  this.backend_error_code = refusal?.errorCode;
676
731
  if (refusal?.httpStatus === 410 || refusal?.errorCode === "expired") {
677
732
  this.next_action = sdk.AgentPaymentNextAction.PaymentWindowExpired;
678
733
  this.retry_with_new_quote = true;
734
+ step = signerRefusalStep({
735
+ nextAction: sdk.AgentPaymentNextAction.PaymentWindowExpired,
736
+ nextTool: null,
737
+ nextToolOmittedReason: "re-run the hosted quote tool you called with the same idempotency_key; which one depends on the flow"
738
+ });
679
739
  } else {
680
740
  this.next_action = sdk.AgentPaymentNextAction.StopAndTellUser;
741
+ step = signerRefusalStep({
742
+ nextAction: sdk.AgentPaymentNextAction.StopAndTellUser,
743
+ nextTool: "haven_get_payment_status",
744
+ nextArguments: { payment_id: paymentId }
745
+ });
681
746
  }
682
747
  } else {
683
748
  this.fallback = "typed_data_b64";
684
749
  this.next_action = sdk.AgentPaymentNextAction.StopAndTellUser;
750
+ step = signerRefusalStep({
751
+ nextAction: sdk.AgentPaymentNextAction.StopAndTellUser,
752
+ nextTool: null,
753
+ nextToolOmittedReason: "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"
754
+ });
685
755
  }
756
+ Object.assign(this, nextStepWireFields(step));
686
757
  }
687
758
  };
688
759
  async function loadHavenIdentity(credentialsPath) {
@@ -714,7 +785,9 @@ async function fetchX402SignContext(identity, paymentId, fetchImpl = fetch, time
714
785
  const timedOut = err instanceof Error && (err.name === "TimeoutError" || err.name === "AbortError");
715
786
  throw new HavenSignContextError(
716
787
  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.`,
717
- timedOut ? "SIGN_CONTEXT_TIMEOUT" : "SIGN_CONTEXT_UNREACHABLE"
788
+ timedOut ? "SIGN_CONTEXT_TIMEOUT" : "SIGN_CONTEXT_UNREACHABLE",
789
+ void 0,
790
+ paymentId
718
791
  );
719
792
  }
720
793
  let body;
@@ -724,7 +797,9 @@ async function fetchX402SignContext(identity, paymentId, fetchImpl = fetch, time
724
797
  if (err instanceof Error && (err.name === "TimeoutError" || err.name === "AbortError")) {
725
798
  throw new HavenSignContextError(
726
799
  `Haven did not finish sending the signing context for ${paymentId} within ${timeoutMs} ms. Retry, or pass typed_data_b64 from the quote result instead.`,
727
- "SIGN_CONTEXT_TIMEOUT"
800
+ "SIGN_CONTEXT_TIMEOUT",
801
+ void 0,
802
+ paymentId
728
803
  );
729
804
  }
730
805
  body = {};
@@ -734,7 +809,8 @@ async function fetchX402SignContext(identity, paymentId, fetchImpl = fetch, time
734
809
  throw new HavenSignContextError(
735
810
  `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." : ""),
736
811
  "SIGN_CONTEXT_REFUSED",
737
- { httpStatus: response.status, errorCode: typeof body.error_code === "string" ? body.error_code : void 0 }
812
+ { httpStatus: response.status, errorCode: typeof body.error_code === "string" ? body.error_code : void 0 },
813
+ paymentId
738
814
  );
739
815
  }
740
816
  const signData = body.sign_data;
@@ -742,7 +818,9 @@ async function fetchX402SignContext(identity, paymentId, fetchImpl = fetch, time
742
818
  if (!signData || typeof signData.hash !== "string" || !signData.typed_data || typeof signData.typed_data !== "object" || !x402Expected) {
743
819
  throw new HavenSignContextError(
744
820
  "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.",
745
- "SIGN_CONTEXT_MALFORMED"
821
+ "SIGN_CONTEXT_MALFORMED",
822
+ void 0,
823
+ paymentId
746
824
  );
747
825
  }
748
826
  const paymentRequired = body.payment_required;
@@ -866,6 +944,7 @@ var toolSchemas = {
866
944
  // #1255: see haven_sign.typed_data_b64 — the copy-through-safe form.
867
945
  typed_data_b64: v3.z.string().min(1).max(262144).optional()
868
946
  }
947
+ // #3101: keys survive on the type (see the hosted server's contracts.ts).
869
948
  };
870
949
  var SIGN_DESCRIPTION = [
871
950
  "Sign an unsigned Haven payment hash with the local delegate key. The delegate key never leaves",
@@ -1170,7 +1249,13 @@ function normalizeError(err) {
1170
1249
  supported_versions: [...err.supportedVersions],
1171
1250
  received_version: err.receivedVersion,
1172
1251
  fallback: err.fallback,
1173
- next_action: sdk.AgentPaymentNextAction.StopAndTellUser
1252
+ next_action: sdk.AgentPaymentNextAction.StopAndTellUser,
1253
+ // #3103: no tool can fix a version skew from inside the call.
1254
+ ...nextStepWireFields(signerRefusalStep({
1255
+ nextAction: sdk.AgentPaymentNextAction.StopAndTellUser,
1256
+ nextTool: null,
1257
+ nextToolOmittedReason: "update @haven_ai/signer by re-running the connector, then repeat the same call"
1258
+ }))
1174
1259
  };
1175
1260
  }
1176
1261
  if (err instanceof HavenSignContextError) {
@@ -1182,7 +1267,14 @@ function normalizeError(err) {
1182
1267
  ...err.fallback !== void 0 ? { fallback: err.fallback } : {},
1183
1268
  ...err.retry_with_new_quote ? { retry_with_new_quote: true } : {},
1184
1269
  ...err.http_status !== void 0 ? { http_status: err.http_status } : {},
1185
- ...err.backend_error_code !== void 0 ? { backend_error_code: err.backend_error_code } : {}
1270
+ ...err.backend_error_code !== void 0 ? { backend_error_code: err.backend_error_code } : {},
1271
+ // #3103: the typed step the error decided beside its action.
1272
+ ...err.next_tool ? { next_tool: err.next_tool } : {},
1273
+ ...err.next_tool_server ? { next_tool_server: err.next_tool_server } : {},
1274
+ ...err.next_tool_name ? { next_tool_name: err.next_tool_name } : {},
1275
+ ...err.next_tool_server_role ? { next_tool_server_role: err.next_tool_server_role } : {},
1276
+ ...err.next_arguments ? { next_arguments: err.next_arguments } : {},
1277
+ ...err.next_tool_omitted_reason ? { next_tool_omitted_reason: err.next_tool_omitted_reason } : {}
1186
1278
  };
1187
1279
  }
1188
1280
  if (err instanceof sdk.HavenSigningError) {
@@ -1201,7 +1293,13 @@ function normalizeError(err) {
1201
1293
  ...err.code === sdk.AgentPaymentFailureCode.PaymentWindowExpired ? {
1202
1294
  next_action: sdk.AgentPaymentNextAction.PaymentWindowExpired,
1203
1295
  retry_with_new_quote: true,
1204
- suggested_tool: "haven_pay_mcp_tool"
1296
+ suggested_tool: "haven_pay_mcp_tool",
1297
+ // #3103: same omission as the hosted window-expired helper.
1298
+ ...nextStepWireFields(signerRefusalStep({
1299
+ nextAction: sdk.AgentPaymentNextAction.PaymentWindowExpired,
1300
+ nextTool: null,
1301
+ 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)"
1302
+ }))
1205
1303
  } : {}
1206
1304
  };
1207
1305
  }
@@ -1331,7 +1429,7 @@ async function writeAckFile(path$1, hash) {
1331
1429
  );
1332
1430
  }
1333
1431
  var SIGNER_NAME = "@haven_ai/signer";
1334
- var SIGNER_VERSION = "0.3.0-alpha.0";
1432
+ var SIGNER_VERSION = "0.4.0-alpha.0";
1335
1433
  async function resolveEdgeSigner(options = {}) {
1336
1434
  const { signer } = await resolveSignerRuntime(options);
1337
1435
  return signer;
@@ -1443,6 +1541,7 @@ function toMcpResult(payload) {
1443
1541
 
1444
1542
  exports.SIGNER_ACK_ENV = SIGNER_ACK_ENV;
1445
1543
  exports.SIGNER_CAPABILITY_KEY = SIGNER_CAPABILITY_KEY;
1544
+ exports.SIGNER_HOSTED_HANDOFF_SHAPES = SIGNER_HOSTED_HANDOFF_SHAPES;
1446
1545
  exports.SIGNER_NAME = SIGNER_NAME;
1447
1546
  exports.SIGNER_VERSION = SIGNER_VERSION;
1448
1547
  exports.SUPPORTED_SWEEP_BINDING_VERSIONS = SUPPORTED_SWEEP_BINDING_VERSIONS;