@desplega.ai/agent-swarm 1.111.0 → 1.112.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 (69) hide show
  1. package/dist/{actions-a4crgvtd.js → actions-eckb4g8r.js} +6 -6
  2. package/dist/{app-va5mr9yw.js → app-b05sgm02.js} +3 -3
  3. package/dist/{assistant-0jm4vy8r.js → assistant-7255sbyw.js} +9 -9
  4. package/dist/{boot-reembed-50zzk97q.js → boot-reembed-y1bt3ttn.js} +4 -4
  5. package/dist/{boot-reembed-0t815mmm.js → boot-reembed-yv7awg4y.js} +3 -3
  6. package/dist/{boot-scrub-logs-r99nbmxh.js → boot-scrub-logs-ddyaeqar.js} +2 -2
  7. package/dist/{cli-aqr96mny.js → cli-0c3n0eba.js} +1 -1
  8. package/dist/{cli-dza5039b.js → cli-15598a4k.js} +299 -38
  9. package/dist/{cli-jrmjxsnk.js → cli-30d2ct3k.js} +3 -3
  10. package/dist/{cli-4f6vw5vy.js → cli-3r5y6ayj.js} +2 -2
  11. package/dist/{cli-3cm8ar1c.js → cli-52tbq17n.js} +1 -1
  12. package/dist/{cli-gchg43wt.js → cli-56s4ehzw.js} +1 -1
  13. package/dist/{cli-0d5xddf1.js → cli-709t753c.js} +1 -1
  14. package/dist/{cli-xqzvygxp.js → cli-76d1vtvq.js} +11 -2
  15. package/dist/{cli-0d404x64.js → cli-7xkwgkrs.js} +1 -1
  16. package/dist/{cli-kavm2cv5.js → cli-8f0dwca2.js} +1 -1
  17. package/dist/{cli-r3y5chyg.js → cli-94vzr3nq.js} +4 -4
  18. package/dist/{cli-4ke1amr9.js → cli-951w7c79.js} +1 -1
  19. package/dist/{cli-2b0ew5v8.js → cli-9p0qhead.js} +1 -1
  20. package/dist/{cli-z626gjqj.js → cli-a5e8n5hg.js} +1 -1
  21. package/dist/{cli-y7v6e7yr.js → cli-h9jx5mag.js} +4 -4
  22. package/dist/{cli-f1f82qgv.js → cli-hm27prrc.js} +5 -5
  23. package/dist/{cli-t8m00q3b.js → cli-pt7c7qxz.js} +3 -3
  24. package/dist/{cli-ee3fkebs.js → cli-r1btc895.js} +2 -2
  25. package/dist/{cli-6tfm5fr1.js → cli-rp8pca98.js} +1 -1
  26. package/dist/{cli-h3vhr1gj.js → cli-s9pj3c7z.js} +2 -2
  27. package/dist/{cli-8j1h9627.js → cli-tch0ypp6.js} +61 -14
  28. package/dist/{cli-1aqc46he.js → cli-tg0f2wmz.js} +5 -5
  29. package/dist/{cli-fdnpymsv.js → cli-tp7e60s3.js} +4 -2
  30. package/dist/cli.js +8 -8
  31. package/dist/{commands-t3rdvb55.js → commands-14g7taf4.js} +2 -2
  32. package/dist/{db-mjqc8hj6.js → db-3tge4jrn.js} +2 -2
  33. package/dist/{handlers-mcmg5qgc.js → handlers-sy64egg6.js} +9 -9
  34. package/dist/{hook-0p4q1tym.js → hook-rnkzw8vp.js} +1 -1
  35. package/dist/{http-gs6fk9sg.js → http-xx9xptcs.js} +33 -31
  36. package/dist/{index-w15fk28m.js → index-3wr6v2mm.js} +7 -7
  37. package/dist/{index-744yvc8x.js → index-brma88d4.js} +9 -9
  38. package/dist/{index-6zpajd9e.js → index-ha8e9xn2.js} +6 -6
  39. package/dist/{index-20mf9wvc.js → index-zfx5p959.js} +8 -8
  40. package/dist/{keepalive-3gvnbxaj.js → keepalive-rbz05zad.js} +4 -4
  41. package/dist/{lead-yjbmhj3g.js → lead-s790v7r0.js} +17 -17
  42. package/dist/{maintenance-jbvy2bfg.js → maintenance-420rmadb.js} +4 -4
  43. package/dist/{onboard-sexatejh.js → onboard-jgqamqnw.js} +2 -2
  44. package/dist/{otel-impl-sgqvcw0f.js → otel-impl-ch3n3783.js} +1 -1
  45. package/dist/{pricing-refresh-rat240yg.js → pricing-refresh-rmtrby6h.js} +4 -4
  46. package/dist/{seed-pricing-7yfaf123.js → seed-pricing-1szjzfj1.js} +3 -3
  47. package/dist/{setup-vm5vcc6c.js → setup-8jfzkyzv.js} +2 -2
  48. package/dist/{worker-02y2r0r2.js → worker-40vq0pm8.js} +17 -17
  49. package/openapi.json +1 -1
  50. package/package.json +1 -1
  51. package/src/be/scripts/typecheck.ts +2 -0
  52. package/src/commands/runner.ts +154 -8
  53. package/src/github/handlers.ts +128 -25
  54. package/src/scripts-runtime/sdk-allowlist.ts +2 -0
  55. package/src/scripts-runtime/types/swarm-sdk.d.ts +2 -0
  56. package/src/server.ts +4 -0
  57. package/src/slack/message-text.ts +25 -0
  58. package/src/tests/github-event-filter.test.ts +3 -2
  59. package/src/tests/github-handlers-inline-comments.test.ts +114 -24
  60. package/src/tests/runner-repo-autostash.test.ts +168 -1
  61. package/src/tests/slack-delete.test.ts +157 -0
  62. package/src/tests/slack-message-text.test.ts +37 -1
  63. package/src/tests/slack-update.test.ts +167 -0
  64. package/src/tests/tool-annotations.test.ts +3 -0
  65. package/src/tools/slack-delete.ts +102 -0
  66. package/src/tools/slack-reply.ts +11 -3
  67. package/src/tools/slack-start-thread.ts +2 -0
  68. package/src/tools/slack-update.ts +127 -0
  69. package/src/tools/tool-config.ts +3 -1
