@haven_ai/signer 0.5.0-alpha.1 → 0.6.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
@@ -476,6 +476,13 @@ var HavenSignContextError = class extends edge.HavenSigningError {
476
476
  nextTool: null,
477
477
  nextToolOmittedReason: "re-run the hosted quote tool you called with the same idempotency_key; which one depends on the flow"
478
478
  });
479
+ } else if (flow === "task") {
480
+ this.next_action = edge.AgentPaymentNextAction.StopAndTellUser;
481
+ step = signerRefusalStep({
482
+ nextAction: edge.AgentPaymentNextAction.StopAndTellUser,
483
+ nextTool: null,
484
+ nextToolOmittedReason: `re-check this task budget's status (id ${paymentId}) with the hosted Haven server before retrying`
485
+ });
479
486
  } else {
480
487
  this.next_action = edge.AgentPaymentNextAction.StopAndTellUser;
481
488
  step = signerRefusalStep({
@@ -484,6 +491,13 @@ var HavenSignContextError = class extends edge.HavenSigningError {
484
491
  nextArguments: { payment_id: paymentId }
485
492
  });
486
493
  }
494
+ } else if (flow === "task") {
495
+ this.next_action = edge.AgentPaymentNextAction.StopAndTellUser;
496
+ step = signerRefusalStep({
497
+ nextAction: edge.AgentPaymentNextAction.StopAndTellUser,
498
+ nextTool: null,
499
+ nextToolOmittedReason: "retry the same haven_sign call with the same task_budget_id"
500
+ });
487
501
  } else {
488
502
  this.fallback = "typed_data_b64";
489
503
  this.next_action = edge.AgentPaymentNextAction.StopAndTellUser;
@@ -652,6 +666,116 @@ async function fetchDirectSignContext(identity, paymentId, fetchImpl = fetch, ti
652
666
  clientUpdate: edge.readClientUpdate(body.client_update)
653
667
  };
654
668
  }
