@haven_ai/mcp 0.0.0-dev.202609302323.7ff7df0 → 0.0.0-dev.202610011057.1c1da3f

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 CHANGED
@@ -119,7 +119,7 @@ Environment variable form:
119
119
  - `haven_list_receipts`
120
120
  - `haven_open_task_budget` (#3329)
121
121
  - `haven_close_task_budget` (#3329)
122
- - `haven_submit` (#3329 — relays a local signer signature by `task_budget_id`; a `payment_id` is refused on this runtime, which signs and submits payments inline)
122
+ - `haven_submit` (#3329 — relays a local signer signature by `task_budget_id`, or (#3506) by `sub_budget_id` for a sub-budget this agent delegates (listed in `haven_get_agent`'s `pendingSubBudgetSignatures[]`); a `payment_id` is refused on this runtime, which signs and submits payments inline)
123
123
 
124
124
  ### `idempotencyKey` is removed — send `idempotency_key`
125
125
 
package/dist/cli.cjs CHANGED
@@ -353,12 +353,13 @@ var toolSchemas = {
353
353
  haven_close_task_budget: {
354
354
  task_budget_id: v3.z.string().min(1)
355
355
  },
356
- // #3329: relays a signature from the local signer — either the open/close
357
- // signature for a task budget, or (schema parity with the hosted surface)
358
- // a direct-payment signature by payment_id. Exactly one of task_budget_id /
359
- // payment_id, never both or neither.
356
+ // #3329 / #3506: relays a signature from the local signer — the open/close
357
+ // signature for a task budget or a sub-budget row, or (schema parity with
358
+ // the hosted surface) a direct-payment signature by payment_id. Exactly one
359
+ // of task_budget_id / sub_budget_id / payment_id, never two or none.
360
360
  haven_submit: {
361
361
  task_budget_id: v3.z.string().min(1).optional(),
362
+ sub_budget_id: v3.z.string().min(1).optional(),
362
363
  payment_id: v3.z.string().min(1).optional(),
363
364
  signature: v3.z.string().regex(/^0x[0-9a-fA-F]+$/, "signature must be a 0x-prefixed hex string")
364
365
  }
@@ -388,9 +389,11 @@ var CLOSE_TASK_BUDGET_DESCRIPTION = [
388
389
  ].join(" ");
389
390
  var SUBMIT_DESCRIPTION = [
390
391
  "Relay a signature from the local signer. Pass exactly one of task_budget_id (from",
391
- "haven_open_task_budget or haven_close_task_budget) or payment_id \u2014 never both, never neither \u2014",
392
- "plus signature. For a task budget this opens or closes it on-chain and returns { task_budget,",
393
- 'status }; a close in progress may return { task_budget, status: "closed", close_tx_hash }.'
392
+ "haven_open_task_budget or haven_close_task_budget), sub_budget_id (from haven_get_agent",
393
+ "pendingSubBudgetSignatures[]) or payment_id \u2014 never two, never none \u2014 plus signature.",
394
+ "For a task budget this opens or closes it on-chain and returns { task_budget,",
395
+ 'status }; a close in progress may return { task_budget, status: "closed", close_tx_hash }.',
396
+ "A sub_budget_id returns { sub_budget, status } with the next signer step, if one is owed."
394
397
  ].join(" ");
395
398
  var toolDescriptions = {
396
399
  haven_send: sdk.composeDescription(sdk.toolDescriptions.send),
@@ -412,6 +415,98 @@ var toolDescriptions = {
412
415
  haven_close_task_budget: CLOSE_TASK_BUDGET_DESCRIPTION,
413
416
  haven_submit: SUBMIT_DESCRIPTION
414
417
  };
418
+ function subBudgetSignStep(subBudgetId) {
419
+ return {
420
+ next_action: "sign",
421
+ next_tool: "mcp__haven-signer__haven_sign",
422
+ next_arguments: { sub_budget_id: subBudgetId }
423
+ };
424
+ }
425
+ async function submitSubBudgetTool(haven, subBudgetId, signature) {
426
+ let result;
427
+ try {
428
+ result = await haven.submitSubBudget(subBudgetId, signature);
429
+ } catch (err) {
430
+ const code = err instanceof sdk.HavenApiError ? err.body?.error_code : void 0;
431
+ if (code === "close_needs_reprepare") {
432
+ let fresh;
433
+ try {
434
+ fresh = await haven.closeSubBudget(subBudgetId);
435
+ } catch (closeErr) {
436
+ return normalizeError(closeErr);
437
+ }
438
+ if (fresh.status === "closed") {
439
+ return {
440
+ success: true,
441
+ data: {
442
+ sub_budget: fresh.subBudget,
443
+ status: "closed",
444
+ next_action: "none",
445
+ next_tool_omitted_reason: "the sub-budget is closed on-chain; nothing further to sign or submit for it"
446
+ }
447
+ };
448
+ }
449
+ return {
450
+ success: false,
451
+ code: "CLOSE_NEEDS_REPREPARE",
452
+ message: "The stored close operation for this sub-budget was stale, so nothing was relayed. Haven prepared a fresh one: sign it with haven_sign, then call haven_submit again with the new signature.",
453
+ statusCode: 409,
454
+ ...subBudgetSignStep(subBudgetId)
455
+ };
456
+ }
457
+ if (code === "close_outcome_unconfirmed") {
458
+ return {
459
+ success: false,
460
+ code: "CLOSE_OUTCOME_UNCONFIRMED",
461
+ message: "The close operation was sent but its outcome is not confirmed yet (on Base that can take tens of minutes). Wait, then call haven_submit again with the same arguments: it reports closed once the chain has finalised the disable, or prepares a fresh operation to sign if the earlier one did not land.",
462
+ statusCode: 502,
463
+ next_action: "check_status_later",
464
+ next_tool: "haven_submit",
465
+ next_arguments: { sub_budget_id: subBudgetId, signature }
466
+ };
467
+ }
468
+ return normalizeError(err);
469
+ }
470
+ return runTool(async () => {
471
+ const submitted = {
472
+ sub_budget: result.subBudget,
473
+ status: result.status,
474
+ ...result.closeTxHash !== void 0 ? { close_tx_hash: result.closeTxHash } : {}
475
+ };
476
+ if (result.status === "closed") {
477
+ return {
478
+ ...submitted,
479
+ next_action: "none",
480
+ next_tool_omitted_reason: "the sub-budget is closed on-chain; nothing further to sign or submit for it"
481
+ };
482
+ }
483
+ let pending;
484
+ try {
485
+ pending = await haven.listPendingSubBudgetSignatures();
486
+ } catch {
487
+ pending = null;
488
+ }
489
+ if (pending === null) {
490
+ return {
491
+ ...submitted,
492
+ next_action: "check_status_later",
493
+ next_tool: "haven_get_agent",
494
+ next_arguments: {},
495
+ reason: "This sub-budget row is open, but Haven could not say whether its tree has another row awaiting your signature. Call haven_get_agent and sign any pendingSubBudgetSignatures row."
496
+ };
497
+ }
498
+ const tree = result.subBudget.parentSubBudgetId ?? result.subBudget.id;
499
+ const sibling = pending.find(
500
+ (row) => row.purpose === "open" && row.subBudgetId !== result.subBudget.id && (row.parentSubBudgetId ?? row.subBudgetId) === tree
501
+ );
502
+ if (sibling) return { ...submitted, ...subBudgetSignStep(sibling.subBudgetId) };
503
+ return {
504
+ ...submitted,
505
+ next_action: "none",
506
+ next_tool_omitted_reason: "no other row of this sub-budget awaits your signature, so the sub-budget is fully open and the sub-agent can spend through its grant"
507
+ };
508
+ });
509
+ }
415
510
  function createToolHandlers(haven) {
416
511
  return {
417
512
  haven_send: async (input) => {
@@ -567,7 +662,17 @@ function createToolHandlers(haven) {
567
662
  const args = objectInput("haven_get_resume_state", input);
568
663
  return runTool(async () => haven.getResumeState(args.payment_id));
569
664
  },
570
- haven_get_agent: async () => runTool(async () => haven.getAgentSummary()),
665
+ haven_get_agent: async () => runTool(async () => {
666
+ const summary = await haven.getAgentSummary();
667
+ if (!Array.isArray(summary.pendingSubBudgetSignatures)) return summary;
668
+ return {
669
+ ...summary,
670
+ pendingSubBudgetSignatures: summary.pendingSubBudgetSignatures.map((row) => ({
671
+ ...row,
672
+ ...subBudgetSignStep(row.subBudgetId)
673
+ }))
674
+ };
675
+ }),
571
676
  haven_get_allowances: async () => runTool(async () => haven.getAllowances()),
572
677
  haven_sweep_delegate: async () => runTool(async () => haven.sweepDelegate()),
573
678
  haven_discover_tools: async (input) => {
@@ -697,14 +802,18 @@ function createToolHandlers(haven) {
697
802
  haven_submit: async (input) => {
698
803
  const args = objectInput("haven_submit", input);
699
804
  const hasTaskBudget = typeof args.task_budget_id === "string" && args.task_budget_id.length > 0;
805
+ const hasSubBudget = typeof args.sub_budget_id === "string" && args.sub_budget_id.length > 0;
700
806
  const hasPayment = typeof args.payment_id === "string" && args.payment_id.length > 0;
701
- if (hasTaskBudget === hasPayment) {
807
+ if ([hasTaskBudget, hasSubBudget, hasPayment].filter(Boolean).length !== 1) {
702
808
  return {
703
809
  success: false,
704
810
  code: "INVALID_INPUT",
705
- message: "haven_submit takes exactly one of task_budget_id or payment_id, never both or neither. Nothing was relayed."
811
+ message: "haven_submit takes exactly one of payment_id, task_budget_id or sub_budget_id, never two or none. Nothing was relayed."
706
812
  };
707
813
  }
814
+ if (hasSubBudget) {
815
+ return submitSubBudgetTool(haven, args.sub_budget_id, args.signature);
816
+ }
708
817
  return runTool(async () => {
709
818
  if (hasTaskBudget) {
710
819
  const result = await haven.submitTaskBudget(args.task_budget_id, args.signature);
@@ -715,7 +824,7 @@ function createToolHandlers(haven) {
715
824
  };
716
825
  }
717
826
  throw new sdk.HavenApiError(
718
- "haven_submit with payment_id is not supported on the local MCP surface: haven_send signs and submits a direct payment in one call, so there is no separate relay step. Use haven_submit with task_budget_id to relay a task-budget open/close signature.",
827
+ "haven_submit with payment_id is not supported on the local MCP surface: haven_send signs and submits a direct payment in one call, so there is no separate relay step. Use haven_submit with task_budget_id or sub_budget_id to relay a budget open/close signature.",
719
828
  400
720
829
  );
721
830
  });
@@ -1147,7 +1256,7 @@ async function resolveHavenClient(options = {}) {
1147
1256
  return { client, credentials };
1148
1257
  }
1149
1258
  var MCP_NAME = "@haven_ai/mcp";
1150
- var MCP_VERSION = "0.0.0-dev.202609302323.7ff7df0";
1259
+ var MCP_VERSION = "0.0.0-dev.202610011057.1c1da3f";
1151
1260
  var MCP_INSTRUCTIONS = [
1152
1261
  "Haven local MCP server: signs in-process with the delegate key it holds on",
1153
1262
  "this machine \u2014 the key never leaves this process. Call haven_get_agent",