@@ -0,0 +1,167 @@
1
+ import { afterAll, beforeAll, beforeEach, describe, expect, mock, test } from "bun:test";
2
+ import { unlink } from "node:fs/promises";
3
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
4
+ import { closeDb, createAgent, initDb } from "../be/db";
5
+ import { registerSlackUpdateTool } from "../tools/slack-update";
6
+
7
+ const TEST_DB_PATH = "./test-slack-update.sqlite";
8
+
9
+ const mockChatUpdate = mock(() => Promise.resolve({ ok: true, ts: "1783411554.596189" }));
10
+ const mockGetSlackApp = mock(() => ({ client: { chat: { update: mockChatUpdate } } }));
11
+
12
+ mock.module("../slack/app", () => ({
13
+ getSlackApp: mockGetSlackApp,
14
+ }));
15
+
16
+ type RegisteredTool = {
17
+ handler: (
18
+ args: unknown,
19
+ extra: unknown,
20
+ ) => Promise<{
21
+ structuredContent: { success: boolean; message: string; messageTs?: string };
22
+ }>;
23
+ };
24
+
25
+ function buildTool() {
26
+ const server = new McpServer({ name: "slack-update-test", version: "1.0.0" });
27
+ registerSlackUpdateTool(server);
28
+ const registered = (server as unknown as { _registeredTools: Record<string, RegisteredTool> })
29
+ ._registeredTools;
30
+ const tool = registered["slack-update"];
31
+ if (!tool) throw new Error("slack-update tool not registered");
32
+ return tool;
33
+ }
34
+
35
+ function meta(agentId?: string) {
36
+ return { sessionId: "s1", requestInfo: { headers: agentId ? { "x-agent-id": agentId } : {} } };
37
+ }
38
+
39
+ describe("slack-update", () => {
40
+ let leadAgentId: string;
41
+ let nonLeadAgentId: string;
42
+
43
+ beforeAll(async () => {
44
+ for (const suffix of ["", "-wal", "-shm"]) {
45
+ try {
46
+ await unlink(`${TEST_DB_PATH}${suffix}`);
47
+ } catch {}
48
+ }
49
+ initDb(TEST_DB_PATH);
50
+ leadAgentId = createAgent({ name: "Lead", isLead: true, status: "idle" }).id;
51
+ nonLeadAgentId = createAgent({ name: "Worker", isLead: false, status: "idle" }).id;
52
+ });
53
+
54
+ beforeEach(() => {
55
+ mockChatUpdate.mockClear();
56
+ mockGetSlackApp.mockClear();
57
+ });
58
+
59
+ afterAll(async () => {
60
+ closeDb();
61
+ for (const suffix of ["", "-wal", "-shm"]) {
62
+ try {
63
+ await unlink(`${TEST_DB_PATH}${suffix}`);
64
+ } catch {}
65
+ }
66
+ });
67
+
68
+ test("rejects when agentId header is missing, without touching Slack", async () => {
69
+ const tool = buildTool();
70
+ const result = await tool.handler(
71
+ { channelId: "C1", messageTs: "1783411554.596189", message: "corrected" },
72
+ meta(),
73
+ );
74
+
75
+ expect(result.structuredContent.success).toBe(false);
76
+ expect(result.structuredContent.message).toBe("Agent ID not found.");
77
+ expect(mockGetSlackApp).not.toHaveBeenCalled();
78
+ expect(mockChatUpdate).not.toHaveBeenCalled();
79
+ });
80
+
81
+ test("rejects when the agent does not exist, without touching Slack", async () => {
82
+ const tool = buildTool();
83
+ const result = await tool.handler(
84
+ { channelId: "C1", messageTs: "1783411554.596189", message: "corrected" },
85
+ meta("00000000-0000-0000-0000-000000000000"),
86
+ );
87
+
88
+ expect(result.structuredContent.success).toBe(false);
89
+ expect(result.structuredContent.message).toBe("Agent not found.");
90
+ expect(mockGetSlackApp).not.toHaveBeenCalled();
91
+ expect(mockChatUpdate).not.toHaveBeenCalled();
92
+ });
93
+
94
+ test("rejects non-lead agents without touching Slack", async () => {
95
+ const tool = buildTool();
96
+ const result = await tool.handler(
97
+ { channelId: "C1", messageTs: "1783411554.596189", message: "corrected" },
98
+ meta(nonLeadAgentId),
99
+ );
100
+
101
+ expect(result.structuredContent.success).toBe(false);
102
+ expect(result.structuredContent.message).toBe(
103
+ "Editing Slack messages requires lead privileges.",
104
+ );
105
+ expect(mockGetSlackApp).not.toHaveBeenCalled();
106
+ expect(mockChatUpdate).not.toHaveBeenCalled();
107
+ });
108
+
109
+ test("lead caller updates using the normalized timestamp", async () => {
110
+ const tool = buildTool();
111
+ // Full permalink form (with a thread_ts query param) must normalize to the dotted API form.
112
+ const result = await tool.handler(
113
+ {
114
+ channelId: "C1",
115
+ messageTs: "https://x.slack.com/archives/C1/p1783411554596189?thread_ts=123.456",
116
+ message: "corrected message",
117
+ },
118
+ meta(leadAgentId),
119
+ );
120
+
121
+ expect(result.structuredContent.success).toBe(true);
122
+ expect(result.structuredContent.messageTs).toBe("1783411554.596189");
123
+ expect(mockChatUpdate).toHaveBeenCalledTimes(1);
124
+ expect(mockChatUpdate.mock.calls[0][0]).toMatchObject({
125
+ channel: "C1",
126
+ ts: "1783411554.596189",
127
+ text: "corrected message",
128
+ });
129
+ });
130
+
131
+ test.each([
132
+ ["message_not_found", "No message found at that timestamp in this channel."],
133
+ [
134
+ "cant_update_message",
135
+ "Cannot edit this message — the bot can only edit messages it authored.",
136
+ ],
137
+ ["edit_window_closed", "The edit window for this message has closed."],
138
+ ["channel_not_found", "Channel not found or the bot has no access."],
139
+ ["not_in_channel", "The bot is not in that channel."],
140
+ ])("maps Slack error %s to a structured failure message", async (errorCode, expectedMessage) => {
141
+ mockChatUpdate.mockImplementationOnce(() =>
142
+ Promise.reject(Object.assign(new Error("slack error"), { data: { error: errorCode } })),
143
+ );
144
+
145
+ const tool = buildTool();
146
+ const result = await tool.handler(
147
+ { channelId: "C1", messageTs: "1783411554.596189", message: "corrected" },
148
+ meta(leadAgentId),
149
+ );
150
+
151
+ expect(result.structuredContent.success).toBe(false);
152
+ expect(result.structuredContent.message).toBe(expectedMessage);
153
+ });
154
+
155
+ test("falls back to the raw error message for unmapped Slack error codes", async () => {
156
+ mockChatUpdate.mockImplementationOnce(() => Promise.reject(new Error("boom")));
157
+
158
+ const tool = buildTool();
159
+ const result = await tool.handler(
160
+ { channelId: "C1", messageTs: "1783411554.596189", message: "corrected" },
161
+ meta(leadAgentId),
162
+ );
163
+
164
+ expect(result.structuredContent.success).toBe(false);
165
+ expect(result.structuredContent.message).toBe("Failed to update message: boom");
166
+ });
167
+ });
@@ -107,6 +107,7 @@ describe("Tool Annotations & Classification", () => {
107
107
  "delete-config",
108
108
  "delete-workflow",
109
109
  "unregister-service",
110
+ "slack-delete",
110
111
  ];
111
112
 
112
113
  for (const name of expectedDestructive) {
@@ -163,6 +164,8 @@ describe("Tool Annotations & Classification", () => {
163
164
  "slack-upload-file",
164
165
  "slack-download-file",
165
166
  "slack-list-channels",
167
+ "slack-delete",
168
+ "slack-update",
166
169
  ];
167
170
 
168
171
  for (const name of slackTools) {
@@ -0,0 +1,102 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import * as z from "zod";
3
+ import { getAgentById } from "@/be/db";
4
+ import { getSlackApp } from "@/slack/app";
5
+ import { parseSlackTs } from "@/slack/message-text";
6
+ import { createToolRegistrar } from "@/tools/utils";
7
+
8
+ export const registerSlackDeleteTool = (server: McpServer) => {
9
+ createToolRegistrar(server)(
10
+ "slack-delete",
11
+ {
12
+ title: "Delete a Slack message",
13
+ description:
14
+ "Deletes a Slack message that THIS bot authored (e.g. a message previously posted via `slack-post`/`slack-reply`). Cannot delete messages authored by humans or other apps. Requires lead privileges.",
15
+ annotations: { openWorldHint: true, destructiveHint: true },
16
+
17
+ inputSchema: z.object({
18
+ channelId: z.string().min(1).describe("The Slack channel ID the message is in."),
19
+ messageTs: z
20
+ .string()
21
+ .min(1)
22
+ .describe(
23
+ "Timestamp of the message to delete. Accepts the dotted form (1783411554.596189), the 'p' deep-link form (p1783411554596189), or a full Slack permalink URL.",
24
+ ),
25
+ }),
26
+ outputSchema: z.object({
27
+ success: z.boolean(),
28
+ message: z.string(),
29
+ }),
30
+ },
31
+ async ({ channelId, messageTs }, requestInfo, _meta) => {
32
+ if (!requestInfo.agentId) {
33
+ return {
34
+ content: [{ type: "text", text: "Agent ID not found." }],
35
+ structuredContent: { success: false, message: "Agent ID not found." },
36
+ };
37
+ }
38
+
39
+ const agent = getAgentById(requestInfo.agentId);
40
+ if (!agent) {
41
+ return {
42
+ content: [{ type: "text", text: "Agent not found." }],
43
+ structuredContent: { success: false, message: "Agent not found." },
44
+ };
45
+ }
46
+
47
+ if (!agent.isLead) {
48
+ return {
49
+ content: [{ type: "text", text: "Deleting Slack messages requires lead privileges." }],
50
+ structuredContent: {
51
+ success: false,
52
+ message: "Deleting Slack messages requires lead privileges.",
53
+ },
54
+ };
55
+ }
56
+
57
+ const app = getSlackApp();
58
+ if (!app) {
59
+ return {
60
+ content: [{ type: "text", text: "Slack not configured." }],
61
+ structuredContent: { success: false, message: "Slack not configured." },
62
+ };
63
+ }
64
+
65
+ try {
66
+ const ts = parseSlackTs(messageTs);
67
+ await app.client.chat.delete({ channel: channelId, ts });
68
+
69
+ return {
70
+ content: [{ type: "text", text: "Message deleted successfully." }],
71
+ structuredContent: { success: true, message: "Message deleted successfully." },
72
+ };
73
+ } catch (error) {
74
+ const errorCode = (error as { data?: { error?: string } } | undefined)?.data?.error;
75
+ const errorMsg = error instanceof Error ? error.message : String(error);
76
+
77
+ let message: string;
78
+ switch (errorCode) {
79
+ case "message_not_found":
80
+ message = "No message found at that timestamp in this channel.";
81
+ break;
82
+ case "cant_delete_message":
83
+ message = "Cannot delete this message — the bot can only delete messages it authored.";
84
+ break;
85
+ case "channel_not_found":
86
+ message = "Channel not found or the bot has no access.";
87
+ break;
88
+ case "not_in_channel":
89
+ message = "The bot is not in that channel.";
90
+ break;
91
+ default:
92
+ message = `Failed to delete message: ${errorMsg}`;
93
+ }
94
+
95
+ return {
96
+ content: [{ type: "text", text: message }],
97
+ structuredContent: { success: false, message },
98
+ };
99
+ }
100
+ },
101
+ );
102
+ };
@@ -35,6 +35,7 @@ export const registerSlackReplyTool = (server: McpServer) => {
35
35
  outputSchema: z.object({
36
36
  success: z.boolean(),
37
37
  message: z.string(),
38
+ messageTs: z.string().optional(),
38
39
  }),
39
40
  },
40
41
  async ({ inboxMessageId, taskId, message }, requestInfo, _meta) => {
@@ -119,7 +120,7 @@ export const registerSlackReplyTool = (server: McpServer) => {
119
120
  try {
120
121
  const slackMessage = markdownToSlack(message);
121
122
 
122
- await withAutoJoin(app.client, slackChannelId, () =>
123
+ const result = await withAutoJoin(app.client, slackChannelId, () =>
123
124
  app.client.chat.postMessage({
124
125
  channel: slackChannelId,
125
126
  thread_ts: slackThreadTs,
@@ -138,6 +139,8 @@ export const registerSlackReplyTool = (server: McpServer) => {
138
139
  }),
139
140
  );
140
141
 
142
+ const messageTs = result.ts;
143
+
141
144
  // After successful postMessage, mark task as having a Slack reply
142
145
  if (taskId) {
143
146
  markTaskSlackReplySent(taskId);
@@ -145,8 +148,13 @@ export const registerSlackReplyTool = (server: McpServer) => {
145
148
  }
146
149
 
147
150
  return {
148
- content: [{ type: "text", text: "Reply sent successfully." }],
149
- structuredContent: { success: true, message: "Reply sent successfully." },
151
+ content: [
152
+ {
153
+ type: "text",
154
+ text: `Reply sent successfully.${messageTs ? ` Message timestamp: ${messageTs}` : ""}`,
155
+ },
156
+ ],
157
+ structuredContent: { success: true, message: "Reply sent successfully.", messageTs },
150
158
  };
151
159
  } catch (error) {
152
160
  return {
@@ -24,6 +24,7 @@ export const registerSlackStartThreadTool = (server: McpServer) => {
24
24
  message: z.string(),
25
25
  channelId: z.string().optional(),
26
26
  ts: z.string().optional(),
27
+ messageTs: z.string().optional(),
27
28
  }),
28
29
  },
29
30
  async ({ channelId, message }, requestInfo, _meta) => {
@@ -112,6 +113,7 @@ export const registerSlackStartThreadTool = (server: McpServer) => {
112
113
  message: "Thread started successfully.",
113
114
  channelId: resolvedChannelId,
114
115
  ts,
116
+ messageTs: ts,
115
117
  },
116
118
  };
117
119
  } catch (error) {
@@ -0,0 +1,127 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import * as z from "zod";
3
+ import { getAgentById } from "@/be/db";
4
+ import { getSlackApp } from "@/slack/app";
5
+ import { parseSlackTs } from "@/slack/message-text";
6
+ import { markdownToSlack } from "@/slack/responses";
7
+ import { createToolRegistrar } from "@/tools/utils";
8
+
9
+ export const registerSlackUpdateTool = (server: McpServer) => {
10
+ createToolRegistrar(server)(
11
+ "slack-update",
12
+ {
13
+ title: "Edit a Slack message",
14
+ description:
15
+ "Edits (in place) the text of a Slack message that THIS bot authored — use it to post corrections to your own messages. Cannot edit messages authored by humans or other apps. Note: editing may reset the message's display name/icon to the app default (Slack's chat.update cannot set the crown persona). Requires lead privileges.",
16
+ annotations: { openWorldHint: true },
17
+
18
+ inputSchema: z.object({
19
+ channelId: z.string().min(1).describe("The Slack channel ID the message is in."),
20
+ messageTs: z
21
+ .string()
22
+ .min(1)
23
+ .describe(
24
+ "Timestamp of the message to edit (dotted, 'p' deep-link, or full permalink URL).",
25
+ ),
26
+ message: z.string().min(1).max(4000).describe("The new message content."),
27
+ }),
28
+ outputSchema: z.object({
29
+ success: z.boolean(),
30
+ message: z.string(),
31
+ messageTs: z.string().optional(),
32
+ }),
33
+ },
34
+ async ({ channelId, messageTs, message }, requestInfo, _meta) => {
35
+ if (!requestInfo.agentId) {
36
+ return {
37
+ content: [{ type: "text", text: "Agent ID not found." }],
38
+ structuredContent: { success: false, message: "Agent ID not found." },
39
+ };
40
+ }
41
+
42
+ const agent = getAgentById(requestInfo.agentId);
43
+ if (!agent) {
44
+ return {
45
+ content: [{ type: "text", text: "Agent not found." }],
46
+ structuredContent: { success: false, message: "Agent not found." },
47
+ };
48
+ }
49
+
50
+ if (!agent.isLead) {
51
+ return {
52
+ content: [{ type: "text", text: "Editing Slack messages requires lead privileges." }],
53
+ structuredContent: {
54
+ success: false,
55
+ message: "Editing Slack messages requires lead privileges.",
56
+ },
57
+ };
58
+ }
59
+
60
+ const app = getSlackApp();
61
+ if (!app) {
62
+ return {
63
+ content: [{ type: "text", text: "Slack not configured." }],
64
+ structuredContent: { success: false, message: "Slack not configured." },
65
+ };
66
+ }
67
+
68
+ try {
69
+ const ts = parseSlackTs(messageTs);
70
+ const slackMessage = markdownToSlack(message);
71
+
72
+ const result = await app.client.chat.update({
73
+ channel: channelId,
74
+ ts,
75
+ text: slackMessage,
76
+ blocks: [
77
+ {
78
+ type: "section",
79
+ text: {
80
+ type: "mrkdwn",
81
+ text: slackMessage,
82
+ },
83
+ },
84
+ ],
85
+ });
86
+
87
+ return {
88
+ content: [{ type: "text", text: "Message updated successfully." }],
89
+ structuredContent: {
90
+ success: true,
91
+ message: "Message updated successfully.",
92
+ messageTs: result.ts,
93
+ },
94
+ };
95
+ } catch (error) {
96
+ const errorCode = (error as { data?: { error?: string } } | undefined)?.data?.error;
97
+ const errorMsg = error instanceof Error ? error.message : String(error);
98
+
99
+ let message: string;
100
+ switch (errorCode) {
101
+ case "message_not_found":
102
+ message = "No message found at that timestamp in this channel.";
103
+ break;
104
+ case "cant_update_message":
105
+ message = "Cannot edit this message — the bot can only edit messages it authored.";
106
+ break;
107
+ case "edit_window_closed":
108
+ message = "The edit window for this message has closed.";
109
+ break;
110
+ case "channel_not_found":
111
+ message = "Channel not found or the bot has no access.";
112
+ break;
113
+ case "not_in_channel":
114
+ message = "The bot is not in that channel.";
115
+ break;
116
+ default:
117
+ message = `Failed to update message: ${errorMsg}`;
118
+ }
119
+
120
+ return {
121
+ content: [{ type: "text", text: message }],
122
+ structuredContent: { success: false, message },
123
+ };
124
+ }
125
+ },
126
+ );
127
+ };
@@ -86,7 +86,7 @@ export const DEFERRED_TOOLS = new Set([
86
86
  "context-history",
87
87
  "context-diff",
88
88
 
89
- // Slack (7)
89
+ // Slack (9)
90
90
  "slack-reply",
91
91
  "slack-read",
92
92
  "slack-upload-file",
@@ -94,6 +94,8 @@ export const DEFERRED_TOOLS = new Set([
94
94
  "slack-list-channels",
95
95
  "slack-post",
96
96
  "slack-start-thread",
97
+ "slack-delete",
98
+ "slack-update",
97
99
 
98
100
  // Channel management (2)
99
101
  "create-channel",