agenthub-multiagent-mcp 1.51.0 → 1.52.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.
Files changed (47) hide show
  1. package/README.md +192 -192
  2. package/dist/channel.js +3 -3
  3. package/dist/client.d.ts +30 -1
  4. package/dist/client.d.ts.map +1 -1
  5. package/dist/client.js +107 -4
  6. package/dist/client.js.map +1 -1
  7. package/dist/client.unit.test.js +50 -0
  8. package/dist/client.unit.test.js.map +1 -1
  9. package/dist/commands/setup-shell.js +11 -11
  10. package/dist/daemon.js +76 -1
  11. package/dist/daemon.js.map +1 -1
  12. package/dist/hooks/periodicReminder.js +9 -9
  13. package/dist/setup.js +56 -56
  14. package/dist/streamingRuntimeLib.d.ts +132 -0
  15. package/dist/streamingRuntimeLib.d.ts.map +1 -0
  16. package/dist/streamingRuntimeLib.js +155 -0
  17. package/dist/streamingRuntimeLib.js.map +1 -0
  18. package/dist/streamingRuntimeLib.test.d.ts +2 -0
  19. package/dist/streamingRuntimeLib.test.d.ts.map +1 -0
  20. package/dist/streamingRuntimeLib.test.js +147 -0
  21. package/dist/streamingRuntimeLib.test.js.map +1 -0
  22. package/dist/tools/index.d.ts.map +1 -1
  23. package/dist/tools/index.js +141 -13
  24. package/dist/tools/index.js.map +1 -1
  25. package/dist/tools/openspec-viz/diagram-generator.js +54 -54
  26. package/dist/tools/openspec-viz/html-generator.js +503 -503
  27. package/dist/tools/tools.test.js +3 -2
  28. package/dist/tools/tools.test.js.map +1 -1
  29. package/dist/tools/zohoAttachments.test.d.ts +10 -0
  30. package/dist/tools/zohoAttachments.test.d.ts.map +1 -0
  31. package/dist/tools/zohoAttachments.test.js +204 -0
  32. package/dist/tools/zohoAttachments.test.js.map +1 -0
  33. package/dist/worker.js +10 -10
  34. package/native/agenthub-memvid/agenthub-memvid.linux-x64-gnu.node +0 -0
  35. package/native/agenthub-memvid/index.d.ts +58 -58
  36. package/native/agenthub-memvid/index.js +319 -319
  37. package/package.json +77 -77
  38. package/skills/catalog.json +76 -76
  39. package/skills/commands/close-session.md +144 -144
  40. package/skills/commands/start-session.md +77 -77
  41. package/skills/manifest.json +50 -50
  42. package/skills/skills/code-brain-hook/SKILL.md +61 -61
  43. package/skills/skills/deploy-staging/SKILL.md +164 -164
  44. package/skills/skills/deploy-vps-openclaw/SKILL.md +97 -97
  45. package/skills/skills/file-bug/SKILL.md +75 -75
  46. package/skills/skills/karpathy-guidelines/SKILL.md +67 -67
  47. package/skills/skills/multiagent-brainstorm/SKILL.md +142 -142
