@haven_ai/signer 0.6.0-alpha.0 → 0.7.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.d.cts CHANGED
@@ -488,6 +488,26 @@ declare const SIGNER_HOSTED_HANDOFF_SHAPES: {
488
488
  };
489
489
  };
490
490
 
491
+ /**
492
+ * The signer's wire identity. Declared HERE, in the tools/handlers module —
493
+ * `server.ts` (the MCP wiring) imports them from this module, keeping the
494
+ * import graph one-directional (tools.ts ← server.ts, never the reverse);
495
+ * `server.ts` re-exports them, so the package's public API is unchanged.
496
+ */
497
+ declare const SIGNER_NAME = "@haven_ai/signer";
498
+ declare const SIGNER_VERSION = "0.7.0-alpha.0";
499
+ /**
500
+ * #3419: the marker the tool layer prefixes the undeclared-argument refusal
501
+ * with. The strict re-parse lives in THIS module (`parseStrictFor`, below) —
502
+ * the same module as the `normalizeError` branch that shapes the refusal — so
503
+ * the marker, the message and the structured fields are one unit and cannot
504
+ * drift. The registration in `server.ts` is passthrough (the issue's "schema
505
+ * that keeps unknown keys"): the SDK layer lets an undeclared key through and
506
+ * the tool layer refuses it here, where the full structured refusal
507
+ * (`UNSUPPORTED_ARGUMENT` with `unknown_arguments`, `signer_version`,
508
+ * `fallback`) is still expressible as JSON on the result.
509
+ */
510
+ declare const UNSUPPORTED_ARGUMENT_MARKER = "HavenSignerUnsupportedArgument:";
491
511
  /**
492
512
  * Local signer tool set. These run on the agent's machine, next to the key,
493
513
  * and pair with the hosted server's construct/relay tools (#183). They sign;
@@ -612,6 +632,7 @@ declare const toolSchemas: {
612
632
  readonly typed_data: z$1.ZodOptional<z$1.ZodRecord<z$1.ZodString, z$1.ZodUnknown>>;
613
633
  readonly typed_data_b64: z$1.ZodOptional<z$1.ZodString>;
614
634
  readonly task_budget_id: z$1.ZodOptional<z$1.ZodString>;
635
+ readonly sub_budget_id: z$1.ZodOptional<z$1.ZodString>;
615
636
  };
616
637
  readonly haven_x402_sign_header: {
617
638
  readonly payment_required: z$1.ZodRecord<z$1.ZodString, z$1.ZodUnknown>;
@@ -727,6 +748,14 @@ interface ToolFailure {
727
748
  * field for every sign-context refusal (`'typed_data_b64'`).
728
749
  */
729
750
  fallback?: string;
751
+ /**
752
+ * #3419: present on `UNSUPPORTED_ARGUMENT` — the undeclared top-level
753
+ * argument names the caller sent (the strict schema's `unrecognized_keys`),
754
+ * so an agent can see exactly what this signer does not accept.
755
+ */
756
+ unknown_arguments?: string[];
757
+ /** #3419: present on `UNSUPPORTED_ARGUMENT` — this signer's own package version. */
758
+ signer_version?: string;
730
759
  /**
731
760
  * #3001: present on `SIGN_CONTEXT_REFUSED` — the HTTP status the backend
732
761
  * answered the sign-context fetch with (404, 410, …) — the x402 fetch, or
@@ -735,7 +764,7 @@ interface ToolFailure {
735
764
  http_status?: number;
736
765
  /** #3001: the backend's own `error_code` on `SIGN_CONTEXT_REFUSED` (`expired`, `already_executed`, `not_signable`, `sign_context_unavailable`, and since #3303 `client_outdated`). */
737
766
  backend_error_code?: string;
738
- /** #3303: on `client_outdated`, the backend's update hint — `upgrade_command` is what updates this signer. */
767
+ /** #3303: on `client_outdated`, the backend's update hint — `upgrade_command` starts the update (the doctor, then its repair line — #3412). */
739
768
  client_update?: HavenClientUpdate;
740
769
  }
741
770
  type ToolPayload<T = unknown> = ToolSuccess<T> | ToolFailure;
@@ -783,8 +812,6 @@ declare function computeSignerConsentHash(input: SignerConsentInput): string;
783
812
  declare function renderSignerConsentBlock(input: SignerConsentInput, hash: string): string;
784
813
  declare function ensureSignerConsent(input: SignerConsentInput, options?: SignerConsentOptions): Promise<SignerConsentDecision>;
785
814
 
