@desplega.ai/agent-swarm 1.152.1 → 1.152.2

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 (66) hide show
  1. package/README.md +1 -0
  2. package/dist/{acp-adapter-4s7q25ft.js → acp-adapter-pfeje2v5.js} +1 -1
  3. package/dist/{actions-cs6rhmvc.js → actions-0sttc7f5.js} +12 -12
  4. package/dist/{app-j54aq0dj.js → app-p8teva95.js} +3 -3
  5. package/dist/{assistant-w1pgn7vf.js → assistant-r45jnsrt.js} +15 -15
  6. package/dist/{boot-reembed-42vgkrrj.js → boot-reembed-1qmybsd4.js} +4 -4
  7. package/dist/{boot-reembed-smy30z32.js → boot-reembed-je26h5y7.js} +3 -3
  8. package/dist/{boot-scrub-logs-b6s72fsw.js → boot-scrub-logs-gte3h1mm.js} +2 -2
  9. package/dist/{claude-adapter-fh7rmcj9.js → claude-adapter-csc1mzka.js} +3 -3
  10. package/dist/{claude-sdk-session-xhynatn9.js → claude-sdk-session-fcd6f078.js} +3 -3
  11. package/dist/{cli-7bwaerjt.js → cli-2nnd56zt.js} +1 -1
  12. package/dist/{cli-b4dyetqt.js → cli-3bpzj8me.js} +2 -2
  13. package/dist/{cli-sqmh7jj1.js → cli-4ahpr4sw.js} +1 -1
  14. package/dist/{cli-vbk025fm.js → cli-56h18ng2.js} +1 -1
  15. package/dist/{cli-kf8qp2vf.js → cli-b9dd0h3k.js} +2 -2
  16. package/dist/{cli-ng4q6a9t.js → cli-ckz5t2hb.js} +7 -7
  17. package/dist/{cli-dsyg66vt.js → cli-fmy65jdn.js} +3 -3
  18. package/dist/{cli-y8tayczk.js → cli-gphgnvpf.js} +2 -2
  19. package/dist/{cli-gxf2ebyz.js → cli-gpqcs935.js} +2 -2
  20. package/dist/{cli-3g61md6v.js → cli-jbmc0r7r.js} +1 -1
  21. package/dist/{cli-jtvs5m8q.js → cli-jew3jda5.js} +1 -1
  22. package/dist/{cli-3pft2z6r.js → cli-jvy9qgjs.js} +12 -10
  23. package/dist/{cli-ftdjz1mk.js → cli-qd65dvr6.js} +8 -8
  24. package/dist/{cli-wfve107r.js → cli-qn042eh7.js} +8 -8
  25. package/dist/{cli-jytf5mw9.js → cli-r84ctdxn.js} +1 -1
  26. package/dist/{cli-ks8a1cgq.js → cli-wpv5rgg2.js} +2 -2
  27. package/dist/{cli-sakxd2rg.js → cli-xw7vak96.js} +2 -2
  28. package/dist/{cli-v9sk4dzw.js → cli-xy1cchaf.js} +1 -1
  29. package/dist/{cli-72tgtzr1.js → cli-z3wkng35.js} +18 -18
  30. package/dist/{cli-bdt7w021.js → cli-zvgj5zg8.js} +1 -1
  31. package/dist/{cli-gfez0qmk.js → cli-zvkq5csn.js} +1 -1
  32. package/dist/cli.js +11 -11
  33. package/dist/{codex-adapter-k6yq95sn.js → codex-adapter-fxmpsabw.js} +2 -2
  34. package/dist/{codex-session-runner-3rp84pk8.js → codex-session-runner-rr1e47ea.js} +2 -2
  35. package/dist/{commands-pbr5jzy5.js → commands-0f5rxz51.js} +2 -2
  36. package/dist/{db-z0xaa59a.js → db-r3jxf3wy.js} +2 -2
  37. package/dist/{dispatcher-pwpgsj0x.js → dispatcher-06vph6q4.js} +11 -11
  38. package/dist/{handlers-5mzk4mbg.js → handlers-0hgtc910.js} +14 -14
  39. package/dist/{hook-8bz33zee.js → hook-9kvrv1er.js} +1 -1
  40. package/dist/{hook-kbh84zb1.js → hook-fbfhkat3.js} +2 -2
  41. package/dist/{http-pppxg5vr.js → http-xbyx8k5j.js} +27 -27
  42. package/dist/{index-y0te09pn.js → index-gpfn1741.js} +3 -3
  43. package/dist/{index-z0zwjtkn.js → index-jsw3aynb.js} +11 -11
  44. package/dist/{index-68bd9b5f.js → index-rnyahfn5.js} +11 -11
  45. package/dist/{index-mjpxsr6k.js → index-sgk50mbg.js} +12 -12
  46. package/dist/{index-k1hq1qt6.js → index-tfsdxyv6.js} +7 -7
  47. package/dist/{keepalive-xvmwnh1d.js → keepalive-s3xpr7sw.js} +4 -4
  48. package/dist/{lead-pf8nvxt8.js → lead-rq4n5r16.js} +15 -15
  49. package/dist/{maintenance-jk4q0wgg.js → maintenance-f0atj1ab.js} +4 -4
  50. package/dist/{oauth-refresh-sweep-6207x9sj.js → oauth-refresh-sweep-q0t3bxre.js} +3 -3
  51. package/dist/{onboard-b0csgm0q.js → onboard-br5j1kch.js} +2 -2
  52. package/dist/{otel-impl-b6rfehth.js → otel-impl-vdqge2nt.js} +1 -1
  53. package/dist/{pricing-refresh-4e201wtd.js → pricing-refresh-hb3691fd.js} +4 -4
  54. package/dist/{rbac-roles-mkc5n5qa.js → rbac-roles-jh280k9g.js} +3 -3
  55. package/dist/{rbac-roles-zp6e4geh.js → rbac-roles-t78xt39b.js} +2 -2
  56. package/dist/{render-v2-4fazz9tf.js → render-v2-7p8r2ymg.js} +3 -3
  57. package/dist/{seed-pricing-thxpn9vn.js → seed-pricing-hf889enh.js} +3 -3
  58. package/dist/{setup-wb0z8cat.js → setup-w6mb03jv.js} +2 -2
  59. package/dist/{work-objects-h3498kwe.js → work-objects-nt8gy1kj.js} +14 -14
  60. package/dist/{worker-2m2tzxp3.js → worker-dwy617t0.js} +15 -15
  61. package/openapi.json +1 -1
  62. package/package.json +1 -1
  63. package/src/slack/assistant.ts +10 -1
  64. package/src/slack/handlers.ts +10 -1
  65. package/src/slack/thread-buffer.ts +14 -3
  66. package/src/tests/slack-assistant.test.ts +134 -0