@@ -0,0 +1,147 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { streamingEnabled, assertSubscriptionAuth, decideInbound, buildTurnContent, toUserTurn, shouldRecycle, defaultRecycleOptions, newRuntimeStats, recordResultUsage, formatUsageLine, inSessionMcpEnv, assertWorkerIdentityReady, } from "./streamingRuntimeLib.js";
3
+ // add-employee-streaming-runtime §2 — pure-helper tests.
4
+ describe("streamingEnabled (kill switch §2.1)", () => {
5
+ it("off by default / falsey", () => {
6
+ for (const v of [undefined, "", "false", "0", "no", "off"]) {
7
+ expect(streamingEnabled(v === undefined ? {} : { AGENTHUB_EMPLOYEE_STREAMING_ENABLED: v })).toBe(false);
8
+ }
9
+ });
10
+ it("on for explicit truthy", () => {
11
+ for (const v of ["true", "1", "yes", "on", " TRUE "]) {
12
+ expect(streamingEnabled({ AGENTHUB_EMPLOYEE_STREAMING_ENABLED: v })).toBe(true);
13
+ }
14
+ });
15
+ });
16
+ describe("assertSubscriptionAuth (§D3 fail-closed)", () => {
17
+ it("throws when ANTHROPIC_API_KEY is set (refuse metered billing)", () => {
18
+ expect(() => assertSubscriptionAuth({ ANTHROPIC_API_KEY: "sk-x", CLAUDE_CODE_OAUTH_TOKEN: "t" })).toThrow(/ANTHROPIC_API_KEY/);
19
+ });
20
+ it("throws when no oauth token", () => {
21
+ expect(() => assertSubscriptionAuth({})).toThrow(/CLAUDE_CODE_OAUTH_TOKEN/);
22
+ });
23
+ it("returns the token on subscription-only env", () => {
24
+ expect(assertSubscriptionAuth({ CLAUDE_CODE_OAUTH_TOKEN: "tok" })).toEqual({ oauthToken: "tok" });
25
+ });
26
+ });
27
+ describe("decideInbound (§2.5 loop-safety)", () => {
28
+ const handled = (ids = []) => ({ has: (id) => ids.includes(id) });
29
+ const msg = (over) => ({ type: "message", message: { id: "m1", from_agent: "alice", type: "task", body: "do it", ...over } });
30
+ it("yields an actionable inbound from another agent", () => {
31
+ const d = decideInbound(msg({}), "me", handled());
32
+ expect(d.yield).toBe(true);
33
+ expect(d.message?.id).toBe("m1");
34
+ });
35
+ it("skips self messages", () => {
36
+ expect(decideInbound(msg({ from_agent: "me" }), "me", handled()).yield).toBe(false);
37
+ });
38
+ it("skips response/status (non-actionable)", () => {
39
+ expect(decideInbound(msg({ type: "response" }), "me", handled()).reason).toBe("non-actionable");
40
+ expect(decideInbound(msg({ type: "status" }), "me", handled()).reason).toBe("non-actionable");
41
+ });
42
+ it("skips duplicates", () => {
43
+ expect(decideInbound(msg({}), "me", handled(["m1"])).reason).toBe("duplicate");
44
+ });
45
+ it("skips non-message envelopes", () => {
46
+ expect(decideInbound({ type: "ping" }, "me", handled()).yield).toBe(false);
47
+ expect(decideInbound(null, "me", handled()).yield).toBe(false);
48
+ });
49
+ });
50
+ describe("buildTurnContent + toUserTurn", () => {
51
+ it("renders sender, body, and reply hint", () => {
52
+ const c = buildTurnContent({ id: "m1", from_agent: "alice", type: "task", subject: "Hi", body: "ping" });
53
+ expect(c).toContain("From: alice");
54
+ expect(c).toContain("ping");
55
+ expect(c).toContain('mcp__agenthub__reply(message_id="m1"');
56
+ });
57
+ it("wraps in the proven SDK user-turn shape", () => {
58
+ const t = toUserTurn("hello", "sess");
59
+ expect(t).toEqual({ type: "user", message: { role: "user", content: "hello" }, parent_tool_use_id: null, session_id: "sess" });
60
+ });
61
+ });
62
+ describe("shouldRecycle (§D4 context bounding)", () => {
63
+ const opts = { maxTurns: 10, tokenBudget: 100 };
64
+ it("recycles at the turn bound", () => {
65
+ expect(shouldRecycle({ turns: 10, estInputTokens: 0 }, opts)).toBe(true);
66
+ expect(shouldRecycle({ turns: 9, estInputTokens: 0 }, opts)).toBe(false);
67
+ });
68
+ it("recycles at the token bound", () => {
69
+ expect(shouldRecycle({ turns: 0, estInputTokens: 100 }, opts)).toBe(true);
70
+ });
71
+ it("default options read env with sane fallbacks", () => {
72
+ expect(defaultRecycleOptions({})).toEqual({ maxTurns: 25, tokenBudget: 200_000 });
73
+ expect(defaultRecycleOptions({ AGENTHUB_EMPLOYEE_STREAM_MAX_TURNS: "5" }).maxTurns).toBe(5);
74
+ });
75
+ });
76
+ describe("RuntimeStats (§4.1 per-employee observability)", () => {
77
+ it("newRuntimeStats starts every counter at zero", () => {
78
+ expect(newRuntimeStats()).toEqual({
79
+ turns: 0,
80
+ estInputTokens: 0,
81
+ estOutputTokens: 0,
82
+ sessionRecycles: 0,
83
+ reconnects: 0,
84
+ dedupHits: 0,
85
+ });
86
+ });
87
+ it("recordResultUsage folds fresh + cache-read + cache-creation input and output across turns", () => {
88
+ const s = newRuntimeStats();
89
+ recordResultUsage(s, {
90
+ input_tokens: 100,
91
+ cache_read_input_tokens: 50,
92
+ cache_creation_input_tokens: 10,
93
+ output_tokens: 20,
94
+ });
95
+ recordResultUsage(s, { input_tokens: 5, output_tokens: 3 });
96
+ expect(s.turns).toBe(2);
97
+ expect(s.estInputTokens).toBe(165); // 160 + 5
98
+ expect(s.estOutputTokens).toBe(23); // 20 + 3
99
+ });
100
+ it("recordResultUsage tolerates missing/undefined usage (still counts the turn)", () => {
101
+ const s = newRuntimeStats();
102
+ recordResultUsage(s, undefined);
103
+ recordResultUsage(s, {});
104
+ expect(s.turns).toBe(2);
105
+ expect(s.estInputTokens).toBe(0);
106
+ expect(s.estOutputTokens).toBe(0);
107
+ });
108
+ it("formatUsageLine renders one greppable line with every counter", () => {
109
+ const s = { turns: 4, estInputTokens: 1000, estOutputTokens: 200, sessionRecycles: 1, reconnects: 2, dedupHits: 3 };
110
+ const line = formatUsageLine("eng-ic-test", s, 65.9);
111
+ expect(line).toBe("[usage] employee=eng-ic-test uptime_s=65 turns=4 est_input_tokens=1000 est_output_tokens=200 " +
112
+ "session_recycles=1 reconnects=2 dedup_hits=3");
113
+ expect(line).not.toContain("\n");
114
+ });
115
+ it("formatUsageLine floors negative/fractional uptime to a non-negative integer", () => {
116
+ expect(formatUsageLine("x", newRuntimeStats(), -5)).toContain("uptime_s=0");
117
+ });
118
+ });
119
+ describe("inSessionMcpEnv (§3 connect-token — no browser-poll stall)", () => {
120
+ const base = { apiUrl: "https://h", apiKey: "ah_x", agentId: "eng-ic-test" };
121
+ it("always sets AGENTHUB_HEADLESS=true so agent_register can't enter the 10-min browser poll", () => {
122
+ expect(inSessionMcpEnv(base).AGENTHUB_HEADLESS).toBe("true");
123
+ });
124
+ it("carries the core connect-token env (url/key/agent) and disables the in-session WS", () => {
125
+ const env = inSessionMcpEnv(base);
126
+ expect(env.AGENTHUB_URL).toBe("https://h");
127
+ expect(env.AGENTHUB_API_KEY).toBe("ah_x");
128
+ expect(env.AGENTHUB_AGENT_ID).toBe("eng-ic-test");
129
+ expect(env.AGENTHUB_DISABLE_WEBSOCKET).toBe("true");
130
+ });
131
+ it("passes a user API key through only when present (coherent fallback register)", () => {
132
+ expect(inSessionMcpEnv(base).AGENTHUB_USER_API_KEY).toBeUndefined();
133
+ expect(inSessionMcpEnv({ ...base, userApiKey: "ah_user" }).AGENTHUB_USER_API_KEY).toBe("ah_user");
134
+ });
135
+ it("does not pass an empty user API key", () => {
136
+ expect(inSessionMcpEnv({ ...base, userApiKey: "" }).AGENTHUB_USER_API_KEY).toBeUndefined();
137
+ });
138
+ });
139
+ describe("assertWorkerIdentityReady (§3 fail-fast identity guard)", () => {
140
+ it("throws when the workspace connect-token state is missing", () => {
141
+ expect(() => assertWorkerIdentityReady(false, "eng-ic-test")).toThrow(/eng-ic-test/);
142
+ });
143
+ it("passes silently when the state file exists", () => {
144
+ expect(() => assertWorkerIdentityReady(true, "eng-ic-test")).not.toThrow();
145
+ });
146
+ });
147
+ //# sourceMappingURL=streamingRuntimeLib.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"streamingRuntimeLib.test.js","sourceRoot":"","sources":["../src/streamingRuntimeLib.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;AACrG,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,oCAAoC,CAAC;AA+B/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,cAAc,EAAE,MAAM,WAAW,CAAC;CACnC;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;AAwJD,wBAAgB,aAAa,IAAI,IAAI,EAAE,CAwwFtC;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,CA0lFlB"}
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;AACrG,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,oCAAoC,CAAC;AA4E/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,cAAc,EAAE,MAAM,WAAW,CAAC;CACnC;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;AAwJD,wBAAgB,aAAa,IAAI,IAAI,EAAE,CA20FtC;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,CAqoFlB"}
@@ -11,6 +11,41 @@ import { registerOpenSpecVizTools, handleOpenSpecVizToolCall } from "./openspec-
11
11
  import { installPackFromArchive } from "../skillPackInstaller.js";