786
- declare const SIGNER_NAME = "@haven_ai/signer";
787
- declare const SIGNER_VERSION = "0.6.0-alpha.0";
788
815
  interface SignerOptions {
789
816
  /** Path to a Haven credential JSON file (delegate_key is read from it). */
790
817
  credentialsPath?: string;
@@ -849,4 +876,4 @@ declare function assertSupportedNodeVersion(nodeVersion?: string): void;
849
876
  declare function runSignerStdioServer(options?: SignerOptions): Promise<void>;
850
877
  declare function runSignerConsentGate(signer: EdgeSigner, credentials: SignerCredentials | undefined, options: SignerOptions): Promise<SignerConsentDecision>;
851
878
 
852
- export { AUDIT_ROTATE_BYTES, type AppendAuditOptions, type EdgeSigner, OWNER_ONLY_MODE, type PermissionLog, type PermissiveFile, type ResolvedSignerRuntime, SIGNER_ACK_ENV, SIGNER_CAPABILITY_KEY, SIGNER_HOSTED_HANDOFF_SHAPES, SIGNER_NAME, SIGNER_VERSION, SUPPORTED_SWEEP_BINDING_VERSIONS, SUPPORTED_X402_EXPECTED_VERSIONS, type SignerCompatibility, type SignerConsentDecision, type SignerConsentInput, type SignerConsentOptions, type SignerCredentials, type SignerOptions, type SignerToolName, type SigningAuditContext, type SigningAuditEntry, type TightenOutcome, type ToolFailure, type ToolPayload, type ToolSuccess, type X402ExpectedPayment, type X402FundingSignatureResult, type X402HeaderResult, 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 };
879
+ export { AUDIT_ROTATE_BYTES, type AppendAuditOptions, type EdgeSigner, OWNER_ONLY_MODE, type PermissionLog, type PermissiveFile, type ResolvedSignerRuntime, SIGNER_ACK_ENV, SIGNER_CAPABILITY_KEY, SIGNER_HOSTED_HANDOFF_SHAPES, SIGNER_NAME, SIGNER_VERSION, SUPPORTED_SWEEP_BINDING_VERSIONS, SUPPORTED_X402_EXPECTED_VERSIONS, type SignerCompatibility, type SignerConsentDecision, type SignerConsentInput, type SignerConsentOptions, type SignerCredentials, type SignerOptions, type SignerToolName, type SigningAuditContext, type SigningAuditEntry, type TightenOutcome, type ToolFailure, type ToolPayload, type ToolSuccess, UNSUPPORTED_ARGUMENT_MARKER, type X402ExpectedPayment, type X402FundingSignatureResult, type X402HeaderResult, 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 };
package/dist/index.d.ts CHANGED
@@ -488,6 +488,26 @@ declare const SIGNER_HOSTED_HANDOFF_SHAPES: {
488
488
  };
489
489
  };
490
490
 
491
+ /**
492
+ * The signer's wire identity. Declared HERE, in the tools/handlers module —
493
+ * `server.ts` (the MCP wiring) imports them from this module, keeping the
494
+ * import graph one-directional (tools.ts ← server.ts, never the reverse);
495
+ * `server.ts` re-exports them, so the package's public API is unchanged.
496
+ */
497
+ declare const SIGNER_NAME = "@haven_ai/signer";
498
+ declare const SIGNER_VERSION = "0.7.0-alpha.0";
499
+ /**
500
+ * #3419: the marker the tool layer prefixes the undeclared-argument refusal
501
+ * with. The strict re-parse lives in THIS module (`parseStrictFor`, below) —
502
+ * the same module as the `normalizeError` branch that shapes the refusal — so
503
+ * the marker, the message and the structured fields are one unit and cannot
504
+ * drift. The registration in `server.ts` is passthrough (the issue's "schema
505
+ * that keeps unknown keys"): the SDK layer lets an undeclared key through and
506
+ * the tool layer refuses it here, where the full structured refusal
507
+ * (`UNSUPPORTED_ARGUMENT` with `unknown_arguments`, `signer_version`,
508
+ * `fallback`) is still expressible as JSON on the result.
509
+ */
510
+ declare const UNSUPPORTED_ARGUMENT_MARKER = "HavenSignerUnsupportedArgument:";
491
511
  /**
492
512
  * Local signer tool set. These run on the agent's machine, next to the key,
493
513
  * and pair with the hosted server's construct/relay tools (#183). They sign;
@@ -612,6 +632,7 @@ declare const toolSchemas: {
612
632
  readonly typed_data: z$1.ZodOptional<z$1.ZodRecord<z$1.ZodString, z$1.ZodUnknown>>;
613
633
  readonly typed_data_b64: z$1.ZodOptional<z$1.ZodString>;
614
634
  readonly task_budget_id: z$1.ZodOptional<z$1.ZodString>;
635
+ readonly sub_budget_id: z$1.ZodOptional<z$1.ZodString>;
615
636
  };
616
637
  readonly haven_x402_sign_header: {
617
638
  readonly payment_required: z$1.ZodRecord<z$1.ZodString, z$1.ZodUnknown>;
@@ -727,6 +748,14 @@ interface ToolFailure {
727
748
  * field for every sign-context refusal (`'typed_data_b64'`).
728
749
  */
729
750
  fallback?: string;
