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.
- package/dist/channel.js +24 -8
- package/dist/channel.js.map +1 -1
- package/dist/client.d.ts +6 -1
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +14 -4
- package/dist/client.js.map +1 -1
- package/dist/messageIdempotency.d.ts +41 -0
- package/dist/messageIdempotency.d.ts.map +1 -0
- package/dist/messageIdempotency.js +65 -0
- package/dist/messageIdempotency.js.map +1 -0
- package/dist/messageIdempotency.test.d.ts +2 -0
- package/dist/messageIdempotency.test.d.ts.map +1 -0
- package/dist/messageIdempotency.test.js +130 -0
- package/dist/messageIdempotency.test.js.map +1 -0
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +29 -5
- package/dist/tools/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -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 @@
|
|
|
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;
|
|
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"}
|
package/dist/tools/index.js
CHANGED
|
@@ -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)
|