12
12
  import { getServerTime, useTimeInjection } from "../lib/server-time.js";
13
13
  import { ENG_IC_CHARTER } from "../employee.js";
14
+ import { promises as fsp } from "node:fs";
15
+ import * as os from "node:os";
16
+ import * as path from "node:path";
17
+ // add-zoho-attachments — downloads land on local disk and the agent receives a
18
+ // PATH. Inline content stays opt-in and small; a 5 MB catalogue as text costs a
19
+ // context window and buys nothing a file reader can't do better.
20
+ const INLINE_ATTACHMENT_MAX_BYTES = 1024 * 1024;
21
+ /** Root for downloaded attachments. Overridable for tests and for operators. */
22
+ function attachmentDownloadRoot() {
23
+ return (process.env.AGENTHUB_ATTACHMENT_DIR ||
24
+ path.join(os.homedir(), ".agenthub", "zoho-attachments"));
25
+ }
26
+ /**
27
+ * Sanitize an untrusted path segment into a single safe directory/file name.
28
+ * Upstream supplies both the message id and the filename, so neither may be
29
+ * allowed to traverse out of the download root.
30
+ */
31
+ function safeSegment(raw, fallback) {
32
+ const base = path.basename((raw || "").replace(/\\/g, "/")).replace(/[\x00-\x1f]/g, "");
33
+ const cleaned = base.replace(/^\.+$/, "").trim();
34
+ return cleaned || fallback;
35
+ }
36
+ /**
37
+ * Write attachment bytes under <root>/<message-id>/<filename> and return the
38
+ * absolute path. Grouping by message keeps a thread's documents together and
39
+ * avoids collisions between identically named files on different messages.
40
+ */
41
+ async function writeAttachmentToDisk(messageId, filename, bytes) {
42
+ const root = attachmentDownloadRoot();
43
+ const dir = path.join(root, safeSegment(messageId, "message"));
44
+ await fsp.mkdir(dir, { recursive: true });
45
+ const filePath = path.join(dir, safeSegment(filename, "attachment"));
46
+ await fsp.writeFile(filePath, bytes);
47
+ return filePath;
48
+ }
14
49
  /**
15
50
  * Background sync: pushes local context to server after every save.
16
51
  * Non-blocking — logs errors but never throws.
@@ -130,12 +165,12 @@ function isUrgentMessage(msg) {
130
165
  * Build instruction text for urgent messages
131
166
  */
