agenthub-multiagent-mcp 1.73.0 → 1.75.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.
@@ -23,6 +23,7 @@ import * as os from "node:os";
23
23
  import * as path from "node:path";
24
24
  import { describeSendResult } from "../messageIdempotency.js";
25
25
  import { readZohoAttachments } from "../zohoAttachments.js";
26
+ import { isUnknownSendOutcome, unknownSendOutcomeError } from "../zohoSendOutcome.js";
26
27
  // fix-message-send-idempotency — shared by the four message tools.
27
28
  const IDEMPOTENCY_KEY_DESCRIPTION = "Optional. Leave unset: a key derived from the message content is sent, so " +
28
29
  "re-issuing this exact call (e.g. after a timeout) never delivers it twice. " +
@@ -1743,7 +1744,22 @@ export function registerTools() {
1743
1744
  inputSchema: {
1744
1745
  type: "object",
1745
1746
  properties: {
1746
- peer: { type: "string", description: "The peer agent id to brainstorm with (e.g. 'sumit-mobile')." },
1747
+ peer: { type: "string", description: "The peer agent id to brainstorm with (e.g. 'sumit-mobile'). For a group, use participants instead." },
1748
+ participants: {
1749
+ type: "array",
1750
+ items: { type: "string" },
1751
+ description: "Group brainstorm (add-multi-agent-conversations): the agent ids to invite, at most 5 besides you. One id behaves like peer. More than one needs AGENTHUB_GROUP_CONVERSATIONS_ENABLED on the server (else group_conversations_disabled).",
1752
+ },
1753
+ turn_policy: {
1754
+ type: "string",
1755
+ enum: ["round_robin", "free", "moderated"],
1756
+ description: "Group only. round_robin (default): the server names next_speaker in order of joining. free: anyone, but not twice in a row. moderated: the moderator speaks and hands the floor with brainstorm_yield.",
1757
+ },
1758
+ moderator: { type: "string", description: "moderated only: you (default) or one of the participants." },
1759
+ mirror_channel: {
1760
+ type: "string",
1761
+ description: "Group only: a Goshti channel (e.g. '#winery') to mirror every turn to as one thread; people's replies there become human_note turns.",
1762
+ },
1747
1763
  topic: { type: "string", description: "One-line subject of the brainstorm." },
1748
1764
  escalation: {
1749
1765
  type: "string",
@@ -1756,7 +1772,7 @@ export function registerTools() {
1756
1772
  description: "Peer-silence backstop in seconds (default 600).",
1757
1773
  },
1758
1774
  },
1759
- required: ["peer", "topic"],
1775
+ required: ["topic"],
1760
1776
  },
1761
1777
  },
1762
1778
  {
@@ -1783,6 +1799,17 @@ export function registerTools() {
1783
1799
  enum: ["message", "propose_conclude", "human_note", "system"],
1784
1800
  description: "Turn kind (default 'message').",
1785
1801
  },
1802
+ // add-live-work-graphs: these are what the brainstorm graph is drawn from.
1803
+ tag: {
1804
+ type: "string",
1805
+ enum: ["idea", "question", "objection", "decision", "summary"],
1806
+ description: "What this turn is for. Tag every turn you can: an untagged turn is drawn as a plain message.",
1807
+ },
1808
+ refs: {
1809
+ type: "array",
1810
+ items: { type: "integer" },
1811
+ description: "The seq numbers of earlier turns in this session that this turn answers or objects to (at most 20). A turn not yet written is refused with invalid_ref.",
1812
+ },
1786
1813
  },
1787
1814
  required: ["session_id", "body"],
1788
1815
  },
@@ -1799,6 +1826,18 @@ export function registerTools() {
1799
1826
  required: ["session_id"],
1800
1827
  },
1801
1828
  },
1829
+ {
1830
+ name: "brainstorm_yield",
1831
+ description: "Moderated group brainstorm only: as the moderator, hand the floor to a joined participant (yield to yourself to take it back). Refusals: not_moderated, not_moderator, yield_target_not_joined. (requires agent_register first)",
1832
+ inputSchema: {
1833
+ type: "object",
1834
+ properties: {
1835
+ session_id: { type: "string", description: "The brainstorm session id." },
1836
+ to: { type: "string", description: "The agent id that gets the floor." },
1837
+ },
1838
+ required: ["session_id", "to"],
1839
+ },
1840
+ },
1802
1841
  {
1803
1842
  name: "brainstorm_conclude",
1804
1843
  description: "Conclude a brainstorm with a structured outcome (mutual consensus). Succeeds ONLY when the peer's last turn was a propose_conclude — else returns conclusion_not_confirmed. The outcome seeds an OpenSpec change. (requires agent_register first)",
@@ -3083,6 +3122,17 @@ function requireBrainstormProject(context) {
3083
3122
  }
3084
3123
  return agentState.project_id;
3085
3124
  }
3125
+ // brainstormTurnAnnotations picks brainstorm_turn's tag and refs out of the
3126
+ // tool arguments (add-live-work-graphs). Absent ones are left out of the body;
3127
+ // the server validates both and refuses a bad ref with 400 invalid_ref.
3128
+ function brainstormTurnAnnotations(args) {
3129
+ const out = {};
3130
+ if (typeof args.tag === "string" && args.tag !== "")
3131
+ out.tag = args.tag;
3132
+ if (Array.isArray(args.refs) && args.refs.length > 0)
3133
+ out.refs = args.refs.map((r) => Number(r));
3134
+ return out;
3135
+ }
3086
3136
  // brainstormError formats a structured (non-throwing) failure result for a
3087
3137
  // brainstorm tool, mirroring the ticket_get error shape.