@@ -9,11 +9,11 @@ import {
9
9
  processSlackRenderV2,
10
10
  renderThreadTree,
11
11
  streamOutcomeCard
12
- } from "./cli-wfve107r.js";
12
+ } from "./cli-qn042eh7.js";
13
13
  import"./cli-q51yprfq.js";
14
14
  import"./cli-yatxqynm.js";
15
15
  import"./cli-2658f6at.js";
16
- import"./cli-bdt7w021.js";
16
+ import"./cli-zvgj5zg8.js";
17
17
  import"./cli-ht5rs63n.js";
18
18
  import"./cli-z61ykvam.js";
19
19
  import"./cli-dv6cdjs2.js";
@@ -21,7 +21,7 @@ import"./cli-c2mk1kgf.js";
21
21
  import"./cli-ej6gbxfb.js";
22
22
  import"./cli-sd0mxa3c.js";
23
23
  import"./cli-w5e1d1vv.js";
24
- import"./cli-gfez0qmk.js";
24
+ import"./cli-zvkq5csn.js";
25
25
  import"./cli-r2ap2czm.js";
26
26
  import"./cli-pzxba7gw.js";
27
27
  import"./cli-zf8jjwex.js";
@@ -2,8 +2,8 @@ import {
2
2
  buildModelsDevSeedRows,
3
3
  buildPricingSeedRows,
4
4
  seedPricingFromModelsDev
5
- } from "./cli-7bwaerjt.js";
6
- import"./cli-bdt7w021.js";
5
+ } from "./cli-2nnd56zt.js";
6
+ import"./cli-zvgj5zg8.js";
7
7
  import"./cli-ht5rs63n.js";
