agenthub-multiagent-mcp 1.67.0 → 1.68.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.
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Idempotent message sends (fix-message-send-idempotency).
3
+ *
4
+ * Under load a POST /messages can time out after the server stored the
5
+ * message. The caller that retries is a model reading an error, and it will
6
+ * not reliably pass a token back — so the default key is derived from the
7
+ * message itself: re-issuing the same call produces the same key, and the
8
+ * server answers the retry with the original instead of delivering it twice.
9
+ * A caller that means to repeat identical text supplies its own key.
10
+ */
11
+ import { createHash } from "node:crypto";
12
+ function target(p) {
13
+ if (p.broadcast)
14
+ return "broadcast";
15
+ if (p.to_channel)
16
+ return `channel:${p.to_channel}`;
17
+ return `agent:${p.to_agent ?? ""}`;
18
+ }
19
+ /** The content-derived key: `auto:` + SHA-256 over every field that makes two
20
+ * sends the same message. */
21
+ export function deriveMessageKey(p) {
22
+ const canonical = JSON.stringify([
23
+ target(p),
24
+ p.type,
25
+ p.subject,
26
+ p.body,
27
+ p.reply_to ?? "",
28
+ p.priority || "normal",
29
+ ]);
30
+ return "auto:" + createHash("sha256").update(canonical).digest("hex");
31
+ }
32
+ /** An explicit key wins; otherwise the derived one. */
33
+ export function resolveMessageKey(p, explicit) {
34
+ if (typeof explicit === "string" && explicit.trim() !== "")
35
+ return explicit;
36
+ return deriveMessageKey(p);
37
+ }
38
+ /** Adds a plain statement to a duplicate answer, so it cannot be read as a
39
+ * fresh send (or as a failure to retry). Non-duplicates pass through. */
40
+ export function describeSendResult(result) {
41
+ if (!result.duplicate)
42
+ return result;
43
+ return {
44
+ ...result,
45
+ note: `Not sent again: this is identical to message ${result.message_id}, ` +
46
+ `already sent at ${result.created_at ?? "an earlier time"}. The recipient ` +
47
+ `has it once. To send the same text again on purpose, call again with a ` +
48
+ `new idempotency_key.`,
49
+ };
50
+ }
51
+ /** The error for a request that hit the client deadline. A GET keeps its old
52
+ * wording; a write says its outcome is unknown, because "timed out" read as
53
+ * "failed" is what turned slow successful sends into duplicates. */
54
+ export function timeoutMessage(method, path, timeoutMs) {
55
+ const base = `Request timed out after ${timeoutMs}ms: ${method} ${path}`;
56
+ if (method.toUpperCase() === "GET")
57
+ return base;
58
+ if (path === "/messages") {
59
+ return (`${base}. The outcome is unknown: the message may have been delivered. ` +
60
+ `Retrying the same call is safe — it carries the same idempotency key, ` +
61
+ `so it will not be delivered twice.`);
62
+ }
63
+ return `${base}. The outcome is unknown: the server may have applied this request.`;
64
+ }
65
+ //# sourceMappingURL=messageIdempotency.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"messageIdempotency.js","sourceRoot":"","sources":["../src/messageIdempotency.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAazC,SAAS,MAAM,CAAC,CAAkB;IAChC,IAAI,CAAC,CAAC,SAAS;QAAE,OAAO,WAAW,CAAC;IACpC,IAAI,CAAC,CAAC,UAAU;QAAE,OAAO,WAAW,CAAC,CAAC,UAAU,EAAE,CAAC;IACnD,OAAO,SAAS,CAAC,CAAC,QAAQ,IAAI,EAAE,EAAE,CAAC;AACrC,CAAC;AAED;6BAC6B;AAC7B,MAAM,UAAU,gBAAgB,CAAC,CAAkB;IACjD,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,CAAC;QAC/B,MAAM,CAAC,CAAC,CAAC;QACT,CAAC,CAAC,IAAI;QACN,CAAC,CAAC,OAAO;QACT,CAAC,CAAC,IAAI;QACN,CAAC,CAAC,QAAQ,IAAI,EAAE;QAChB,CAAC,CAAC,QAAQ,IAAI,QAAQ;KACvB,CAAC,CAAC;IACH,OAAO,OAAO,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AACxE,CAAC;AAED,uDAAuD;AACvD,MAAM,UAAU,iBAAiB,CAAC,CAAkB,EAAE,QAAkB;IACtE,IAAI,OAAO,QAAQ,KAAK,QAAQ,IAAI,QAAQ,CAAC,IAAI,EAAE,KAAK,EAAE;QAAE,OAAO,QAAQ,CAAC;IAC5E,OAAO,gBAAgB,CAAC,CAAC,CAAC,CAAC;AAC7B,CAAC;AASD;yEACyE;AACzE,MAAM,UAAU,kBAAkB,CAAuB,MAAS;IAChE,IAAI,CAAC,MAAM,CAAC,SAAS;QAAE,OAAO,MAAM,CAAC;IACrC,OAAO;QACL,GAAG,MAAM;QACT,IAAI,EACF,gDAAgD,MAAM,CAAC,UAAU,IAAI;YACrE,mBAAmB,MAAM,CAAC,UAAU,IAAI,iBAAiB,kBAAkB;YAC3E,yEAAyE;YACzE,sBAAsB;KACzB,CAAC;AACJ,CAAC;AAED;;oEAEoE;AACpE,MAAM,UAAU,cAAc,CAAC,MAAc,EAAE,IAAY,EAAE,SAAiB;IAC5E,MAAM,IAAI,GAAG,2BAA2B,SAAS,OAAO,MAAM,IAAI,IAAI,EAAE,CAAC;IACzE,IAAI,MAAM,CAAC,WAAW,EAAE,KAAK,KAAK;QAAE,OAAO,IAAI,CAAC;IAChD,IAAI,IAAI,KAAK,WAAW,EAAE,CAAC;QACzB,OAAO,CACL,GAAG,IAAI,iEAAiE;YACxE,wEAAwE;YACxE,oCAAoC,CACrC,CAAC;IACJ,CAAC;IACD,OAAO,GAAG,IAAI,qEAAqE,CAAC;AACtF,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=messageIdempotency.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"messageIdempotency.test.d.ts","sourceRoot":"","sources":["../src/messageIdempotency.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,130 @@
1
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
2
+ import { ApiClient } from "./client.js";
3
+ import { deriveMessageKey, describeSendResult, resolveMessageKey, timeoutMessage, } from "./messageIdempotency.js";
4
+ // fix-message-send-idempotency §2.
5
+ const base = {
6
+ to_agent: "krishi-dev",
7
+ type: "status",
8
+ subject: "Report",
9
+ body: "Deployed.",
10
+ reply_to: "m-1",
11
+ priority: "high",
12
+ };
13
+ describe("deriveMessageKey", () => {
14
+ it("is stable for identical arguments", () => {
15
+ expect(deriveMessageKey({ ...base })).toBe(deriveMessageKey({ ...base }));
16
+ expect(deriveMessageKey(base)).toMatch(/^auto:[0-9a-f]{64}$/);
17
+ });
18
+ it("differs when any field that makes it a different message differs", () => {
19
+ const variants = [
20
+ { ...base, to_agent: "other" },
21
+ { ...base, to_agent: undefined, to_channel: "krishi-dev" },
22
+ { ...base, to_agent: undefined, broadcast: true },
23
+ { ...base, type: "task" },
24
+ { ...base, subject: "Report 2" },
25
+ { ...base, body: "Deployed!" },
26
+ { ...base, reply_to: "m-2" },
27
+ { ...base, priority: "urgent" },
28
+ ];
29
+ const keys = new Set(variants.map(deriveMessageKey));
30
+ expect(keys.size).toBe(variants.length);
31
+ expect(keys.has(deriveMessageKey(base))).toBe(false);
32
+ });
33
+ it("treats an unset priority as normal, as the server does", () => {
34
+ expect(deriveMessageKey({ ...base, priority: undefined })).toBe(deriveMessageKey({ ...base, priority: "normal" }));
35
+ });
36
+ it("lets an explicit key win", () => {
37
+ expect(resolveMessageKey(base, "mine")).toBe("mine");
38
+ expect(resolveMessageKey(base, "")).toBe(deriveMessageKey(base));
39
+ expect(resolveMessageKey(base, undefined)).toBe(deriveMessageKey(base));
40
+ });
41
+ });
42
+ describe("describeSendResult", () => {
43
+ it("says a duplicate was not sent again, names the original, and how to repeat", () => {
44
+ const out = describeSendResult({
45
+ message_id: "m-9",
46
+ created_at: "2026-10-22T10:00:00Z",
47
+ duplicate: true,
48
+ });
49
+ expect(out.note).toMatch(/not sent again/i);
50
+ expect(out.note).toContain("m-9");
51
+ expect(out.note).toContain("2026-10-22T10:00:00Z");
52
+ expect(out.note).toContain("idempotency_key");
53
+ });
54
+ it("leaves a fresh send unchanged", () => {
55
+ const fresh = { message_id: "m-1", status: "pending" };
56
+ expect(describeSendResult(fresh)).toEqual(fresh);
57
+ });
58
+ });
59
+ describe("timeoutMessage", () => {
60
+ it("keeps the GET wording unchanged", () => {
61
+ expect(timeoutMessage("GET", "/messages/inbox/x", 45000)).toBe("Request timed out after 45000ms: GET /messages/inbox/x");
62
+ });
63
+ it("says a timed-out message send may have been delivered and that retrying is safe", () => {
64
+ const m = timeoutMessage("POST", "/messages", 45000);
65
+ expect(m).toMatch(/^Request timed out after 45000ms: POST \/messages/);
66
+ expect(m).toMatch(/may have been delivered/);
67
+ expect(m).toMatch(/retrying the same call is safe/i);
68
+ });
69
+ it("says any other write's outcome is unknown, without promising a safe retry", () => {
70
+ const m = timeoutMessage("PATCH", "/tasks/1", 45000);
71
+ expect(m).toMatch(/outcome is unknown/);
72
+ expect(m).not.toMatch(/safe/);
73
+ });
74
+ });
75
+ describe("ApiClient message sends", () => {
76
+ let fetchMock;
77
+ const bodies = () => fetchMock.mock.calls
78
+ .filter(([, init]) => init.method === "POST")
79
+ .map(([, init]) => JSON.parse(init.body));
80
+ beforeEach(() => {
81
+ fetchMock = vi.fn().mockImplementation(async (url) => {
82
+ const payload = url.endsWith("/messages/orig")
83
+ ? { id: "orig", from_agent: "asker", subject: "Q" }
84
+ : { message_id: "m-1", status: "pending", created_at: "t" };
85
+ return new Response(JSON.stringify(payload), {
86
+ status: 200,
87
+ headers: { "Content-Type": "application/json" },
88
+ });
89
+ });
90
+ vi.stubGlobal("fetch", fetchMock);
91
+ });
92
+ afterEach(() => {
93
+ vi.unstubAllGlobals();
94
+ });
95
+ it("sends the same derived key when an identical send is re-issued", async () => {
96
+ const client = new ApiClient("http://example.test", "k");
97
+ const params = { from_agent: "a", to_agent: "b", type: "task", subject: "s", body: "b" };
98
+ await client.sendMessage({ ...params });
99
+ await client.sendMessage({ ...params });
100
+ const [first, second] = bodies();
101
+ expect(first.idempotency_key).toMatch(/^auto:/);
102
+ expect(second.idempotency_key).toBe(first.idempotency_key);
103
+ });
104
+ it("sends distinct explicit keys as given, so a deliberate repeat goes through", async () => {
105
+ const client = new ApiClient("http://example.test", "k");
106
+ const params = { from_agent: "a", to_agent: "b", type: "task", subject: "s", body: "b" };
107
+ await client.sendMessage({ ...params, idempotency_key: "one" });
108
+ await client.sendMessage({ ...params, idempotency_key: "two" });
109
+ expect(bodies().map((b) => b.idempotency_key)).toEqual(["one", "two"]);
110
+ });
111
+ it("gives a reply a key derived from its recipient and reply_to", async () => {
112
+ const client = new ApiClient("http://example.test", "k");
113
+ await client.reply("orig", "me", "answer");
114
+ const [sent] = bodies();
115
+ expect(sent.to_agent).toBe("asker");
116
+ expect(sent.idempotency_key).toBe(deriveMessageKey({
117
+ to_agent: "asker",
118
+ type: "response",
119
+ subject: "Re: Q",
120
+ body: "answer",
121
+ reply_to: "orig",
122
+ }));
123
+ });
124
+ it("reports a timed-out send as possibly delivered", async () => {
125
+ fetchMock.mockRejectedValue(Object.assign(new Error("aborted"), { name: "TimeoutError" }));
126
+ const client = new ApiClient("http://example.test", "k");
127
+ await expect(client.sendMessage({ to_agent: "b", type: "task", subject: "s", body: "b" })).rejects.toThrow(/may have been delivered/);
128
+ });
129
+ });
130
+ //# sourceMappingURL=messageIdempotency.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"messageIdempotency.test.js","sourceRoot":"","sources":["../src/messageIdempotency.test.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/tools/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,SAAS,EAAE,OAAO,EAAqD,MAAM,cAAc,CAAC;AAOrG,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,oCAAoC,CAAC;AAmG/D,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,WAAW,EAAE,CAAC;IACrB,QAAQ,EAAE,OAAO,EAAE,CAAC;CACrB;AAED,MAAM,WAAW,WAAW;IAC1B,iBAAiB,EAAE,MAAM,MAAM,CAAC;IAChC,iBAAiB,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC,aAAa,EAAE,MAAM,IAAI,CAAC;IAC1B,aAAa,EAAE,MAAM,MAAM,CAAC;IAC5B,aAAa,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IACrC;;;OAGG;IACH,cAAc,EAAE,MAAM,WAAW,CAAC;IAClC,gDAAgD;IAChD,eAAe,CAAC,EAAE,MAAM,WAAW,CAAC;IACpC,oEAAoE;IACpE,iBAAiB,CAAC,EAAE,CAAC,QAAQ,EAAE;QAC7B,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;QACnB,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;KACvB,KAAK,IAAI,CAAC;CACZ;AAED,UAAU,WAAW;IACnB,EAAE,EAAE,MAAM,CAAC;IACX,WAAW,EAAE,MAAM,CAAC;IACpB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;CACpB;AAyLD,wBAAgB,aAAa,IAAI,IAAI,EAAE,CAk2FtC;AA2BD,wBAAsB,cAAc,CAClC,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,MAAM,EAAE,SAAS,EACjB,OAAO,EAAE,WAAW,GACnB,OAAO,CAAC,OAAO,CAAC,CA+qFlB"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/tools/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,SAAS,EAAE,OAAO,EAAqD,MAAM,cAAc,CAAC;AAOrG,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,oCAAoC,CAAC;AA0G/D,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,WAAW,EAAE,CAAC;IACrB,QAAQ,EAAE,OAAO,EAAE,CAAC;CACrB;AAED,MAAM,WAAW,WAAW;IAC1B,iBAAiB,EAAE,MAAM,MAAM,CAAC;IAChC,iBAAiB,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC,aAAa,EAAE,MAAM,IAAI,CAAC;IAC1B,aAAa,EAAE,MAAM,MAAM,CAAC;IAC5B,aAAa,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IACrC;;;OAGG;IACH,cAAc,EAAE,MAAM,WAAW,CAAC;IAClC,gDAAgD;IAChD,eAAe,CAAC,EAAE,MAAM,WAAW,CAAC;IACpC,oEAAoE;IACpE,iBAAiB,CAAC,EAAE,CAAC,QAAQ,EAAE;QAC7B,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;QACnB,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;KACvB,KAAK,IAAI,CAAC;CACZ;AAED,UAAU,WAAW;IACnB,EAAE,EAAE,MAAM,CAAC;IACX,WAAW,EAAE,MAAM,CAAC;IACpB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;CACpB;AAyLD,wBAAgB,aAAa,IAAI,IAAI,EAAE,CAk3FtC;AA2BD,wBAAsB,cAAc,CAClC,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,MAAM,EAAE,SAAS,EACjB,OAAO,EAAE,WAAW,GACnB,OAAO,CAAC,OAAO,CAAC,CAurFlB"}
@@ -21,6 +21,11 @@ import { ENG_IC_CHARTER } from "../employee.js";
21
21
  import { promises as fsp } from "node:fs";
