@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/README.md +7 -3
- package/dist/cli.cjs +267 -20
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +268 -21
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +268 -20
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +31 -4
- package/dist/index.d.ts +31 -4
- package/dist/index.js +269 -22
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { randomUUID, createHash } from 'crypto';
|
|
3
3
|
import { readFile, mkdir, writeFile, stat, lstat, rename, appendFile, chmod } from 'fs/promises';
|
|
4
4
|
import { resolve, dirname, join } from 'path';
|
|
5
|
-
import { createNextStepBuilder, havenClientIdentity, isSupportedNodeVersion, unsupportedNodeVersionMessage, addressFromKey, HavenSigningError, buildSweepTypedData, selectStandardPaymentOption, HavenApiError, toStandardPaymentRequirements, decodeBase64Json, encodeBase64Json, x402V2PaymentEnvelope, isSettlementChildTypedData, chainIdForNetwork, assertOwnSettlementChild, isPackedUserOperationTypedData, assertUserOpTypedDataBinding, assertBoundDirectPaymentUserOp as assertBoundDirectPaymentUserOp$1, assertFundingLegPaysDelegate, HavenTypedDataRefusedError,
|
|
5
|
+
import { createNextStepBuilder, havenClientIdentity, isSupportedNodeVersion, unsupportedNodeVersionMessage, addressFromKey, HavenSigningError, buildSweepTypedData, selectStandardPaymentOption, HavenApiError, toStandardPaymentRequirements, decodeBase64Json, encodeBase64Json, x402V2PaymentEnvelope, isSettlementChildTypedData, chainIdForNetwork, assertOwnSettlementChild, isPackedUserOperationTypedData, assertUserOpTypedDataBinding, assertBoundDirectPaymentUserOp as assertBoundDirectPaymentUserOp$1, assertFundingLegPaysDelegate, HavenTypedDataRefusedError, connectorUpgradeCommand, buildSweepAuthorizationMessage, verifySignature, x402AuthorizationAmount, SignerRefusalCode, SIGNER_UPDATE_FALLBACK, HavenUnsupportedSignerVersionError, buildX402ExpectedMessage, TASK_SIGN_CONTEXT_VERSION, DIRECT_SIGN_CONTEXT_VERSION, HavenError, AgentPaymentFailureCode, AgentPaymentNextAction, HavenUserOpBindingError, connectorRerunCommand, assertOwnTaskChild, assertOwnTaskBudgetCloseUserOp, assertOwnSubBudgetChild, assertOwnSubBudgetCloseUserOp, readClientUpdate, HAVEN_CLIENT_HEADER } from '@haven_ai/sdk/edge';
|
|
6
6
|
import { recoverTypedDataAddress, hashTypedData, hashMessage } from 'viem';
|
|
7
7
|
import { z as z$1 } from 'zod/v3';
|
|
8
8
|
import { z } from 'zod';
|
|
@@ -212,7 +212,7 @@ var HavenSignContextError = class extends HavenSigningError {
|
|
|
212
212
|
/**
|
|
213
213
|
* #3303: on a 426 `client_outdated` refusal, the backend's update hint —
|
|
214
214
|
* this signer is below the minimum the deployment set, and
|
|
215
|
-
* `client_update.upgrade_command`
|
|
215
|
+
* `client_update.upgrade_command` starts the update (#3412: for the signer, the doctor, then its repair line).
|
|
216
216
|
*/
|
|
217
217
|
client_update;
|
|
218
218
|
constructor(message, code, refusal, paymentId, flow = "x402") {
|
|
@@ -229,7 +229,7 @@ var HavenSignContextError = class extends HavenSigningError {
|
|
|
229
229
|
step = signerRefusalStep({
|
|
230
230
|
nextAction: AgentPaymentNextAction.StopAndTellUser,
|
|
231
231
|
nextTool: null,
|
|
232
|
-
nextToolOmittedReason: `this signer is below the minimum version Haven accepts \u2014 tell the user to run ${refusal.clientUpdate?.upgrade_command ??
|
|
232
|
+
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`
|
|
233
233
|
});
|
|
234
234
|
} else if (flow === "direct" && refusal?.httpStatus === 404) {
|
|
235
235
|
this.fallback = "typed_data_b64";
|
|
@@ -444,7 +444,120 @@ async function fetchDirectSignContext(identity, paymentId, fetchImpl = fetch, ti
|
|
|
444
444
|
clientUpdate: readClientUpdate(body.client_update)
|
|
445
445
|
};
|
|
446
446
|
}
|
|
447
|
-
var SUPPORTED_TASK_SIGN_CONTEXT_VERSIONS = [
|
|
447
|
+
var SUPPORTED_TASK_SIGN_CONTEXT_VERSIONS = [TASK_SIGN_CONTEXT_VERSION];
|
|
448
|
+
var SUPPORTED_SUB_BUDGET_SIGN_CONTEXT_VERSIONS = [1];
|
|
449
|
+
async function fetchSubBudgetSignContext(identity, subBudgetId, fetchImpl = fetch, timeoutMs = SIGN_CONTEXT_TIMEOUT_MS, clientIdentity) {
|
|
450
|
+
let response;
|
|
451
|
+
try {
|
|
452
|
+
response = await fetchImpl(
|
|
453
|
+
`${identity.apiUrl}/sub-budgets/${encodeURIComponent(subBudgetId)}/sign-context`,
|
|
454
|
+
{
|
|
455
|
+
headers: signContextHeaders(identity, clientIdentity),
|
|
456
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
457
|
+
}
|
|
458
|
+
);
|
|
459
|
+
} catch (err) {
|
|
460
|
+
const timedOut = err instanceof Error && (err.name === "TimeoutError" || err.name === "AbortError");
|
|
461
|
+
throw new HavenSignContextError(
|
|
462
|
+
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)}.`,
|
|
463
|
+
timedOut ? "SIGN_CONTEXT_TIMEOUT" : "SIGN_CONTEXT_UNREACHABLE",
|
|
464
|
+
void 0,
|
|
465
|
+
subBudgetId,
|
|
466
|
+
"task"
|
|
467
|
+
);
|
|
468
|
+
}
|
|
469
|
+
let body;
|
|
470
|
+
try {
|
|
471
|
+
body = await response.json();
|
|
472
|
+
} catch (err) {
|
|
473
|
+
if (err instanceof Error && (err.name === "TimeoutError" || err.name === "AbortError")) {
|
|
474
|
+
throw new HavenSignContextError(
|
|
475
|
+
`Haven did not finish sending the sub-budget signing context for ${subBudgetId} within ${timeoutMs} ms. Retry.`,
|
|
476
|
+
"SIGN_CONTEXT_TIMEOUT",
|
|
477
|
+
void 0,
|
|
478
|
+
subBudgetId,
|
|
479
|
+
"task"
|
|
480
|
+
);
|
|
481
|
+
}
|
|
482
|
+
body = {};
|
|
483
|
+
}
|
|
484
|
+
if (!response.ok) {
|
|
485
|
+
const detail = typeof body.error === "string" ? body.error : `HTTP ${response.status}`;
|
|
486
|
+
throw new HavenSignContextError(
|
|
487
|
+
`Haven refused the sub-budget signing-context fetch for ${subBudgetId}: ${detail}`,
|
|
488
|
+
"SIGN_CONTEXT_REFUSED",
|
|
489
|
+
refusalOf(response.status, body),
|
|
490
|
+
subBudgetId,
|
|
491
|
+
"task"
|
|
492
|
+
);
|
|
493
|
+
}
|
|
494
|
+
const purpose = body.purpose;
|
|
495
|
+
const typedData = body.typed_data;
|
|
496
|
+
const version = body.sub_budget_sign_context_version;
|
|
497
|
+
const expected = body.expected;
|
|
498
|
+
const malformed = (detail) => {
|
|
499
|
+
throw new HavenSignContextError(
|
|
500
|
+
`The Haven sub-budget sign-context response for ${subBudgetId} is malformed: ${detail}.`,
|
|
501
|
+
"SIGN_CONTEXT_MALFORMED",
|
|
502
|
+
void 0,
|
|
503
|
+
subBudgetId,
|
|
504
|
+
"task"
|
|
505
|
+
);
|
|
506
|
+
};
|
|
507
|
+
if (typeof version !== "number" || !SUPPORTED_SUB_BUDGET_SIGN_CONTEXT_VERSIONS.includes(version)) {
|
|
508
|
+
throw new HavenSignContextError(
|
|
509
|
+
`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.`,
|
|
510
|
+
"SIGN_CONTEXT_MALFORMED",
|
|
511
|
+
void 0,
|
|
512
|
+
subBudgetId,
|
|
513
|
+
"task"
|
|
514
|
+
);
|
|
515
|
+
}
|
|
516
|
+
if (!typedData || typeof typedData !== "object") return malformed("missing typed_data");
|
|
517
|
+
if (!expected || typeof expected !== "object") return malformed("missing expected");
|
|
518
|
+
const e = expected;
|
|
519
|
+
if (purpose === "open") {
|
|
520
|
+
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") {
|
|
521
|
+
return malformed("expected is missing a required field for purpose open");
|
|
522
|
+
}
|
|
523
|
+
return {
|
|
524
|
+
purpose: "open",
|
|
525
|
+
subBudgetId: String(body.sub_budget_id ?? subBudgetId),
|
|
526
|
+
typedData,
|
|
527
|
+
expected: {
|
|
528
|
+
delegateAccount: e.delegate_account,
|
|
529
|
+
chainId: e.chain_id,
|
|
530
|
+
tokenAddress: e.token_address,
|
|
531
|
+
periodAmountAtomic: e.period_amount_atomic,
|
|
532
|
+
periodDurationSeconds: e.period_duration_seconds,
|
|
533
|
+
startDate: e.start_date,
|
|
534
|
+
recipientAddress: e.recipient_address ?? null,
|
|
535
|
+
expiresAt: e.expires_at,
|
|
536
|
+
parentDelegationHash: e.parent_delegation_hash,
|
|
537
|
+
childDelegateAccount: e.child_delegate_account
|
|
538
|
+
}
|
|
539
|
+
};
|
|
540
|
+
}
|
|
541
|
+
if (purpose === "close") {
|
|
542
|
+
const userOpHash = body.user_op_hash;
|
|
543
|
+
if (typeof userOpHash !== "string") return malformed("missing user_op_hash for purpose close");
|
|
544
|
+
if (typeof e.delegate_account !== "string" || typeof e.chain_id !== "number" || typeof e.delegation_hash !== "string") {
|
|
545
|
+
return malformed("expected is missing a required field for purpose close");
|
|
546
|
+
}
|
|
547
|
+
return {
|
|
548
|
+
purpose: "close",
|
|
549
|
+
subBudgetId: String(body.sub_budget_id ?? subBudgetId),
|
|
550
|
+
typedData,
|
|
551
|
+
userOpHash,
|
|
552
|
+
expected: {
|
|
553
|
+
delegateAccount: e.delegate_account,
|
|
554
|
+
chainId: e.chain_id,
|
|
555
|
+
delegationHash: e.delegation_hash
|
|
556
|
+
}
|
|
557
|
+
};
|
|
558
|
+
}
|
|
559
|
+
return malformed(`unknown purpose '${String(purpose)}'`);
|
|
560
|
+
}
|
|
448
561
|
async function fetchTaskBudgetSignContext(identity, taskBudgetId, fetchImpl = fetch, timeoutMs = SIGN_CONTEXT_TIMEOUT_MS, clientIdentity) {
|
|
449
562
|
let response;
|
|
450
563
|
try {
|
|
@@ -556,6 +669,9 @@ async function fetchTaskBudgetSignContext(identity, taskBudgetId, fetchImpl = fe
|
|
|
556
669
|
}
|
|
557
670
|
|
|
558
671
|
// src/tools.ts
|
|
672
|
+
var SIGNER_NAME = "@haven_ai/signer";
|
|
673
|
+
var SIGNER_VERSION = "0.7.0-alpha.0";
|
|
674
|
+
var UNSUPPORTED_ARGUMENT_MARKER = "HavenSignerUnsupportedArgument:";
|
|
559
675
|
var USEROP_BINDING_MISMATCH = "USEROP_BINDING_MISMATCH";
|
|
560
676
|
var HavenUserOpBindingRefusedError = class extends HavenSigningError {
|
|
561
677
|
next_action = AgentPaymentNextAction.StopAndTellUser;
|
|
@@ -683,7 +799,13 @@ var toolSchemas = {
|
|
|
683
799
|
// Mutually exclusive with payment_id / payload_hash (checked in the
|
|
684
800
|
// handler, since Zod's object-level refine on a raw shape used by
|
|
685
801
|
// several call sites is not the shape this schema is composed as).
|
|
686
|
-
task_budget_id: z$1.string().min(1).optional()
|
|
802
|
+
task_budget_id: z$1.string().min(1).optional(),
|
|
803
|
+
// #3330: sign a sub-budget's own child delegation or its early-close
|
|
804
|
+
// UserOp — same fetch-verify-sign channel as task_budget_id, against
|
|
805
|
+
// `GET /sub-budgets/:id/sign-context`. Mutually exclusive with
|
|
806
|
+
// payment_id / payload_hash AND with task_budget_id (checked in the
|
|
807
|
+
// handler).
|
|
808
|
+
sub_budget_id: z$1.string().min(1).optional()
|
|
687
809
|
},
|
|
688
810
|
haven_x402_sign_header: {
|
|
689
811
|
// The parsed HTTP 402 PaymentRequired from the merchant. Typed as an object
|
|
@@ -964,9 +1086,76 @@ function createToolHandlers(signer, options = {}) {
|
|
|
964
1086
|
next_arguments: { task_budget_id: ctx.taskBudgetId, signature }
|
|
965
1087
|
};
|
|
966
1088
|
}
|
|
1089
|
+
async function signSubBudget(subBudgetId) {
|
|
1090
|
+
const identity = await options.signContext?.loadIdentity() ?? null;
|
|
1091
|
+
if (!identity) {
|
|
1092
|
+
throw new HavenSigningError(
|
|
1093
|
+
`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.`
|
|
1094
|
+
);
|
|
1095
|
+
}
|
|
1096
|
+
const ctx = await fetchSubBudgetSignContext(
|
|
1097
|
+
identity,
|
|
1098
|
+
subBudgetId,
|
|
1099
|
+
options.signContext?.fetchImpl,
|
|
1100
|
+
void 0,
|
|
1101
|
+
options.signContext?.clientIdentity
|
|
1102
|
+
);
|
|
1103
|
+
let signature;
|
|
1104
|
+
if (ctx.purpose === "open") {
|
|
1105
|
+
try {
|
|
1106
|
+
assertOwnSubBudgetChild(
|
|
1107
|
+
ctx.typedData,
|
|
1108
|
+
{
|
|
1109
|
+
chainId: ctx.expected.chainId,
|
|
1110
|
+
tokenAddress: ctx.expected.tokenAddress,
|
|
1111
|
+
periodAmountAtomic: ctx.expected.periodAmountAtomic,
|
|
1112
|
+
periodDurationSeconds: ctx.expected.periodDurationSeconds,
|
|
1113
|
+
startDate: ctx.expected.startDate,
|
|
1114
|
+
recipientAddress: ctx.expected.recipientAddress,
|
|
1115
|
+
expiresAt: ctx.expected.expiresAt,
|
|
1116
|
+
parentDelegationHash: ctx.expected.parentDelegationHash,
|
|
1117
|
+
delegateAccount: ctx.expected.delegateAccount,
|
|
1118
|
+
childDelegateAccount: ctx.expected.childDelegateAccount
|
|
1119
|
+
},
|
|
1120
|
+
signer.delegateAddress
|
|
1121
|
+
);
|
|
1122
|
+
} catch (err) {
|
|
1123
|
+
if (err instanceof HavenTypedDataRefusedError) throw err;
|
|
1124
|
+
throw new HavenTypedDataRefusedError(err instanceof Error ? err.message : String(err));
|
|
1125
|
+
}
|
|
1126
|
+
signature = await signer.signDelegationTypedData(ctx.typedData);
|
|
1127
|
+
} else {
|
|
1128
|
+
try {
|
|
1129
|
+
assertUserOpTypedDataBinding(ctx.typedData, ctx.userOpHash);
|
|
1130
|
+
} catch (err) {
|
|
1131
|
+
if (err instanceof HavenUserOpBindingError) throw new HavenUserOpBindingRefusedError(err.message);
|
|
1132
|
+
throw err;
|
|
1133
|
+
}
|
|
1134
|
+
try {
|
|
1135
|
+
assertOwnSubBudgetCloseUserOp(
|
|
1136
|
+
ctx.typedData,
|
|
1137
|
+
{ delegationHash: ctx.expected.delegationHash },
|
|
1138
|
+
signer.delegateAddress
|
|
1139
|
+
);
|
|
1140
|
+
} catch (err) {
|
|
1141
|
+
if (err instanceof HavenTypedDataRefusedError) throw err;
|
|
1142
|
+
throw new HavenTypedDataRefusedError(err instanceof Error ? err.message : String(err));
|
|
1143
|
+
}
|
|
1144
|
+
signature = await signer.signDelegationTypedData(ctx.typedData);
|
|
1145
|
+
}
|
|
1146
|
+
await auditSigning("haven_sign", hashTypedData(ctx.typedData));
|
|
1147
|
+
return {
|
|
1148
|
+
signature,
|
|
1149
|
+
sub_budget_id: ctx.subBudgetId,
|
|
1150
|
+
purpose: ctx.purpose,
|
|
1151
|
+
next_tool_name: "haven_submit",
|
|
1152
|
+
next_tool_server_role: "hosted",
|
|
1153
|
+
next_arguments: { sub_budget_id: ctx.subBudgetId, signature }
|
|
1154
|
+
};
|
|
1155
|
+
}
|
|
967
1156
|
return {
|
|
968
1157
|
haven_sign: async (input) => runTool(async () => {
|
|
969
|
-
const args =
|
|
1158
|
+
const args = parseStrictFor("haven_sign", coerceX402Expected(input));
|
|
970
1159
|
if (args.task_budget_id) {
|
|
971
1160
|
if (args.payment_id || args.payload_hash) {
|
|
972
1161
|
throw new z$1.ZodError([
|
|
@@ -977,8 +1166,29 @@ function createToolHandlers(signer, options = {}) {
|
|
|
977
1166
|
}
|
|
978
1167
|
]);
|
|
979
1168
|
}
|
|
1169
|
+
if (args.sub_budget_id) {
|
|
1170
|
+
throw new z$1.ZodError([
|
|
1171
|
+
{
|
|
1172
|
+
code: z$1.ZodIssueCode.custom,
|
|
1173
|
+
path: ["sub_budget_id"],
|
|
1174
|
+
message: "task_budget_id is mutually exclusive with sub_budget_id \u2014 pass exactly one."
|
|
1175
|
+
}
|
|
1176
|
+
]);
|
|
1177
|
+
}
|
|
980
1178
|
return signTaskBudget(args.task_budget_id);
|
|
981
1179
|
}
|
|
1180
|
+
if (args.sub_budget_id) {
|
|
1181
|
+
if (args.payment_id || args.payload_hash) {
|
|
1182
|
+
throw new z$1.ZodError([
|
|
1183
|
+
{
|
|
1184
|
+
code: z$1.ZodIssueCode.custom,
|
|
1185
|
+
path: ["sub_budget_id"],
|
|
1186
|
+
message: "sub_budget_id is mutually exclusive with payment_id / payload_hash \u2014 pass exactly one."
|
|
1187
|
+
}
|
|
1188
|
+
]);
|
|
1189
|
+
}
|
|
1190
|
+
return signSubBudget(args.sub_budget_id);
|
|
1191
|
+
}
|
|
982
1192
|
const resolved = await resolveSignContext(args, { allowDirectFallback: true });
|
|
983
1193
|
const typedData = resolved?.ctx.typedData ?? resolveTypedData(args);
|
|
984
1194
|
const payloadHash = resolved?.ctx.payloadHash ?? args.payload_hash;
|
|
@@ -1021,7 +1231,7 @@ function createToolHandlers(signer, options = {}) {
|
|
|
1021
1231
|
return { signature: result.signature, x402_binding: result.x402Binding, ...clientUpdateField(resolved) };
|
|
1022
1232
|
}),
|
|
1023
1233
|
haven_x402_sign_header: async (input) => runTool(async () => {
|
|
1024
|
-
const args =
|
|
1234
|
+
const args = parseStrictFor("haven_x402_sign_header", coercePaymentRequired(input));
|
|
1025
1235
|
const result = await signer.buildX402PaymentHeader(
|
|
1026
1236
|
args.payment_required,
|
|
1027
1237
|
args.x402_binding
|
|
@@ -1033,7 +1243,7 @@ function createToolHandlers(signer, options = {}) {
|
|
|
1033
1243
|
return { payment_header: result.paymentHeader, accepted: result.accepted };
|
|
1034
1244
|
}),
|
|
1035
1245
|
haven_sign_x402: async (input) => runTool(async () => {
|
|
1036
|
-
const args =
|
|
1246
|
+
const args = parseStrictFor("haven_sign_x402", coerceX402Expected(coercePaymentRequired(input)));
|
|
1037
1247
|
const resolved = await resolveSignContext(args, { allowDirectFallback: false });
|
|
1038
1248
|
const fetched = resolved?.kind === "x402" ? resolved.ctx : void 0;
|
|
1039
1249
|
const payloadHash = fetched?.payloadHash ?? args.payload_hash;
|
|
@@ -1069,7 +1279,7 @@ function createToolHandlers(signer, options = {}) {
|
|
|
1069
1279
|
};
|
|
1070
1280
|
}),
|
|
1071
1281
|
haven_sign_sweep_delegate: async (input) => runTool(async () => {
|
|
1072
|
-
const args =
|
|
1282
|
+
const args = parseStrictFor("haven_sign_sweep_delegate", input);
|
|
1073
1283
|
const result = await signer.signSweepAuthorization({
|
|
1074
1284
|
authorization: args.authorization,
|
|
1075
1285
|
expectedAuth: args.expected_auth,
|
|
@@ -1102,8 +1312,18 @@ function createToolHandlers(signer, options = {}) {
|
|
|
1102
1312
|
}
|
|
1103
1313
|
}
|
|
1104
1314
|
}
|
|
1105
|
-
function
|
|
1106
|
-
|
|
1315
|
+
function parseStrictFor(name, input) {
|
|
1316
|
+
const result = z$1.object(toolSchemas[name]).strict().safeParse(input ?? {});
|
|
1317
|
+
if (result.success) return result.data;
|
|
1318
|
+
const topLevelUnknown = result.error.issues.flatMap(
|
|
1319
|
+
(issue) => issue.code === "unrecognized_keys" && issue.path.length === 0 ? issue.keys : []
|
|
1320
|
+
);
|
|
1321
|
+
if (topLevelUnknown.length === 0) throw result.error;
|
|
1322
|
+
const unknown = [...new Set(topLevelUnknown)];
|
|
1323
|
+
const err = new Error(
|
|
1324
|
+
`${UNSUPPORTED_ARGUMENT_MARKER} ${name} does not accept ${unknown.map((k) => `"${k}"`).join(", ")} \u2014 this signer predates that argument form. Nothing was signed or fetched.`
|
|
1325
|
+
);
|
|
1326
|
+
throw err;
|
|
1107
1327
|
}
|
|
1108
1328
|
function coerceX402Expected(input) {
|
|
1109
1329
|
if (!input || typeof input !== "object") return input;
|
|
@@ -1142,6 +1362,25 @@ async function runTool(fn) {
|
|
|
1142
1362
|
}
|
|
1143
1363
|
}
|
|
1144
1364
|
function normalizeError(err) {
|
|
1365
|
+
if (err instanceof Error && err.message.startsWith(UNSUPPORTED_ARGUMENT_MARKER)) {
|
|
1366
|
+
const unknown = [...err.message.matchAll(/"([^"]+)"/g)].map((m) => m[1]);
|
|
1367
|
+
return {
|
|
1368
|
+
success: false,
|
|
1369
|
+
code: "UNSUPPORTED_ARGUMENT",
|
|
1370
|
+
message: err.message.replace(`${UNSUPPORTED_ARGUMENT_MARKER} `, ""),
|
|
1371
|
+
unknown_arguments: unknown,
|
|
1372
|
+
signer_version: SIGNER_VERSION,
|
|
1373
|
+
fallback: connectorUpgradeCommand(),
|
|
1374
|
+
next_action: AgentPaymentNextAction.StopAndTellUser,
|
|
1375
|
+
...nextStepWireFields(
|
|
1376
|
+
signerRefusalStep({
|
|
1377
|
+
nextAction: AgentPaymentNextAction.StopAndTellUser,
|
|
1378
|
+
nextTool: null,
|
|
1379
|
+
nextToolOmittedReason: `update @haven_ai/signer: run ${connectorUpgradeCommand()} and the repair line it prints, then repeat the same call`
|
|
1380
|
+
})
|
|
1381
|
+
)
|
|
1382
|
+
};
|
|
1383
|
+
}
|
|
1145
1384
|
if (err instanceof z$1.ZodError) {
|
|
1146
1385
|
return {
|
|
1147
1386
|
success: false,
|
|
@@ -1163,7 +1402,9 @@ function normalizeError(err) {
|
|
|
1163
1402
|
...nextStepWireFields(signerRefusalStep({
|
|
1164
1403
|
nextAction: AgentPaymentNextAction.StopAndTellUser,
|
|
1165
1404
|
nextTool: null,
|
|
1166
|
-
|
|
1405
|
+
// #3412: the upgrade command, not "re-run the connector" — a bare re-run
|
|
1406
|
+
// stops at "Missing --setup" on an existing install.
|
|
1407
|
+
nextToolOmittedReason: `update @haven_ai/signer: run ${connectorUpgradeCommand()} and the repair line it prints, then repeat the same call`
|
|
1167
1408
|
}))
|
|
1168
1409
|
};
|
|
1169
1410
|
}
|
|
@@ -1680,7 +1921,7 @@ function assertSupportedBindingVersion(received, supported, context) {
|
|
|
1680
1921
|
const highest = Math.max(...supported);
|
|
1681
1922
|
const outOfDate = received > highest;
|
|
1682
1923
|
const code = context === "x402 expected context" ? SignerRefusalCode.UnsupportedExpectedContextVersion : SignerRefusalCode.UnsupportedSweepBindingVersion;
|
|
1683
|
-
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
|
|
1924
|
+
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(", ")}.`;
|
|
1684
1925
|
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.`;
|
|
1685
1926
|
throw new HavenUnsupportedSignerVersionError(
|
|
1686
1927
|
`${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.`,
|
|
@@ -1806,8 +2047,8 @@ function signerInstructions() {
|
|
|
1806
2047
|
"Haven quote and prepare results report the expected-context version they will emit",
|
|
1807
2048
|
"(signer_compatibility.x402_expected_context_version). If that version is not in the list",
|
|
1808
2049
|
"above, this signer is out of date: STOP before signing, and tell the user to update",
|
|
1809
|
-
`@haven_ai/signer
|
|
1810
|
-
"MCP runtime. Do not edit the version field to a supported value \u2014 it is part of the",
|
|
2050
|
+
`@haven_ai/signer: run \`${connectorUpgradeCommand()}\`, then the repair line it prints,`,
|
|
2051
|
+
"which reinstalls the pinned MCP runtime. Do not edit the version field to a supported value \u2014 it is part of the",
|
|
1811
2052
|
"Haven-signed binding message, so changing it invalidates the signature.",
|
|
1812
2053
|
"",
|
|
1813
2054
|
"A version-mismatch refusal from haven_sign / haven_sign_x402 / haven_sign_sweep_delegate is",
|
|
@@ -1815,7 +2056,13 @@ function signerInstructions() {
|
|
|
1815
2056
|
"fallback fields alongside the message, so you can branch on it directly. Every signer",
|
|
1816
2057
|
"refusal also carries the next-step family: next_tool_name + next_tool_server_role when a",
|
|
1817
2058
|
"hosted tool follows (resolve the role against your own server names), else",
|
|
1818
|
-
"next_tool_omitted_reason saying why not."
|
|
2059
|
+
"next_tool_omitted_reason saying why not.",
|
|
2060
|
+
"",
|
|
2061
|
+
"An undeclared top-level argument is refused, not stripped: haven_sign answers",
|
|
2062
|
+
"UNSUPPORTED_ARGUMENT with unknown_arguments, signer_version and fallback (the update",
|
|
2063
|
+
"command) instead of dropping the key and answering the generic signing error \u2014 if you see",
|
|
2064
|
+
"that refusal, this signer predates the argument form you sent. Update the signer; nothing",
|
|
2065
|
+
"was signed, fetched or audited."
|
|
1819
2066
|
].join("\n");
|
|
1820
2067
|
}
|
|
1821
2068
|
async function loadSignerCredentials(path = process.env.HAVEN_CREDENTIALS) {
|
|
@@ -1918,8 +2165,6 @@ async function warnIfCredentialFilePermissive(path, log = (message) => process.s
|
|
|
1918
2165
|
}
|
|
1919
2166
|
|
|
1920
2167
|
// src/server.ts
|
|
1921
|
-
var SIGNER_NAME = "@haven_ai/signer";
|
|
1922
|
-
var SIGNER_VERSION = "0.6.0-alpha.0";
|
|
1923
2168
|
async function resolveSignerRuntime(options = {}) {
|
|
1924
2169
|
assertSupportedNodeVersion(options.nodeVersion);
|
|
1925
2170
|
if (options.delegateKey) {
|
|
@@ -1970,12 +2215,14 @@ function buildSignerMcpServer(signer, options = {}) {
|
|
|
1970
2215
|
clientIdentity: havenClientIdentity(SIGNER_NAME, SIGNER_VERSION)
|
|
1971
2216
|
}
|
|
1972
2217
|
});
|
|
1973
|
-
const registerTool = server.
|
|
2218
|
+
const registerTool = server.registerTool.bind(server);
|
|
1974
2219
|
for (const name of Object.keys(toolSchemas)) {
|
|
1975
2220
|
registerTool(
|
|
1976
2221
|
name,
|
|
1977
|
-
|
|
1978
|
-
|
|
2222
|
+
{
|
|
2223
|
+
description: toolDescriptions[name],
|
|
2224
|
+
inputSchema: z.object(toolSchemas[name]).passthrough()
|
|
2225
|
+
},
|
|
1979
2226
|
async (args) => toMcpResult(await handlers[name](args))
|
|
1980
2227
|
);
|
|
1981
2228
|
}
|