8
8
  import"./cli-z61ykvam.js";
9
9
  import"./cli-dv6cdjs2.js";
@@ -11,7 +11,7 @@ import"./cli-c2mk1kgf.js";
11
11
  import"./cli-ej6gbxfb.js";
12
12
  import"./cli-sd0mxa3c.js";
13
13
  import"./cli-w5e1d1vv.js";
14
- import"./cli-gfez0qmk.js";
14
+ import"./cli-zvkq5csn.js";
15
15
  import"./cli-r2ap2czm.js";
16
16
  import"./cli-zf8jjwex.js";
17
17
  import"./cli-bc05qbss.js";
@@ -4,14 +4,14 @@ import {
4
4
  createDefaultMcpJson,
5
5
  createDefaultSettingsLocal,
6
6
  createHooksConfig
7
- } from "./cli-3g61md6v.js";
7
+ } from "./cli-jbmc0r7r.js";
8
8
  import {
9
9
  require_jsx_dev_runtime
10
10
  } from "./cli-s2qjdd6z.js";
11
11
  import {
12
12
  getApiKey
13
13
  } from "./cli-f14fvzag.js";
14
- import"./cli-gfez0qmk.js";
14
+ import"./cli-zvkq5csn.js";
15
15
  import {
16
16
  Box_default,
17
17
  Spinner,
@@ -1,34 +1,34 @@
1
1
  import {
2
2
  isUserAllowed
3
- } from "./cli-ng4q6a9t.js";
4
- import"./cli-3pft2z6r.js";
5
- import"./cli-b4dyetqt.js";
6
- import"./cli-72tgtzr1.js";
3
+ } from "./cli-ckz5t2hb.js";
4
+ import"./cli-jvy9qgjs.js";
5
+ import"./cli-3bpzj8me.js";
6
+ import"./cli-z3wkng35.js";
7
7
  import"./cli-0z2v4nhw.js";
8
8
  import"./cli-4wppfkcm.js";
9
9
  import"./cli-e7d20cty.js";
10
10
  import"./cli-re8z5p8p.js";
11
11
  import"./cli-3dpj7atn.js";
12
- import"./cli-kf8qp2vf.js";
13
- import"./cli-jytf5mw9.js";
12
+ import"./cli-b9dd0h3k.js";
13
+ import"./cli-r84ctdxn.js";
14
14
  import"./cli-zfzznn7c.js";
15
- import"./cli-dsyg66vt.js";
15
+ import"./cli-fmy65jdn.js";
16
16
  import"./cli-epan13p1.js";
17
17
  import"./cli-y89t4nca.js";
18
18
  import"./cli-f14fvzag.js";
19
19
  import"./cli-fad8m16k.js";
20
- import"./cli-vbk025fm.js";
21
- import"./cli-7bwaerjt.js";
20
+ import"./cli-56h18ng2.js";
21
+ import"./cli-2nnd56zt.js";
22
22
  import"./cli-bsakbbkm.js";
23
- import"./cli-gxf2ebyz.js";
23
+ import"./cli-gpqcs935.js";
24
24
  import"./cli-51tf7ac3.js";