669
+ var SUPPORTED_TASK_SIGN_CONTEXT_VERSIONS = [1];
670
+ async function fetchTaskBudgetSignContext(identity, taskBudgetId, fetchImpl = fetch, timeoutMs = SIGN_CONTEXT_TIMEOUT_MS, clientIdentity) {
671
+ let response;
672
+ try {
673
+ response = await fetchImpl(
674
+ `${identity.apiUrl}/task-budgets/${encodeURIComponent(taskBudgetId)}/sign-context`,
675
+ {
676
+ headers: signContextHeaders(identity, clientIdentity),
677
+ signal: AbortSignal.timeout(timeoutMs)
678
+ }
679
+ );
680
+ } catch (err) {
681
+ const timedOut = err instanceof Error && (err.name === "TimeoutError" || err.name === "AbortError");
682
+ throw new HavenSignContextError(
683
+ timedOut ? `Haven did not answer the task-budget signing-context fetch for ${taskBudgetId} within ${timeoutMs} ms. Retry.` : `Could not reach Haven to fetch the task-budget signing context for ${taskBudgetId}: ${err instanceof Error ? err.message : String(err)}.`,
684
+ timedOut ? "SIGN_CONTEXT_TIMEOUT" : "SIGN_CONTEXT_UNREACHABLE",
685
+ void 0,
686
+ taskBudgetId,
687
+ "task"
688
+ );
689
+ }
690
+ let body;
691
+ try {
692
+ body = await response.json();
693
+ } catch (err) {
694
+ if (err instanceof Error && (err.name === "TimeoutError" || err.name === "AbortError")) {
695
+ throw new HavenSignContextError(
696
+ `Haven did not finish sending the task-budget signing context for ${taskBudgetId} within ${timeoutMs} ms. Retry.`,
697
+ "SIGN_CONTEXT_TIMEOUT",
698
+ void 0,
699
+ taskBudgetId,
700
+ "task"
701
+ );
702
+ }
703
+ body = {};
704
+ }
705
+ if (!response.ok) {
706
+ const detail = typeof body.error === "string" ? body.error : `HTTP ${response.status}`;
707
+ throw new HavenSignContextError(
708
+ `Haven refused the task-budget signing-context fetch for ${taskBudgetId}: ${detail}`,
709
+ "SIGN_CONTEXT_REFUSED",
710
+ refusalOf(response.status, body),
711
+ taskBudgetId,
712
+ "task"
713
+ );
714
+ }
715
+ const purpose = body.purpose;
716
+ const typedData = body.typed_data;
717
+ const version = body.task_sign_context_version;
718
+ const expected = body.expected;
719
+ const malformed = (detail) => {
720
+ throw new HavenSignContextError(
721
+ `The Haven task-budget sign-context response for ${taskBudgetId} is malformed: ${detail}.`,
722
+ "SIGN_CONTEXT_MALFORMED",
723
+ void 0,
724
+ taskBudgetId,
725
+ "task"
726
+ );
727
+ };
728
+ if (typeof version !== "number" || !SUPPORTED_TASK_SIGN_CONTEXT_VERSIONS.includes(version)) {
729
+ throw new HavenSignContextError(
730
+ `The Haven task-budget sign-context response is version ${String(version)}, which this signer does not support (supported: ${SUPPORTED_TASK_SIGN_CONTEXT_VERSIONS.join(", ")}). Update @haven_ai/signer.`,
731
+ "SIGN_CONTEXT_MALFORMED",
732
+ void 0,
733
+ taskBudgetId,
734
+ "task"
735
+ );
736
+ }
737
+ if (!typedData || typeof typedData !== "object") return malformed("missing typed_data");
738
+ if (!expected || typeof expected !== "object") return malformed("missing expected");
739
+ const e = expected;
740
+ if (purpose === "open") {
741
+ if (typeof e.delegate_account !== "string" || typeof e.chain_id !== "number" || typeof e.token_address !== "string" || typeof e.max_amount_atomic !== "string" || e.recipient_address !== null && typeof e.recipient_address !== "string" || typeof e.expires_at !== "number" || typeof e.parent_delegation_hash !== "string") {
742
+ return malformed("expected is missing a required field for purpose open");
743
+ }
744
+ return {
745
+ purpose: "open",
746
+ taskBudgetId: String(body.task_budget_id ?? taskBudgetId),
747
+ typedData,
748
+ expected: {
749
+ delegateAccount: e.delegate_account,
750
+ chainId: e.chain_id,
751
+ tokenAddress: e.token_address,
752
+ maxAmountAtomic: e.max_amount_atomic,
753
+ recipientAddress: e.recipient_address ?? null,
754
+ expiresAt: e.expires_at,
755
+ parentDelegationHash: e.parent_delegation_hash
756
+ }
757
+ };
758
+ }
759
+ if (purpose === "close") {
760
+ const userOpHash = body.user_op_hash;
761
+ if (typeof userOpHash !== "string") return malformed("missing user_op_hash for purpose close");
762
+ if (typeof e.delegate_account !== "string" || typeof e.chain_id !== "number" || typeof e.delegation_hash !== "string") {
763
+ return malformed("expected is missing a required field for purpose close");
764
+ }
765
+ return {
766
+ purpose: "close",
767
+ taskBudgetId: String(body.task_budget_id ?? taskBudgetId),
768
+ typedData,
769
+ userOpHash,
770
+ expected: {
771
+ delegateAccount: e.delegate_account,
772
+ chainId: e.chain_id,
773
+ delegationHash: e.delegation_hash
774
+ }
775
+ };
776
+ }
777
+ return malformed(`unknown purpose '${String(purpose)}'`);
778
+ }
655
779
 
656
780
  // src/capabilities.ts
657
781
  var SIGNER_CAPABILITY_KEY = "haven/signer-compatibility";
@@ -659,7 +783,8 @@ function signerCompatibility() {
659
783
  return {
660
784
  x402_expected_context_versions: [...SUPPORTED_X402_EXPECTED_VERSIONS],
661
785
  sweep_binding_versions: [...SUPPORTED_SWEEP_BINDING_VERSIONS],
662
- direct_sign_context_versions: [...SUPPORTED_DIRECT_SIGN_CONTEXT_VERSIONS]
786
+ direct_sign_context_versions: [...SUPPORTED_DIRECT_SIGN_CONTEXT_VERSIONS],
787
+ task_sign_context_versions: [...SUPPORTED_TASK_SIGN_CONTEXT_VERSIONS]
663
788
  };
664
789
  }