22
22
  import * as os from "node:os";
23
23
  import * as path from "node:path";
24
+ import { describeSendResult } from "../messageIdempotency.js";
25
+ // fix-message-send-idempotency — shared by the four message tools.
26
+ const IDEMPOTENCY_KEY_DESCRIPTION = "Optional. Leave unset: a key derived from the message content is sent, so " +
27
+ "re-issuing this exact call (e.g. after a timeout) never delivers it twice. " +
28
+ "Pass a new value only to send identical text again on purpose within an hour.";
24
29
  // add-zoho-attachments — downloads land on local disk and the agent receives a
25
30
  // PATH. Inline content stays opt-in and small; a 5 MB catalogue as text costs a
26
31
  // context window and buys nothing a file reader can't do better.
@@ -659,6 +664,10 @@ export function registerTools() {
659
664
  enum: ["normal", "high", "urgent"],
660
665
  description: "Message priority (default: normal)",
661
666
  },
667
+ idempotency_key: {
668
+ type: "string",
669
+ description: IDEMPOTENCY_KEY_DESCRIPTION,
670
+ },
662
671
  },
663
672
  required: ["to", "type", "subject", "body"],
664
673
  },
@@ -686,6 +695,10 @@ export function registerTools() {
686
695
  type: "string",
687
696
  description: "Full message content",
688
697
  },