25
25
  import {
26
26
  getTaskUrl,
27
27
  slackTaskOutput
28
- } from "./cli-wfve107r.js";
28
+ } from "./cli-qn042eh7.js";
29
29
  import"./cli-q51yprfq.js";
30
30
  import"./cli-yatxqynm.js";
31
- import"./cli-jtvs5m8q.js";
31
+ import"./cli-jew3jda5.js";
32
32
  import"./cli-fm4ytsdk.js";
33
33
  import"./cli-aehm1rbq.js";
34
34
  import"./cli-2658f6at.js";
@@ -37,7 +37,7 @@ import {
37
37
  getAgentById,
38
38
  getTaskById,
39
39
  init_db
40
- } from "./cli-bdt7w021.js";
40
+ } from "./cli-zvgj5zg8.js";
41
41
  import"./cli-ht5rs63n.js";
42
42
  import"./cli-z61ykvam.js";
43
43
  import"./cli-dv6cdjs2.js";
@@ -48,7 +48,7 @@ import {
48
48
  } from "./cli-ej6gbxfb.js";
49
49
  import"./cli-sd0mxa3c.js";
50
50
  import"./cli-w5e1d1vv.js";
51
- import"./cli-gfez0qmk.js";
51
+ import"./cli-zvkq5csn.js";
52
52
  import"./cli-r2ap2czm.js";
53
53
  import"./cli-pzxba7gw.js";
54
54
  import"./cli-f7kvq46p.js";
@@ -1,16 +1,16 @@
1
1
  import {
2
2
  runAgent
3
- } from "./cli-ftdjz1mk.js";
3
+ } from "./cli-qd65dvr6.js";
4
4
  import"./cli-pgj4a5fb.js";
5
5
  import"./cli-kj76p1ez.js";
6
6
  import"./cli-9b13ggd1.js";
7
7
  import"./cli-d3p82erb.js";
8
- import"./cli-sakxd2rg.js";
9
- import"./cli-sqmh7jj1.js";
8
+ import"./cli-xw7vak96.js";
9
+ import"./cli-4ahpr4sw.js";
10
10
  import"./cli-wbyk465z.js";
11
11
  import"./cli-tng3131n.js";
12
12
  import"./cli-5bfdm1ah.js";
13
- import"./cli-v9sk4dzw.js";
13
+ import"./cli-xy1cchaf.js";
14
14
  import"./cli-hsxj0yar.js";
15
15
  import"./cli-r94z9p1z.js";
16
16
  import"./cli-b5b30yzk.js";
@@ -25,34 +25,34 @@ import"./cli-eree7rnv.js";
25
25
  import"./cli-4yc5d8fk.js";
26
26
  import {
27
27
  getEnabledCapabilities
28
- } from "./cli-72tgtzr1.js";
28
+ } from "./cli-z3wkng35.js";
29
29
  import"./cli-0z2v4nhw.js";
30
30
  import"./cli-4wppfkcm.js";
31
31
  import"./cli-e7d20cty.js";
32
32
  import"./cli-re8z5p8p.js";
33
33
  import"./cli-3dpj7atn.js";
34
- import"./cli-kf8qp2vf.js";
35
- import"./cli-jytf5mw9.js";
34
+ import"./cli-b9dd0h3k.js";
35
+ import"./cli-r84ctdxn.js";
36
36
  import"./cli-zfzznn7c.js";
37
- import"./cli-dsyg66vt.js";
37
+ import"./cli-fmy65jdn.js";
38
38
  import"./cli-epan13p1.js";
39
39
  import"./cli-y89t4nca.js";
40
40
  import"./cli-f14fvzag.js";
41
41
  import"./cli-fad8m16k.js";
42
- import"./cli-vbk025fm.js";
43
- import"./cli-7bwaerjt.js";
42
+ import"./cli-56h18ng2.js";
43
+ import"./cli-2nnd56zt.js";
44
44
  import"./cli-bsakbbkm.js";