665
790
  function signerCapabilityAdvertisement() {
@@ -678,6 +803,7 @@ function signerInstructions() {
678
803
  `- x402 expected-context versions supported: ${compatibility.x402_expected_context_versions.join(", ")}`,
679
804
  `- sweep authorization binding versions supported: ${compatibility.sweep_binding_versions.join(", ")}`,
680
805
  `- 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`,
806
+ `- task-budget sign-context versions supported: ${compatibility.task_sign_context_versions.join(", ")} \u2014 pass task_budget_id alone to haven_sign; this signer fetches the exact bytes`,
681
807
  "",
682
808
  "Haven quote and prepare results report the expected-context version they will emit",
683
809
  "(signer_compatibility.x402_expected_context_version). If that version is not in the list",
@@ -1030,7 +1156,14 @@ var toolSchemas = {
1030
1156
  // Bounded: a realistic redemption payload is ~10KB encoded; 256KB is
1031
1157
  // generous headroom while keeping the offline signer from materializing
1032
1158
  // arbitrarily large caller input.
1033
- typed_data_b64: v3.z.string().min(1).max(262144).optional()
1159
+ typed_data_b64: v3.z.string().min(1).max(262144).optional(),
1160
+ // #3329: sign a task budget's own child delegation (purpose `open`) or
1161
+ // its early-close UserOp (purpose `close`) — the signer fetches the
1162
+ // exact typed data and expectation itself, exactly like payment_id.
1163
+ // Mutually exclusive with payment_id / payload_hash (checked in the
1164
+ // handler, since Zod's object-level refine on a raw shape used by
1165
+ // several call sites is not the shape this schema is composed as).
1166
+ task_budget_id: v3.z.string().min(1).optional()
1034
1167
  },
1035
1168
  haven_x402_sign_header: {
1036
1169
  // The parsed HTTP 402 PaymentRequired from the merchant. Typed as an object
@@ -1080,7 +1213,11 @@ var SIGN_DESCRIPTION = [
1080
1213
  "intent against a Haven-signed context \u2014 never an arbitrary typed_data payload, however it is shaped.",
1081
1214
  "Next: call mcp__haven__haven_submit with signature, then pass x402_binding",
1082
1215
  "to mcp__haven-signer__haven_x402_sign_header. A bare payload_hash with no payment_id, typed_data",
1083
- "or x402_expected is REFUSED (BARE_HASH_REFUSED): a hash carries nothing this signer can verify."
1216
+ "or x402_expected is REFUSED (BARE_HASH_REFUSED): a hash carries nothing this signer can verify.",
1217
+ "TASK BUDGETS (#3329): pass task_budget_id ALONE \u2014 mutually exclusive with payment_id and",
1218
+ "payload_hash \u2014 to sign a task budget\u2019s own child delegation (opening it) or its early-close",
1219
+ "UserOp; this signer fetches the exact typed data itself. Result then carries next_tool_name",
1220
+ "(haven_submit) and next_arguments { task_budget_id, signature } instead of x402_binding."
1084
1221
  ].join(" ");
1085
1222
  var X402_SIGN_HEADER_DESCRIPTION = [
1086
1223
  "Build and sign the EIP-3009 merchant payment header for the merchant leg of an x402 payment.",
@@ -1244,9 +1381,84 @@ function createToolHandlers(signer, options = {}) {
1244
1381
  throw err;
1245
1382
  }
1246
1383
  }
1384
+ async function signTaskBudget(taskBudgetId) {
1385
+ const identity = await options.signContext?.loadIdentity() ?? null;
1386
+ if (!identity) {
1387
+ throw new edge.HavenSigningError(
1388
+ `task_budget_id signing needs the agent identity (identity.json next to the signer credentials), which this signer could not load. Re-run \`${edge.connectorRerunCommand()}\` to restore it.`
1389
+ );
1390
+ }
1391
+ const ctx = await fetchTaskBudgetSignContext(
1392
+ identity,
1393
+ taskBudgetId,
1394
+ options.signContext?.fetchImpl,
1395
+ void 0,
1396
+ options.signContext?.clientIdentity
1397
+ );
1398
+ let signature;
1399
+ if (ctx.purpose === "open") {
1400
+ try {
1401
+ edge.assertOwnTaskChild(
1402
+ ctx.typedData,
1403
+ {
1404
+ chainId: ctx.expected.chainId,
1405
+ tokenAddress: ctx.expected.tokenAddress,
1406
+ maxAmountAtomic: ctx.expected.maxAmountAtomic,
1407
+ recipientAddress: ctx.expected.recipientAddress,
1408
+ expiresAt: ctx.expected.expiresAt,
1409
+ parentDelegationHash: ctx.expected.parentDelegationHash
1410
+ },
1411
+ signer.delegateAddress
1412
+ );
1413
+ } catch (err) {
1414
+ if (err instanceof edge.HavenTypedDataRefusedError) throw err;
1415
+ throw new edge.HavenTypedDataRefusedError(err instanceof Error ? err.message : String(err));
1416
+ }
1417
+ signature = await signer.signDelegationTypedData(ctx.typedData);
1418
+ } else {
1419
+ try {
1420
+ edge.assertUserOpTypedDataBinding(ctx.typedData, ctx.userOpHash);
1421
+ } catch (err) {
1422
+ if (err instanceof edge.HavenUserOpBindingError) throw new HavenUserOpBindingRefusedError(err.message);
1423
+ throw err;
1424
+ }
1425
+ try {
1426
+ edge.assertOwnTaskBudgetCloseUserOp(
1427
+ ctx.typedData,
1428
+ { delegationHash: ctx.expected.delegationHash },
1429
+ signer.delegateAddress
1430
+ );
1431
+ } catch (err) {
1432
+ if (err instanceof edge.HavenTypedDataRefusedError) throw err;
1433
+ throw new edge.HavenTypedDataRefusedError(err instanceof Error ? err.message : String(err));
1434
+ }
1435
+ signature = await signer.signDelegationTypedData(ctx.typedData);
1436
+ }
1437
+ await auditSigning("haven_sign", viem.hashTypedData(ctx.typedData));
1438
+ return {
1439
+ signature,
1440
+ task_budget_id: ctx.taskBudgetId,
1441
+ purpose: ctx.purpose,
1442
+ next_tool_name: "haven_submit",
1443
+ next_tool_server_role: "hosted",
1444
+ next_arguments: { task_budget_id: ctx.taskBudgetId, signature }
1445
+ };
1446
+ }
1247
1447
  return {
1248
1448
  haven_sign: async (input) => runTool(async () => {
1249
1449
  const args = parse("haven_sign", coerceX402Expected(input));
1450
+ if (args.task_budget_id) {
1451
+ if (args.payment_id || args.payload_hash) {
1452
+ throw new v3.z.ZodError([
1453
+ {
1454
+ code: v3.z.ZodIssueCode.custom,
1455
+ path: ["task_budget_id"],
1456
+ message: "task_budget_id is mutually exclusive with payment_id / payload_hash \u2014 pass exactly one."
1457
+ }
1458
+ ]);
1459
+ }
1460
+ return signTaskBudget(args.task_budget_id);
1461
+ }
1250
1462
  const resolved = await resolveSignContext(args, { allowDirectFallback: true });
1251
1463
  const typedData = resolved?.ctx.typedData ?? resolveTypedData(args);
1252
1464
  const payloadHash = resolved?.ctx.payloadHash ?? args.payload_hash;
@@ -1517,7 +1729,7 @@ function normalizeError(err) {
1517
1729
 
1518
1730
  // src/consent.ts
1519
1731
  var toolSummaries = {
1520
- 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)",
1732
+ haven_sign: "signs a Haven-prepared payment, task-budget open, or task-budget close (a UserOp from your own account whose only call redeems a delegation made to it, directly or through a self-delegated task-budget child, or an x402 funding intent against a Haven-signed context)",
1521
1733
  haven_x402_sign_header: "builds and signs the EIP-3009 merchant payment header for an x402 payment",
1522
1734
  haven_sign_x402: "the two above in one call: funding payload + merchant header",
1523
1735
  haven_sign_sweep_delegate: "signs a Haven-prepared gasless sweep of stranded funds back to your own account"
@@ -1647,7 +1859,7 @@ async function writeAckFile(path$1, hash) {
1647
1859
  );
1648
1860
  }
1649
1861
  var SIGNER_NAME = "@haven_ai/signer";
1650
- var SIGNER_VERSION = "0.5.0-alpha.1";
1862
+ var SIGNER_VERSION = "0.6.0-alpha.0";
1651
1863
  async function resolveEdgeSigner(options = {}) {
1652
1864
  const { signer } = await resolveSignerRuntime(options);
1653
1865
  return signer;