698
+ idempotency_key: {
699
+ type: "string",
700
+ description: IDEMPOTENCY_KEY_DESCRIPTION,
701
+ },
689
702
  },
690
703
  required: ["channel", "type", "subject", "body"],
691
704
  },
@@ -709,6 +722,10 @@ export function registerTools() {
709
722
  type: "string",
710
723
  description: "Full message content",
711
724
  },
725
+ idempotency_key: {
726
+ type: "string",
727
+ description: IDEMPOTENCY_KEY_DESCRIPTION,
728
+ },
712
729
  },
713
730
  required: ["type", "subject", "body"],
714
731
  },
@@ -766,6 +783,10 @@ export function registerTools() {
766
783
  type: "string",
767
784
  description: "Reply content",
768
785
  },
786
+ idempotency_key: {
787
+ type: "string",
788
+ description: IDEMPOTENCY_KEY_DESCRIPTION,
789
+ },
769
790
  },
770
791
  required: ["message_id", "body"],
771
792
  },
@@ -3707,8 +3728,9 @@ export async function handleToolCall(name, args, client, context) {
3707
3728
  subject: args.subject,
3708
3729
  body: args.body,
3709
3730
  priority: args.priority,
3731
+ idempotency_key: args.idempotency_key,
3710
3732
  });