132
167
  function buildUrgentInstruction(msg) {
133
- return `URGENT: Agent [${msg.from_agent}] needs immediate assistance.
134
-
135
- Query: ${msg.subject}
136
-
137
- Details: ${msg.body}
138
-
168
+ return `URGENT: Agent [${msg.from_agent}] needs immediate assistance.
169
+
170
+ Query: ${msg.subject}
171
+
172
+ Details: ${msg.body}
173
+
139
174
  Spawn a subagent with Task tool to research and respond to this query. Use send_message to reply when done.`;
140
175
  }
141
176
  /**
@@ -143,10 +178,10 @@ Spawn a subagent with Task tool to research and respond to this query. Use send_
143
178
  */
144
179
  function buildTaskInstruction(task) {
145
180
  const assignerName = task.assigned_by_name || task.assigned_by;
146
- return `ACTION REQUIRED: You have a task assigned by ${assignerName}.
147
-
148
- Task: ${task.task}
149
-
181
+ return `ACTION REQUIRED: You have a task assigned by ${assignerName}.
182
+
183
+ Task: ${task.task}
184
+
150
185
  Use accept_task with task_id="${task.id}" to accept and start working, or decline_task to decline with a reason.`;
151
186
  }
152
187
  export function registerTools() {
@@ -2824,17 +2859,78 @@ export function registerTools() {
2824
2859
  },
2825
2860
  {
2826
2861
  name: "zoho_search",
2827
- description: "Search messages in one of your registered mailboxes by a Zoho search key. (requires agent_register first)",
2862
+ description: "Search messages in one of your registered mailboxes. `query` accepts a plain term " +
2863
+ "(normalized to Zoho's `entire:` form for you) OR Zoho's typed search syntax, which is " +
2864
+ "passed through unchanged. Typed forms: `entire:<term>` (anywhere in the mail), " +
2865
+ "`content:<term>` (body only), `sender:<addr-or-word>`, `to:<addr>`, `cc:<addr>`, " +
2866
+ "`subject:<term>`. Combine conditions with `::` (e.g. `entire:payroll::has:attachment`) " +
2867
+ "and alternate with `:or:` (e.g. `sender:a@x.com::or:to:a@x.com`). " +
2868
+ "Searches the last 30 days unless you pass `since`. (requires agent_register first)",
2828
2869
  inputSchema: {
2829
2870
  type: "object",
2830
2871
  properties: {
2831
2872
  mailbox: { type: "string", description: "Your mailbox address" },
2832
- query: { type: "string", description: "Zoho search key" },
2873
+ query: {
2874
+ type: "string",
2875
+ description: "A plain term (e.g. 'Baccara') or a typed Zoho search key (e.g. 'sender:eknath.a@baccara-geva.com')",
2876
+ },
2833
2877
  limit: { type: "number", description: "Max results (default 20)" },
2878
+ since: {
2879
+ type: "string",
2880
+ description: "Oldest message to search, as RFC3339 (e.g. '2026-01-01T00:00:00Z') or unix millis. " +
2881
+ "Defaults to 30 days ago — widen this to search further back.",
2882
+ },
2834
2883
  },
2835
2884
  required: ["mailbox", "query"],
2836
2885
  },
2837
2886
  },
2887
+ {
2888
+ name: "zoho_list_attachments",
2889
+ description: "List the attachments on a message in one of your registered mailboxes, without downloading " +
2890
+ "them. Returns attachment_id, filename, mime_type (inferred from the extension — Zoho sends " +
2891
+ "no content type) and size_bytes. Inline parts (signatures, logos) are excluded unless you " +
2892
+ "pass include_inline. Use this to see WHAT is attached, then zoho_download_attachment to " +
2893
+ "fetch one. (requires agent_register first)",
2894
+ inputSchema: {
2895
+ type: "object",
2896
+ properties: {
2897
+ mailbox: { type: "string", description: "Your mailbox address (must be registered by you)" },
2898
+ message_id: { type: "string", description: "Zoho message id" },
2899
+ folder: { type: "string", description: "Zoho folder id the message is in" },
2900
+ include_inline: {
2901
+ type: "boolean",
2902
+ description: "Also return inline content-id parts (signatures, logos). Default false.",
2903
+ },
2904
+ },
2905
+ required: ["mailbox", "message_id"],
2906
+ },
2907
+ },
2908
+ {
2909
+ name: "zoho_download_attachment",
2910
+ description: "Download one attachment to a local file and return its PATH — hand that path to a PDF/doc " +
2911
+ "reader rather than reading the bytes into context. Returns { file_path, filename, mime_type, " +
2912
+ "size_bytes }. Content is NOT returned inline unless you pass inline:true, which is capped at " +
2913
+ "1 MB and intended only for small text files. Attachments over the server's size cap fail with " +
2914
+ "an error naming the actual size and the cap — never a truncated file. " +
2915
+ "(requires agent_register first)",
2916
+ inputSchema: {
2917
+ type: "object",
2918
+ properties: {
2919
+ mailbox: { type: "string", description: "Your mailbox address (must be registered by you)" },
2920
+ message_id: { type: "string", description: "Zoho message id" },
2921
+ attachment_id: {
2922
+ type: "string",
2923
+ description: "Attachment id from zoho_list_attachments or zoho_read_message",
2924
+ },
2925
+ folder: { type: "string", description: "Zoho folder id the message is in" },
2926
+ inline: {
2927
+ type: "boolean",
2928
+ description: "Also return the content as text in the response (max 1 MB). Default false — prefer file_path.",
2929
+ },
2930
+ },
2931
+ required: ["mailbox", "message_id", "attachment_id"],
2932
+ },
2933
+ },
2838
2934
  {
2839
2935
  name: "zoho_read_message",
2840
2936
  description: "Read one message (headers + body) from one of your registered mailboxes. (requires agent_register first)",
@@ -5246,9 +5342,41 @@ export async function handleToolCall(name, args, client, context) {
5246
5342
  case "zoho_search": {
5247
5343
  if (!agentId)
5248
5344
  throw new Error("Not registered. Call agent_register first.");
5249
- const res = await client.zohoSearchMessages(args.mailbox, args.query, args.limit);
5345
+ const res = await client.zohoSearchMessages(args.mailbox, args.query, args.limit, args.since);
5250
5346
  return { content: [{ type: "text", text: JSON.stringify(res, null, 2) }] };
5251
5347
  }
5348
+ case "zoho_list_attachments": {
5349
+ if (!agentId)
5350
+ throw new Error("Not registered. Call agent_register first.");
5351
+ const res = await client.zohoListAttachments(args.mailbox, args.message_id, args.folder, args.include_inline === true);
5352
+ return { content: [{ type: "text", text: JSON.stringify(res, null, 2) }] };
5353
+ }
5354
+ case "zoho_download_attachment": {
5355
+ if (!agentId)
5356
+ throw new Error("Not registered. Call agent_register first.");
5357
+ const messageId = args.message_id;
5358
+ const { filename, mimeType, bytes } = await client.zohoDownloadAttachment(args.mailbox, messageId, args.attachment_id, args.folder);
5359
+ const filePath = await writeAttachmentToDisk(messageId, filename, bytes);
5360
+ const out = {
5361
+ file_path: filePath,
5362
+ filename: path.basename(filePath),
5363
+ mime_type: mimeType,
5364
+ size_bytes: bytes.length,
5365
+ };
5366
+ // Inline content is opt-in and capped: a multi-MB document as text blows
5367
+ // out the context window for no benefit when a path will do.
5368
+ if (args.inline === true) {
5369
+ if (bytes.length <= INLINE_ATTACHMENT_MAX_BYTES) {
5370
+ out.content = bytes.toString("utf8");
5371
+ }
5372
+ else {
5373
+ out.inline_omitted =
5374
+ `Attachment is ${bytes.length} bytes, over the ${INLINE_ATTACHMENT_MAX_BYTES} byte ` +
5375
+ `inline limit. It was written to file_path — read that instead.`;
5376
+ }
5377
+ }
5378
+ return { content: [{ type: "text", text: JSON.stringify(out, null, 2) }] };
5379
+ }
5252
5380
  case "zoho_read_message": {
5253
5381
  if (!agentId)
5254
5382
  throw new Error("Not registered. Call agent_register first.");