751
+ /**
752
+ * #3419: present on `UNSUPPORTED_ARGUMENT` — the undeclared top-level
753
+ * argument names the caller sent (the strict schema's `unrecognized_keys`),
754
+ * so an agent can see exactly what this signer does not accept.
755
+ */
756
+ unknown_arguments?: string[];
757
+ /** #3419: present on `UNSUPPORTED_ARGUMENT` — this signer's own package version. */
758
+ signer_version?: string;
730
759
  /**
731
760
  * #3001: present on `SIGN_CONTEXT_REFUSED` — the HTTP status the backend
732
761
  * answered the sign-context fetch with (404, 410, …) — the x402 fetch, or
@@ -735,7 +764,7 @@ interface ToolFailure {
735
764
  http_status?: number;
736
765
  /** #3001: the backend's own `error_code` on `SIGN_CONTEXT_REFUSED` (`expired`, `already_executed`, `not_signable`, `sign_context_unavailable`, and since #3303 `client_outdated`). */
737
766
  backend_error_code?: string;
738
- /** #3303: on `client_outdated`, the backend's update hint — `upgrade_command` is what updates this signer. */
767
+ /** #3303: on `client_outdated`, the backend's update hint — `upgrade_command` starts the update (the doctor, then its repair line — #3412). */
739
768
  client_update?: HavenClientUpdate;
740
769
  }
741
770
  type ToolPayload<T = unknown> = ToolSuccess<T> | ToolFailure;
@@ -783,8 +812,6 @@ declare function computeSignerConsentHash(input: SignerConsentInput): string;
783
812
  declare function renderSignerConsentBlock(input: SignerConsentInput, hash: string): string;
784
813
  declare function ensureSignerConsent(input: SignerConsentInput, options?: SignerConsentOptions): Promise<SignerConsentDecision>;
785
814
 