3711
- return wrapWithPendingItems(client, agentId, result, context);
3733
+ return wrapWithPendingItems(client, agentId, describeSendResult(result), context);
3712
3734
  }
3713
3735
  case "send_to_channel": {
3714
3736
  if (!agentId)
@@ -3719,8 +3741,9 @@ export async function handleToolCall(name, args, client, context) {
3719
3741
  type: args.type,
3720
3742
  subject: args.subject,
3721
3743
  body: args.body,
3744
+ idempotency_key: args.idempotency_key,
3722
3745
  });
3723
- return wrapWithPendingItems(client, agentId, result, context);
3746
+ return wrapWithPendingItems(client, agentId, describeSendResult(result), context);
3724
3747
  }
3725
3748
  case "broadcast": {
3726
3749
  if (!agentId)
@@ -3731,8 +3754,9 @@ export async function handleToolCall(name, args, client, context) {
3731
3754
  type: args.type,
3732
3755
  subject: args.subject,
3733
3756
  body: args.body,
3757
+ idempotency_key: args.idempotency_key,
3734
3758
  });
3735
- return wrapWithPendingItems(client, agentId, result, context);
3759
+ return wrapWithPendingItems(client, agentId, describeSendResult(result), context);
3736
3760
  }
3737
3761
  case "check_inbox": {
3738
3762
  // No wrapping - already fetching messages.
@@ -3756,8 +3780,8 @@ export async function handleToolCall(name, args, client, context) {
3756
3780
  case "reply": {
3757
3781
  if (!agentId)
3758
3782
  throw new Error("Not registered. Call agent_register first.");
3759
- const result = await client.reply(args.message_id, agentId, args.body);
3760
- return wrapWithPendingItems(client, agentId, result, context);
3783
+ const result = await client.reply(args.message_id, agentId, args.body, args.idempotency_key);
3784
+ return wrapWithPendingItems(client, agentId, describeSendResult(result), context);
3761
3785
  }
3762
3786
  case "deliver_file": {
3763
3787
  if (!agentId)