45
- import"./cli-gxf2ebyz.js";
45
+ import"./cli-gpqcs935.js";
46
46
  import"./cli-51tf7ac3.js";
47
- import"./cli-wfve107r.js";
47
+ import"./cli-qn042eh7.js";
48
48
  import"./cli-q51yprfq.js";
49
49
  import"./cli-yatxqynm.js";
50
- import"./cli-jtvs5m8q.js";
50
+ import"./cli-jew3jda5.js";
51
51
  import"./cli-fm4ytsdk.js";
52
52
  import"./cli-aehm1rbq.js";
53
53
  import"./cli-2658f6at.js";
54
54
  import"./cli-jsp14b6c.js";
55
- import"./cli-bdt7w021.js";
55
+ import"./cli-zvgj5zg8.js";
56
56
  import"./cli-ht5rs63n.js";
57
57
  import"./cli-z61ykvam.js";
58
58
  import"./cli-dv6cdjs2.js";
@@ -60,7 +60,7 @@ import"./cli-c2mk1kgf.js";
60
60
  import"./cli-ej6gbxfb.js";
61
61
  import"./cli-sd0mxa3c.js";
62
62
  import"./cli-w5e1d1vv.js";
63
- import"./cli-gfez0qmk.js";
63
+ import"./cli-zvkq5csn.js";
64
64
  import"./cli-r2ap2czm.js";
65
65
  import"./cli-pzxba7gw.js";
66
66
  import"./cli-f7kvq46p.js";
package/openapi.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "openapi": "3.1.0",
3
3
  "info": {
4
4
  "title": "Agent Swarm API",
5
- "version": "1.152.1",
5
+ "version": "1.152.2",
6
6
  "description": "Multi-agent orchestration API for Claude Code, Codex, and Gemini CLI. Enables task distribution, agent communication, and service discovery.\n\nMCP tools are documented separately in [MCP.md](./MCP.md)."
7
7
  },