786
- declare const SIGNER_NAME = "@haven_ai/signer";
787
- declare const SIGNER_VERSION = "0.6.0-alpha.0";
788
815
  interface SignerOptions {
789
816
  /** Path to a Haven credential JSON file (delegate_key is read from it). */
790
817
  credentialsPath?: string;
@@ -849,4 +876,4 @@ declare function assertSupportedNodeVersion(nodeVersion?: string): void;
849
876
  declare function runSignerStdioServer(options?: SignerOptions): Promise<void>;
850
877
  declare function runSignerConsentGate(signer: EdgeSigner, credentials: SignerCredentials | undefined, options: SignerOptions): Promise<SignerConsentDecision>;
851
878
 
852
- export { AUDIT_ROTATE_BYTES, type AppendAuditOptions, type EdgeSigner, OWNER_ONLY_MODE, type PermissionLog, type PermissiveFile, type ResolvedSignerRuntime, SIGNER_ACK_ENV, SIGNER_CAPABILITY_KEY, SIGNER_HOSTED_HANDOFF_SHAPES, SIGNER_NAME, SIGNER_VERSION, SUPPORTED_SWEEP_BINDING_VERSIONS, SUPPORTED_X402_EXPECTED_VERSIONS, type SignerCompatibility, type SignerConsentDecision, type SignerConsentInput, type SignerConsentOptions, type SignerCredentials, type SignerOptions, type SignerToolName, type SigningAuditContext, type SigningAuditEntry, type TightenOutcome, type ToolFailure, type ToolPayload, type ToolSuccess, type X402ExpectedPayment, type X402FundingSignatureResult, type X402HeaderResult, 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 };
879
+ export { AUDIT_ROTATE_BYTES, type AppendAuditOptions, type EdgeSigner, OWNER_ONLY_MODE, type PermissionLog, type PermissiveFile, type ResolvedSignerRuntime, SIGNER_ACK_ENV, SIGNER_CAPABILITY_KEY, SIGNER_HOSTED_HANDOFF_SHAPES, SIGNER_NAME, SIGNER_VERSION, SUPPORTED_SWEEP_BINDING_VERSIONS, SUPPORTED_X402_EXPECTED_VERSIONS, type SignerCompatibility, type SignerConsentDecision, type SignerConsentInput, type SignerConsentOptions, type SignerCredentials, type SignerOptions, type SignerToolName, type SigningAuditContext, type SigningAuditEntry, type TightenOutcome, type ToolFailure, type ToolPayload, type ToolSuccess, UNSUPPORTED_ARGUMENT_MARKER, type X402ExpectedPayment, type X402FundingSignatureResult, type X402HeaderResult, 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 };
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, havenClientIdentity, isSupportedNodeVersion, unsupportedNodeVersionMessage, HavenUserOpBindingError, assertOwnTaskChild, assertOwnTaskBudgetCloseUserOp, readClientUpdate, HAVEN_CLIENT_HEADER } 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, connectorUpgradeCommand, SIGNER_UPDATE_FALLBACK, HavenUnsupportedSignerVersionError, buildX402ExpectedMessage, TASK_SIGN_CONTEXT_VERSION, DIRECT_SIGN_CONTEXT_VERSION, AgentPaymentNextAction, HavenError, AgentPaymentFailureCode, havenClientIdentity, isSupportedNodeVersion, unsupportedNodeVersionMessage, HavenUserOpBindingError, connectorRerunCommand, assertOwnTaskChild, assertOwnTaskBudgetCloseUserOp, assertOwnSubBudgetChild, assertOwnSubBudgetCloseUserOp, 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';
@@ -254,7 +254,7 @@ function assertSupportedBindingVersion(received, supported, context) {
254
254
  const highest = Math.max(...supported);
255
255
  const outOfDate = received > highest;
256
256
  const code = context === "x402 expected context" ? SignerRefusalCode.UnsupportedExpectedContextVersion : SignerRefusalCode.UnsupportedSweepBindingVersion;
257
- const ceiling = outOfDate ? `This signer is out of date: it supports ${context} versions up to ${highest}, and Haven sent version ${received}. Update @haven_ai/signer \u2014 rerun the Haven connector (\`${connectorRerunCommand()}\`), which reinstalls the pinned MCP runtime.` : `Unsupported ${context} version ${received}: this signer supports ${supported.join(", ")}.`;
257
+ const ceiling = outOfDate ? `This signer is out of date: it supports ${context} versions up to ${highest}, and Haven sent version ${received}. Update @haven_ai/signer \u2014 run \`${connectorUpgradeCommand()}\`, then the repair line it prints, which reinstalls the pinned MCP runtime.` : `Unsupported ${context} version ${received}: this signer supports ${supported.join(", ")}.`;
258
258
  const fallback = outOfDate ? SIGNER_UPDATE_FALLBACK : `This ${context} version (${received}) is older than what this signer enforces (${supported.join(", ")}) \u2014 updating @haven_ai/signer will not restore it. Nothing was signed or spent; stop and tell the user rather than retrying.`;
259
259
  throw new HavenUnsupportedSignerVersionError(
260
260
  `${ceiling} Nothing was signed. Do not rewrite the version field to a supported value: it is part of the Haven-signed binding message, so changing it invalidates the signature and would misrepresent what Haven declared.`,
@@ -433,7 +433,7 @@ var HavenSignContextError = class extends HavenSigningError {
433
433
  /**
434
434
  * #3303: on a 426 `client_outdated` refusal, the backend's update hint —
435
435
  * this signer is below the minimum the deployment set, and
436
- * `client_update.upgrade_command` is what updates it.
436
+ * `client_update.upgrade_command` starts the update (#3412: for the signer, the doctor, then its repair line).
437
437
  */
438
438
  client_update;
439
439
  constructor(message, code, refusal, paymentId, flow = "x402") {
@@ -450,7 +450,7 @@ var HavenSignContextError = class extends HavenSigningError {
450
450
  step = signerRefusalStep({
451
451
  nextAction: AgentPaymentNextAction.StopAndTellUser,
452
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`
453
+ nextToolOmittedReason: `this signer is below the minimum version Haven accepts \u2014 tell the user to run ${refusal.clientUpdate?.upgrade_command ?? connectorUpgradeCommand()} and the repair line it prints, restart the agent runtime, then retry the same haven_sign / haven_sign_x402 call`
454
454
  });
455
455
  } else if (flow === "direct" && refusal?.httpStatus === 404) {
456
456
  this.fallback = "typed_data_b64";
@@ -665,7 +665,120 @@ async function fetchDirectSignContext(identity, paymentId, fetchImpl = fetch, ti
665
665
  clientUpdate: readClientUpdate(body.client_update)
666
666
  };
667
667
  }
668
- var SUPPORTED_TASK_SIGN_CONTEXT_VERSIONS = [1];
668
+ var SUPPORTED_TASK_SIGN_CONTEXT_VERSIONS = [TASK_SIGN_CONTEXT_VERSION];
669
+ var SUPPORTED_SUB_BUDGET_SIGN_CONTEXT_VERSIONS = [1];
670
+ async function fetchSubBudgetSignContext(identity, subBudgetId, fetchImpl = fetch, timeoutMs = SIGN_CONTEXT_TIMEOUT_MS, clientIdentity) {
671
+ let response;
672
+ try {
673
+ response = await fetchImpl(
674
+ `${identity.apiUrl}/sub-budgets/${encodeURIComponent(subBudgetId)}/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 sub-budget signing-context fetch for ${subBudgetId} within ${timeoutMs} ms. Retry.` : `Could not reach Haven to fetch the sub-budget signing context for ${subBudgetId}: ${err instanceof Error ? err.message : String(err)}.`,
684
+ timedOut ? "SIGN_CONTEXT_TIMEOUT" : "SIGN_CONTEXT_UNREACHABLE",
685
+ void 0,
686
+ subBudgetId,
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 sub-budget signing context for ${subBudgetId} within ${timeoutMs} ms. Retry.`,
697
+ "SIGN_CONTEXT_TIMEOUT",
698
+ void 0,
699
+ subBudgetId,
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 sub-budget signing-context fetch for ${subBudgetId}: ${detail}`,
709
+ "SIGN_CONTEXT_REFUSED",
710
+ refusalOf(response.status, body),
711
+ subBudgetId,
712
+ "task"
713
+ );
714
+ }
715
+ const purpose = body.purpose;
716
+ const typedData = body.typed_data;
717
+ const version = body.sub_budget_sign_context_version;
718
+ const expected = body.expected;
719
+ const malformed = (detail) => {
720
+ throw new HavenSignContextError(
721
+ `The Haven sub-budget sign-context response for ${subBudgetId} is malformed: ${detail}.`,
722
+ "SIGN_CONTEXT_MALFORMED",
723
+ void 0,
724
+ subBudgetId,
725
+ "task"
726
+ );
727
+ };
728
+ if (typeof version !== "number" || !SUPPORTED_SUB_BUDGET_SIGN_CONTEXT_VERSIONS.includes(version)) {
729
+ throw new HavenSignContextError(
730
+ `The Haven sub-budget sign-context response is version ${String(version)}, which this signer does not support (supported: ${SUPPORTED_SUB_BUDGET_SIGN_CONTEXT_VERSIONS.join(", ")}). Update @haven_ai/signer.`,
731
+ "SIGN_CONTEXT_MALFORMED",
732
+ void 0,
733
+ subBudgetId,
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.period_amount_atomic !== "string" || typeof e.period_duration_seconds !== "number" || typeof e.start_date !== "number" || e.recipient_address !== null && typeof e.recipient_address !== "string" || typeof e.expires_at !== "number" || typeof e.parent_delegation_hash !== "string" || typeof e.child_delegate_account !== "string") {
742
+ return malformed("expected is missing a required field for purpose open");
743
+ }
744
+ return {
745
+ purpose: "open",
746
+ subBudgetId: String(body.sub_budget_id ?? subBudgetId),
747
+ typedData,
748
+ expected: {
749
+ delegateAccount: e.delegate_account,
750
+ chainId: e.chain_id,
751
+ tokenAddress: e.token_address,
752
+ periodAmountAtomic: e.period_amount_atomic,
753
+ periodDurationSeconds: e.period_duration_seconds,
754
+ startDate: e.start_date,
755
+ recipientAddress: e.recipient_address ?? null,
756
+ expiresAt: e.expires_at,
757
+ parentDelegationHash: e.parent_delegation_hash,
758
+ childDelegateAccount: e.child_delegate_account
759
+ }
760
+ };
761
+ }
762
+ if (purpose === "close") {
763
+ const userOpHash = body.user_op_hash;
764
+ if (typeof userOpHash !== "string") return malformed("missing user_op_hash for purpose close");
765
+ if (typeof e.delegate_account !== "string" || typeof e.chain_id !== "number" || typeof e.delegation_hash !== "string") {
766
+ return malformed("expected is missing a required field for purpose close");
767
+ }
768
+ return {
769
+ purpose: "close",
770
+ subBudgetId: String(body.sub_budget_id ?? subBudgetId),
771
+ typedData,
772
+ userOpHash,
773
+ expected: {
774
+ delegateAccount: e.delegate_account,
775
+ chainId: e.chain_id,
776
+ delegationHash: e.delegation_hash
777
+ }
778
+ };
779
+ }
780
+ return malformed(`unknown purpose '${String(purpose)}'`);
781
+ }
669
782
  async function fetchTaskBudgetSignContext(identity, taskBudgetId, fetchImpl = fetch, timeoutMs = SIGN_CONTEXT_TIMEOUT_MS, clientIdentity) {
670
783
  let response;
671
784
  try {
@@ -807,8 +920,8 @@ function signerInstructions() {
807
920
  "Haven quote and prepare results report the expected-context version they will emit",
808
921
  "(signer_compatibility.x402_expected_context_version). If that version is not in the list",
809
922
  "above, this signer is out of date: STOP before signing, and tell the user to update",
810
- `@haven_ai/signer by rerunning \`${connectorRerunCommand()}\`, which reinstalls the pinned`,
811
- "MCP runtime. Do not edit the version field to a supported value \u2014 it is part of the",
923
+ `@haven_ai/signer: run \`${connectorUpgradeCommand()}\`, then the repair line it prints,`,
924
+ "which reinstalls the pinned MCP runtime. Do not edit the version field to a supported value \u2014 it is part of the",
812
925
  "Haven-signed binding message, so changing it invalidates the signature.",
813
926
  "",
814
927
  "A version-mismatch refusal from haven_sign / haven_sign_x402 / haven_sign_sweep_delegate is",
@@ -816,7 +929,13 @@ function signerInstructions() {
816
929
  "fallback fields alongside the message, so you can branch on it directly. Every signer",
817
930
  "refusal also carries the next-step family: next_tool_name + next_tool_server_role when a",
818
931
  "hosted tool follows (resolve the role against your own server names), else",
819
- "next_tool_omitted_reason saying why not."
932
+ "next_tool_omitted_reason saying why not.",
933
+ "",
934
+ "An undeclared top-level argument is refused, not stripped: haven_sign answers",
935
+ "UNSUPPORTED_ARGUMENT with unknown_arguments, signer_version and fallback (the update",
936
+ "command) instead of dropping the key and answering the generic signing error \u2014 if you see",
937
+ "that refusal, this signer predates the argument form you sent. Update the signer; nothing",
938
+ "was signed, fetched or audited."
820
939
  ].join("\n");
821
940
  }
822
941
  var OWNER_ONLY_MODE = 384;
@@ -1035,6 +1154,9 @@ var HavenBareHashRefusedError = class extends HavenSigningError {
1035
1154
  };
1036
1155
 
1037
1156
  // src/tools.ts
1157
+ var SIGNER_NAME = "@haven_ai/signer";
1158
+ var SIGNER_VERSION = "0.7.0-alpha.0";
1159
+ var UNSUPPORTED_ARGUMENT_MARKER = "HavenSignerUnsupportedArgument:";
1038
1160
  var USEROP_BINDING_MISMATCH = "USEROP_BINDING_MISMATCH";
1039
1161
  var HavenUserOpBindingRefusedError = class extends HavenSigningError {
1040
1162
  next_action = AgentPaymentNextAction.StopAndTellUser;
@@ -1162,7 +1284,13 @@ var toolSchemas = {
1162
1284
  // Mutually exclusive with payment_id / payload_hash (checked in the
1163
1285
  // handler, since Zod's object-level refine on a raw shape used by
1164
1286
  // several call sites is not the shape this schema is composed as).
1165
- task_budget_id: z$1.string().min(1).optional()
1287
+ task_budget_id: z$1.string().min(1).optional(),
1288
+ // #3330: sign a sub-budget's own child delegation or its early-close
1289
+ // UserOp — same fetch-verify-sign channel as task_budget_id, against
1290
+ // `GET /sub-budgets/:id/sign-context`. Mutually exclusive with
1291
+ // payment_id / payload_hash AND with task_budget_id (checked in the
1292
+ // handler).
1293
+ sub_budget_id: z$1.string().min(1).optional()
1166
1294
  },
1167
1295
  haven_x402_sign_header: {
1168
1296
  // The parsed HTTP 402 PaymentRequired from the merchant. Typed as an object
@@ -1443,9 +1571,76 @@ function createToolHandlers(signer, options = {}) {
1443
1571
  next_arguments: { task_budget_id: ctx.taskBudgetId, signature }
1444
1572
  };
1445
1573
  }
1574
+ async function signSubBudget(subBudgetId) {
1575
+ const identity = await options.signContext?.loadIdentity() ?? null;
1576
+ if (!identity) {
1577
+ throw new HavenSigningError(
1578
+ `sub_budget_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.`
1579
+ );
1580
+ }
1581
+ const ctx = await fetchSubBudgetSignContext(
1582
+ identity,
1583
+ subBudgetId,
1584
+ options.signContext?.fetchImpl,
1585
+ void 0,
1586
+ options.signContext?.clientIdentity
1587
+ );
1588
+ let signature;
1589
+ if (ctx.purpose === "open") {
1590
+ try {
1591
+ assertOwnSubBudgetChild(
1592
+ ctx.typedData,
1593
+ {
1594
+ chainId: ctx.expected.chainId,
1595
+ tokenAddress: ctx.expected.tokenAddress,
1596
+ periodAmountAtomic: ctx.expected.periodAmountAtomic,
1597
+ periodDurationSeconds: ctx.expected.periodDurationSeconds,
1598
+ startDate: ctx.expected.startDate,
1599
+ recipientAddress: ctx.expected.recipientAddress,
1600
+ expiresAt: ctx.expected.expiresAt,
1601
+ parentDelegationHash: ctx.expected.parentDelegationHash,
1602
+ delegateAccount: ctx.expected.delegateAccount,
1603
+ childDelegateAccount: ctx.expected.childDelegateAccount
1604
+ },
1605
+ signer.delegateAddress
1606
+ );
1607
+ } catch (err) {
1608
+ if (err instanceof HavenTypedDataRefusedError) throw err;
1609
+ throw new HavenTypedDataRefusedError(err instanceof Error ? err.message : String(err));
1610
+ }
1611
+ signature = await signer.signDelegationTypedData(ctx.typedData);
1612
+ } else {
1613
+ try {
1614
+ assertUserOpTypedDataBinding(ctx.typedData, ctx.userOpHash);
1615
+ } catch (err) {
1616
+ if (err instanceof HavenUserOpBindingError) throw new HavenUserOpBindingRefusedError(err.message);
1617
+ throw err;
1618
+ }
1619
+ try {
1620
+ assertOwnSubBudgetCloseUserOp(
1621
+ ctx.typedData,
1622
+ { delegationHash: ctx.expected.delegationHash },
1623
+ signer.delegateAddress
1624
+ );
1625
+ } catch (err) {
1626
+ if (err instanceof HavenTypedDataRefusedError) throw err;
1627
+ throw new HavenTypedDataRefusedError(err instanceof Error ? err.message : String(err));
1628
+ }
1629
+ signature = await signer.signDelegationTypedData(ctx.typedData);
1630
+ }
1631
+ await auditSigning("haven_sign", hashTypedData(ctx.typedData));
1632
+ return {
1633
+ signature,
1634
+ sub_budget_id: ctx.subBudgetId,
1635
+ purpose: ctx.purpose,
1636
+ next_tool_name: "haven_submit",
1637
+ next_tool_server_role: "hosted",
1638
+ next_arguments: { sub_budget_id: ctx.subBudgetId, signature }
1639
+ };
1640
+ }
1446
1641
  return {
1447
1642
  haven_sign: async (input) => runTool(async () => {
1448
- const args = parse("haven_sign", coerceX402Expected(input));
1643
+ const args = parseStrictFor("haven_sign", coerceX402Expected(input));
1449
1644
  if (args.task_budget_id) {
1450
1645
  if (args.payment_id || args.payload_hash) {
1451
1646
  throw new z$1.ZodError([
@@ -1456,8 +1651,29 @@ function createToolHandlers(signer, options = {}) {
1456
1651
  }
1457
1652
  ]);
1458
1653
  }
1654
+ if (args.sub_budget_id) {
1655
+ throw new z$1.ZodError([
1656
+ {
1657
+ code: z$1.ZodIssueCode.custom,
1658
+ path: ["sub_budget_id"],
1659
+ message: "task_budget_id is mutually exclusive with sub_budget_id \u2014 pass exactly one."
1660
+ }
1661
+ ]);
1662
+ }
1459
1663
  return signTaskBudget(args.task_budget_id);
1460
1664
  }
1665
+ if (args.sub_budget_id) {
1666
+ if (args.payment_id || args.payload_hash) {
1667
+ throw new z$1.ZodError([
1668
+ {
1669
+ code: z$1.ZodIssueCode.custom,
1670
+ path: ["sub_budget_id"],
1671
+ message: "sub_budget_id is mutually exclusive with payment_id / payload_hash \u2014 pass exactly one."
1672
+ }
1673
+ ]);
1674
+ }
1675
+ return signSubBudget(args.sub_budget_id);
1676
+ }
1461
1677
  const resolved = await resolveSignContext(args, { allowDirectFallback: true });
1462
1678
  const typedData = resolved?.ctx.typedData ?? resolveTypedData(args);
1463
1679
  const payloadHash = resolved?.ctx.payloadHash ?? args.payload_hash;
@@ -1500,7 +1716,7 @@ function createToolHandlers(signer, options = {}) {
1500
1716
  return { signature: result.signature, x402_binding: result.x402Binding, ...clientUpdateField(resolved) };
1501
1717
  }),
1502
1718
  haven_x402_sign_header: async (input) => runTool(async () => {
1503
- const args = parse("haven_x402_sign_header", coercePaymentRequired(input));
1719
+ const args = parseStrictFor("haven_x402_sign_header", coercePaymentRequired(input));
1504
1720
  const result = await signer.buildX402PaymentHeader(
1505
1721
  args.payment_required,
1506
1722
  args.x402_binding
@@ -1512,7 +1728,7 @@ function createToolHandlers(signer, options = {}) {
1512
1728
  return { payment_header: result.paymentHeader, accepted: result.accepted };
1513
1729
  }),
1514
1730
  haven_sign_x402: async (input) => runTool(async () => {
1515
- const args = parse("haven_sign_x402", coerceX402Expected(coercePaymentRequired(input)));
1731
+ const args = parseStrictFor("haven_sign_x402", coerceX402Expected(coercePaymentRequired(input)));
1516
1732
  const resolved = await resolveSignContext(args, { allowDirectFallback: false });
1517
1733
  const fetched = resolved?.kind === "x402" ? resolved.ctx : void 0;
1518
1734
  const payloadHash = fetched?.payloadHash ?? args.payload_hash;
@@ -1548,7 +1764,7 @@ function createToolHandlers(signer, options = {}) {
1548
1764
  };
1549
1765
  }),
1550
1766
  haven_sign_sweep_delegate: async (input) => runTool(async () => {
1551
- const args = parse("haven_sign_sweep_delegate", input);
1767
+ const args = parseStrictFor("haven_sign_sweep_delegate", input);
1552
1768
  const result = await signer.signSweepAuthorization({
1553
1769
  authorization: args.authorization,
1554
1770
  expectedAuth: args.expected_auth,
@@ -1581,8 +1797,18 @@ function createToolHandlers(signer, options = {}) {
1581
1797
  }
1582
1798
  }
1583
1799
  }
1584
- function parse(name, input) {
1585
- return z$1.object(toolSchemas[name]).parse(input ?? {});
1800
+ function parseStrictFor(name, input) {
1801
+ const result = z$1.object(toolSchemas[name]).strict().safeParse(input ?? {});
1802
+ if (result.success) return result.data;
1803
+ const topLevelUnknown = result.error.issues.flatMap(
1804
+ (issue) => issue.code === "unrecognized_keys" && issue.path.length === 0 ? issue.keys : []
1805
+ );
1806
+ if (topLevelUnknown.length === 0) throw result.error;
1807
+ const unknown = [...new Set(topLevelUnknown)];
1808
+ const err = new Error(
1809
+ `${UNSUPPORTED_ARGUMENT_MARKER} ${name} does not accept ${unknown.map((k) => `"${k}"`).join(", ")} \u2014 this signer predates that argument form. Nothing was signed or fetched.`
1810
+ );
1811
+ throw err;
1586
1812
  }
1587
1813
  function coerceX402Expected(input) {
1588
1814
  if (!input || typeof input !== "object") return input;
@@ -1621,6 +1847,25 @@ async function runTool(fn) {
1621
1847
  }
1622
1848
  }
1623
1849
  function normalizeError(err) {
1850
+ if (err instanceof Error && err.message.startsWith(UNSUPPORTED_ARGUMENT_MARKER)) {
1851
+ const unknown = [...err.message.matchAll(/"([^"]+)"/g)].map((m) => m[1]);
1852
+ return {
1853
+ success: false,
1854
+ code: "UNSUPPORTED_ARGUMENT",
1855
+ message: err.message.replace(`${UNSUPPORTED_ARGUMENT_MARKER} `, ""),
1856
+ unknown_arguments: unknown,
1857
+ signer_version: SIGNER_VERSION,
1858
+ fallback: connectorUpgradeCommand(),
1859
+ next_action: AgentPaymentNextAction.StopAndTellUser,
1860
+ ...nextStepWireFields(
1861
+ signerRefusalStep({
1862
+ nextAction: AgentPaymentNextAction.StopAndTellUser,
1863
+ nextTool: null,
1864
+ nextToolOmittedReason: `update @haven_ai/signer: run ${connectorUpgradeCommand()} and the repair line it prints, then repeat the same call`
1865
+ })
1866
+ )
1867
+ };
1868
+ }
1624
1869
  if (err instanceof z$1.ZodError) {
1625
1870
  return {
1626
1871
  success: false,
@@ -1642,7 +1887,9 @@ function normalizeError(err) {
1642
1887
  ...nextStepWireFields(signerRefusalStep({
1643
1888
  nextAction: AgentPaymentNextAction.StopAndTellUser,
1644
1889
  nextTool: null,
1645
- nextToolOmittedReason: "update @haven_ai/signer by re-running the connector, then repeat the same call"
1890
+ // #3412: the upgrade command, not "re-run the connector" — a bare re-run
1891
+ // stops at "Missing --setup" on an existing install.
1892
+ nextToolOmittedReason: `update @haven_ai/signer: run ${connectorUpgradeCommand()} and the repair line it prints, then repeat the same call`
1646
1893
  }))
1647
1894
  };
1648
1895
  }
@@ -1857,8 +2104,6 @@ async function writeAckFile(path, hash) {
1857
2104
  "utf8"
1858
2105
  );
1859
2106
  }
1860
- var SIGNER_NAME = "@haven_ai/signer";
1861
- var SIGNER_VERSION = "0.6.0-alpha.0";
1862
2107
  async function resolveEdgeSigner(options = {}) {
1863
2108
  const { signer } = await resolveSignerRuntime(options);
1864
2109
  return signer;
@@ -1913,12 +2158,14 @@ function buildSignerMcpServer(signer, options = {}) {
1913
2158
  clientIdentity: havenClientIdentity(SIGNER_NAME, SIGNER_VERSION)
1914
2159
  }
1915
2160
  });
1916
- const registerTool = server.tool.bind(server);
2161
+ const registerTool = server.registerTool.bind(server);
1917
2162
  for (const name of Object.keys(toolSchemas)) {
1918
2163
  registerTool(
1919
2164
  name,
1920
- toolDescriptions[name],
1921
- toolSchemas[name],
2165
+ {
2166
+ description: toolDescriptions[name],
2167
+ inputSchema: z.object(toolSchemas[name]).passthrough()
2168
+ },
1922
2169
  async (args) => toMcpResult(await handlers[name](args))
1923
2170
  );
1924
2171
  }
@@ -1972,6 +2219,6 @@ function toMcpResult(payload) {
1972
2219
  };
1973
2220
  }
1974
2221
 
1975
- 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 };
2222
+ 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, UNSUPPORTED_ARGUMENT_MARKER, 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 };
1976
2223
  //# sourceMappingURL=index.js.map
1977
2224
  //# sourceMappingURL=index.js.map