3088
3138
  function brainstormError(tool, result) {
@@ -4514,21 +4564,31 @@ export async function handleToolCall(name, args, client, context) {
4514
4564
  // =============================================================================
4515
4565
  case "brainstorm_open": {
4516
4566
  const projectId = requireBrainstormProject(context);
4567
+ const participants = Array.isArray(args.participants) ? args.participants : undefined;
4517
4568
  const result = await client.brainstormOpen(projectId, {
4518
4569
  peer: args.peer,
4519
4570
  topic: args.topic,
4520
4571
  escalation: args.escalation,
4521
4572
  max_turns: args.max_turns,
4522
4573
  silence_deadline_secs: args.silence_deadline_secs,
4574
+ participants,
4575
+ turn_policy: args.turn_policy,
4576
+ moderator: args.moderator,
4577
+ mirror_channel: args.mirror_channel,
4523
4578
  });
4524
4579
  if (!result.ok)
4525
4580
  return brainstormError("brainstorm_open", result);
4581
+ const invitees = [args.peer, ...(participants ?? [])].filter(Boolean).join(", ");
4526
4582
  return {
4527
4583
  ok: true,
4528
4584
  session_id: result.data.session_id,
4529
4585
  display_number: result.data.display_number,
4530
4586
  invite_delivered: result.data.invite_delivered ?? true,
4531
- message: `Brainstorm ${result.data.display_number} opened with ${args.peer}. Invite delivered${result.data.invite_delivered === false ? " FAILED" : ""}.`,
4587
+ turn_policy: result.data.turn_policy,
4588
+ invites: result.data.invites,
4589
+ mirror_thread_id: result.data.mirror_thread_id,
4590
+ mirror_warning: result.data.mirror_warning,
4591
+ message: `Brainstorm ${result.data.display_number} opened with ${invitees}. Invite delivered${result.data.invite_delivered === false ? " FAILED" : ""}.`,
4532
4592
  };
4533
4593
  }
4534
4594
  case "brainstorm_accept": {
@@ -4552,7 +4612,8 @@ export async function handleToolCall(name, args, client, context) {
4552
4612
  }
4553
4613
  case "brainstorm_turn": {
4554
4614
  const projectId = requireBrainstormProject(context);
4555
- const result = await client.brainstormTurn(projectId, args.session_id, args.body, args.kind);
4615
+ const result = await client.brainstormTurn(projectId, args.session_id, args.body, args.kind, brainstormTurnAnnotations(args) // add-live-work-graphs
4616
+ );
4556
4617
  if (!result.ok)
4557
4618
  return brainstormError("brainstorm_turn", result);
4558
4619
  return { ok: true, turn: result.data, message: `Turn ${result.data.seq} posted.` };
@@ -4563,12 +4624,32 @@ export async function handleToolCall(name, args, client, context) {
4563
4624
  if (!result.ok)
4564
4625
  return brainstormError("brainstorm_poll", result);
4565
4626
  const turns = Array.isArray(result.data.turns) ? result.data.turns : [];
4627
+ // add-multi-agent-conversations: a group session names who speaks next;
4628
+ // your_turn is set only when it does (undefined for two-agent and free).
4629
+ const nextSpeaker = result.data.next_speaker || undefined;
4566
4630
  return {
4567
4631
  ok: true,
4568
4632
  state: result.data.state,
4569
4633
  turn_count: result.data.turn_count,
4570
4634
  turns,
4571
- message: `${turns.length} new turn(s); state=${result.data.state}.`,
4635
+ next_speaker: nextSpeaker,
4636
+ your_turn: nextSpeaker ? nextSpeaker === agentId : undefined,
4637
+ turn_policy: result.data.turn_policy || undefined,
4638
+ conclusion_pending: result.data.conclusion_pending,
4639
+ participants: result.data.participants,
4640
+ message: `${turns.length} new turn(s); state=${result.data.state}${nextSpeaker ? `; next speaker ${nextSpeaker}` : ""}.`,
4641
+ };
4642
+ }
4643
+ case "brainstorm_yield": {
4644
+ const projectId = requireBrainstormProject(context);
4645
+ const result = await client.brainstormYield(projectId, args.session_id, args.to);
4646
+ if (!result.ok)
4647
+ return brainstormError("brainstorm_yield", result);
4648
+ return {
4649
+ ok: true,
4650
+ floor: result.data.floor,
4651
+ next_speaker: result.data.next_speaker,
4652
+ message: `Floor handed to ${result.data.next_speaker}.`,
4572
4653
  };
4573
4654
  }
4574
4655
  case "brainstorm_conclude": {
@@ -5525,6 +5606,7 @@ export async function handleToolCall(name, args, client, context) {
5525
5606
  throw new Error("Not registered. Call agent_register first.");
5526
5607
  // Read and check every local file before any request (never a partial send).
5527
5608
  const attachments = readZohoAttachments(args.attachments);
5609
+ const sendMode = args.mode === "send" ? "send" : "draft";
5528
5610
  const res = await client.zohoSend({
5529
5611
  mailbox: args.mailbox,
5530
5612
  to: args.to,
@@ -5539,6 +5621,11 @@ export async function handleToolCall(name, args, client, context) {
5539
5621
  reply_all: args.reply_all,
5540
5622
  mode: args.mode,
5541
5623
  dedupe_key: args.dedupe_key,
5624
+ }).catch((err) => {
5625
+ if (isUnknownSendOutcome(err)) {
5626
+ throw unknownSendOutcomeError(args.mailbox, sendMode, err);
5627
+ }
5628
+ throw err;
5542
5629
  });
5543
5630
  const extras = (res.threaded ? " Threaded as a reply." : "") +
5544
5631
  (attachments.length > 0 ? ` ${attachments.length} attachment(s).` : "");