8
8
  "servers": [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@desplega.ai/agent-swarm",
3
- "version": "1.152.1",
3
+ "version": "1.152.2",
4
4
  "description": "agent-swarm.dev — an open-source operating system for AI work: a lead agent breaks goals into tasks, routes them to specialized workers such as Claude Code or Codex, runs each worker in an isolated container, and preserves shared memory, tools, schedules, and review gates so delegated work compounds across sessions.",
5
5
  "keywords": [
6
6
  "agent-swarm.dev",
@@ -129,7 +129,15 @@ export function createAssistant(): Assistant {
129
129
 
130
130
  // Follow-up message → buffer text and file metadata until the flush.
131
131
  if (workingAgent && workingAgent.status !== "offline" && isAdditiveSlack()) {
132
- bufferThreadMessage(channelId, threadTs, messageText, userId, message.ts, files);
132
+ bufferThreadMessage(
133
+ channelId,
134
+ threadTs,
135
+ messageText,
136
+ userId,
137
+ message.ts,
138
+ files,
139
+ cachedBotUserId ?? undefined,
140
+ );
133
141
  const count = getBufferMessageCount(`${channelId}:${threadTs}`);
134
142
  const event = count === 1 ? "accepted" : "buffered";
135
143
  await ackSlackMessage(client, channelId, message.ts, reactionName(event), event);
@@ -147,6 +155,7 @@ export function createAssistant(): Assistant {
147
155
  // raw `messageText`, not this rendered copy.
148
156
  const renderedMessageText = await rewriteSlackMentions(
149
157
  buildEffectiveText(messageText, inbound.files, inbound.failed),
158
+ cachedBotUserId ?? undefined,
150
159
  );
151
160
 
152
161
  if (workingAgent && workingAgent.status !== "offline") {
@@ -523,7 +523,15 @@ export function registerMessageHandler(app: App): void {
523
523
  );
524
524
 
525
525
  if (nowMessage || msg.files?.length) {
526
- bufferThreadMessage(msg.channel, msg.thread_ts, nowMessage, msg.user, msg.ts, msg.files);
526
+ bufferThreadMessage(
527
+ msg.channel,
528
+ msg.thread_ts,
529
+ nowMessage,
530
+ msg.user,
531
+ msg.ts,
532
+ msg.files,
533
+ botUserId,
534
+ );
527
535
  }
528
536
 
529
537
  // Instant flush — no dependency
@@ -563,6 +571,7 @@ export function registerMessageHandler(app: App): void {
563
571
  msg.user,
564
572
  msg.ts,
565
573
  msg.files,
574
+ botUserId,
566
575
  );
567
576
 
568
577
  // Slack feedback: react with the accepted reaction on first buffer, buffered on appends
@@ -30,6 +30,7 @@ interface BufferedMessage {
30
30
  channelId: string;
31
31
  threadTs: string;
32
32
  files?: SlackFile[];
33
+ botUserId?: string;
33
34
  }
34
35
 
35
36
  const BUFFER_TIMEOUT_MS = Number(process.env.ADDITIVE_SLACK_BUFFER_MS) || 10_000;
@@ -62,6 +63,7 @@ export function bufferThreadMessage(
62
63
  userId: string,
63
64
  ts: string,
64
65
  files?: SlackFile[],
66
+ botUserId?: string,
65
67
  ): void {
66
68
  slackBuffer.enqueue(makeKey(channelId, threadTs), {
67
69
  text,
@@ -70,6 +72,7 @@ export function bufferThreadMessage(
70
72
  channelId,
71
73
  threadTs,
72
74
  files,
75
+ botUserId,
73
76
  });
74
77
  }
75
78
 
@@ -98,7 +101,11 @@ export async function instantFlush(key: string): Promise<void> {
98
101
  /**
99
102
  * Fetch thread context from Slack for the buffer flush task description.
100
103
  */
101
- async function getThreadContextForBuffer(channelId: string, threadTs: string): Promise<string> {
104
+ async function getThreadContextForBuffer(
105
+ channelId: string,
106
+ threadTs: string,
107
+ botUserId?: string,
108
+ ): Promise<string> {
102
109
  const app = getSlackApp();
103
110
  if (!app) return "";
104
111
 
@@ -126,7 +133,7 @@ async function getThreadContextForBuffer(channelId: string, threadTs: string): P
126
133
  })
127
134
  .join("\n");
128
135
 
129
- return await rewriteSlackMentions(formatted);
136
+ return await rewriteSlackMentions(formatted, botUserId);
130
137
  } catch (error) {
131
138
  console.error("[Slack] Failed to fetch thread context for buffer:", error);
132
139
  return "";
@@ -153,6 +160,9 @@ async function slackFlush(
153
160
  // Buffer is guaranteed to have at least one item — the first carries the
154
161
  // original requester's userId (same semantics as the pre-refactor version).
155
162
  const originalRequesterId = items[0]!.userId;
163
+ // Every buffered item is resolved from the same bot's auth.test() cache —
164
+ // any item carrying it is representative of the whole flush.
165
+ const botUserId = items.find((item) => item.botUserId)?.botUserId;
156
166
 
157
167
  console.log(`[Slack] Flushing buffer: ${key} (${items.length} messages, immediate=${immediate})`);
158
168
 
@@ -182,6 +192,7 @@ async function slackFlush(
182
192
  ),
183
193
  )
184
194
  .join("\n---\n"),
195
+ botUserId,
185
196
  );
186
197
  const description = `[Thread follow-up — ${items.length} message(s) buffered]\n\n${combinedText}`;
187
198
 
@@ -234,7 +245,7 @@ async function slackFlush(
234
245
  const lead = await getLeadAgent();
235
246
 
236
247
  // Thread context for the task
237
- const threadContext = await getThreadContextForBuffer(channelId, threadTs);
248
+ const threadContext = await getThreadContextForBuffer(channelId, threadTs, botUserId);
238
249
  const fullDescription = threadContext
239
250
  ? `<thread_context>\n${threadContext}\n</thread_context>\n\n${description}`
240
251
  : description;
@@ -6,9 +6,15 @@ import {
6
6
  createTaskExtended,
7
7
  getAgentWorkingOnThread,
8
8
  getLeadAgent,
9
+ getMostRecentTaskInThread,
9
10
  getTaskById,
10
11
  initDb,
11
12
  } from "../be/db";
13
+ import { createAssistant } from "../slack/assistant";
14
+ import { getBufferMessageCount, instantFlush } from "../slack/thread-buffer";
15
+
16
+ /** The single function Bolt's `Assistant` stores for the `message` event — not part of its public type. */
17
+ type AssistantUserMessageHandler = (args: Record<string, unknown>) => Promise<void>;
12
18
 
13
19
  process.env.SLACK_RENDER_V2 = "false";
14
20
 
@@ -121,6 +127,134 @@ describe("assistant userMessage routing — follow-up (working agent exists)", (
121
127
  });
122
128
  });
123
129
 
130
+ describe("assistant DM path self-mention rendering — production ingestion path", () => {
131
+ // Drives `createAssistant()`'s real `userMessage` handler end-to-end (the
132
+ // production DM path), rather than calling `rewriteSlackMentions` in
133
+ // isolation. It must fail if the fix at src/slack/assistant.ts (the
134
+ // `cachedBotUserId ?? undefined` argument to `rewriteSlackMentions`) is
135
+ // reverted.
136
+ const BOT_USER_ID = "U0PRODBOT99";
137
+ const OTHER_USER_ID = "U0PRODOTHR1";
138
+ const CHANNEL_ID = "D_PROD_DM_SELF_MENTION";
139
+ const THREAD_TS = "5551112223.000001";
140
+
141
+ test("resolves the bot's own mention and leaves another user's mention correct", async () => {
142
+ const assistant = createAssistant();
143
+ const userMessage = (assistant as unknown as { userMessage: AssistantUserMessageHandler[] })
144
+ .userMessage[0];
145
+
146
+ const client = {
147
+ auth: { test: async () => ({ user_id: BOT_USER_ID }) },
148
+ reactions: { add: async () => ({}) },
149
+ chat: { postMessage: async () => ({}) },
150
+ users: { info: async () => ({ user: undefined }) },
151
+ };
152
+
153
+ const message = {
154
+ channel: CHANNEL_ID,
155
+ ts: THREAD_TS,
156
+ thread_ts: THREAD_TS,
157
+ text: `<@${BOT_USER_ID}> can you loop in <@${OTHER_USER_ID}> on this?`,
158
+ user: "U_PROD_REQUESTER",
159
+ };
160
+
161
+ await userMessage({
162
+ message,
163
+ body: { event_id: "Ev_PROD_DM_SELF_MENTION_1" },
164
+ say: async () => {},
165
+ setStatus: async () => ({}),
166
+ setTitle: async () => ({}),
167
+ getThreadContext: async () => undefined,
168
+ client,
169
+ });
170
+
171
+ const task = await getMostRecentTaskInThread(CHANNEL_ID, THREAD_TS);
172
+ expect(task).toBeDefined();
173
+ expect(task!.task).toBe(
174
+ `<@${BOT_USER_ID}> (that's you) can you loop in <@${OTHER_USER_ID}> (unknown user) on this?`,
175
+ );
176
+ });
177
+ });
178
+
179
+ describe("assistant DM path self-mention rendering — buffered follow-up path (ADDITIVE_SLACK)", () => {
180
+ // With ADDITIVE_SLACK=true and an active agent already on the DM thread,
181
+ // userMessage buffers the message and returns before the direct
182
+ // rewriteSlackMentions(..., cachedBotUserId) call runs. The bot ID must
183
+ // still reach the later flush in thread-buffer.ts, or the bot's own
184
+ // mention renders as "(unknown user)" instead of "(that's you)".
185
+ // `cachedBotUserId` in assistant.ts is a module-level singleton resolved
186
+ // once per process, so this must reuse the ID the earlier describe block
187
+ // already cached via auth.test() — a fresh ID here would make this
188
+ // message look like it mentions "someone else" and never reach the
189
+ // buffered branch at all.
190
+ const BOT_USER_ID = "U0PRODBOT99";
191
+ const OTHER_USER_ID = "U0BUFOTHR01";
192
+ const CHANNEL_ID = "D_BUF_DM_SELF_MENTION";
193
+ const THREAD_TS = "5552223334.000001";
194
+
195
+ const originalAdditiveSlack = process.env.ADDITIVE_SLACK;
196
+
197
+ beforeAll(() => {
198
+ process.env.ADDITIVE_SLACK = "true";
199
+ });
200
+
201
+ afterAll(() => {
202
+ if (originalAdditiveSlack === undefined) delete process.env.ADDITIVE_SLACK;
203
+ else process.env.ADDITIVE_SLACK = originalAdditiveSlack;
204
+ });
205
+
206
+ test("bot's own mention resolves through the buffered flush", async () => {
207
+ const worker = await createAgent({ name: "BufferedDmWorker", isLead: false, status: "idle" });
208
+ await createTaskExtended("original task in buffered DM thread", {
209
+ agentId: worker.id,
210
+ source: "slack",
211
+ slackChannelId: CHANNEL_ID,
212
+ slackThreadTs: THREAD_TS,
213
+ slackUserId: "U_BUF_REQUESTER",
214
+ });
215
+
216
+ const assistant = createAssistant();
217
+ const userMessage = (assistant as unknown as { userMessage: AssistantUserMessageHandler[] })
218
+ .userMessage[0];
219
+
220
+ const client = {
221
+ auth: { test: async () => ({ user_id: BOT_USER_ID }) },
222
+ reactions: { add: async () => ({}) },
223
+ chat: { postMessage: async () => ({}) },
224
+ users: { info: async () => ({ user: undefined }) },
225
+ };
226
+
227
+ const message = {
228
+ channel: CHANNEL_ID,
229
+ ts: "5552223334.000002",
230
+ thread_ts: THREAD_TS,
231
+ text: `<@${BOT_USER_ID}> can you loop in <@${OTHER_USER_ID}> on this?`,
232
+ user: "U_BUF_REQUESTER",
233
+ };
234
+
235
+ await userMessage({
236
+ message,
237
+ body: { event_id: "Ev_BUF_DM_SELF_MENTION_1" },
238
+ say: async () => {},
239
+ setStatus: async () => ({}),
240
+ setTitle: async () => ({}),
241
+ getThreadContext: async () => undefined,
242
+ client,
243
+ });
244
+
245
+ // Buffered, not turned into a task synchronously.
246
+ expect(getBufferMessageCount(`${CHANNEL_ID}:${THREAD_TS}`)).toBe(1);
247
+
248
+ await instantFlush(`${CHANNEL_ID}:${THREAD_TS}`);
249
+
250
+ const flushed = await getMostRecentTaskInThread(CHANNEL_ID, THREAD_TS);
251
+ expect(flushed).toBeDefined();
252
+ expect(flushed!.task).toContain(`<@${BOT_USER_ID}> (that's you)`);
253
+ expect(flushed!.task).not.toContain(`<@${BOT_USER_ID}> (unknown user)`);
254
+ expect(flushed!.task).toContain(`<@${OTHER_USER_ID}> (unknown user)`);
255
+ });
256
+ });
257
+
124
258
  describe("assistant thread title truncation", () => {
125
259
  test("short messages stay as-is", () => {
126
260
  const text = "Check agent status";