agent-messenger 2.38.0 → 2.39.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 (115) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/dist/package.json +1 -1
  3. package/dist/src/platforms/discordbot/client-message.d.ts +15 -0
  4. package/dist/src/platforms/discordbot/client-message.d.ts.map +1 -0
  5. package/dist/src/platforms/discordbot/client-message.js +70 -0
  6. package/dist/src/platforms/discordbot/client-message.js.map +1 -0
  7. package/dist/src/platforms/discordbot/client-threads.d.ts +10 -0
  8. package/dist/src/platforms/discordbot/client-threads.d.ts.map +1 -0
  9. package/dist/src/platforms/discordbot/client-threads.js +31 -0
  10. package/dist/src/platforms/discordbot/client-threads.js.map +1 -0
  11. package/dist/src/platforms/discordbot/client.d.ts +14 -2
  12. package/dist/src/platforms/discordbot/client.d.ts.map +1 -1
  13. package/dist/src/platforms/discordbot/client.js +21 -28
  14. package/dist/src/platforms/discordbot/client.js.map +1 -1
  15. package/dist/src/platforms/discordbot/commands/file.d.ts +14 -10
  16. package/dist/src/platforms/discordbot/commands/file.d.ts.map +1 -1
  17. package/dist/src/platforms/discordbot/commands/file.js +58 -24
  18. package/dist/src/platforms/discordbot/commands/file.js.map +1 -1
  19. package/dist/src/platforms/discordbot/commands/message.d.ts +3 -9
  20. package/dist/src/platforms/discordbot/commands/message.d.ts.map +1 -1
  21. package/dist/src/platforms/discordbot/commands/message.js +9 -51
  22. package/dist/src/platforms/discordbot/commands/message.js.map +1 -1
  23. package/dist/src/platforms/discordbot/commands/reaction.d.ts +9 -0
  24. package/dist/src/platforms/discordbot/commands/reaction.d.ts.map +1 -1
  25. package/dist/src/platforms/discordbot/commands/reaction.js +39 -0
  26. package/dist/src/platforms/discordbot/commands/reaction.js.map +1 -1
  27. package/dist/src/platforms/discordbot/commands/shared.d.ts +36 -0
  28. package/dist/src/platforms/discordbot/commands/shared.d.ts.map +1 -1
  29. package/dist/src/platforms/discordbot/commands/shared.js +36 -0
  30. package/dist/src/platforms/discordbot/commands/shared.js.map +1 -1
  31. package/dist/src/platforms/discordbot/commands/thread.d.ts +14 -0
  32. package/dist/src/platforms/discordbot/commands/thread.d.ts.map +1 -1
  33. package/dist/src/platforms/discordbot/commands/thread.js +40 -0
  34. package/dist/src/platforms/discordbot/commands/thread.js.map +1 -1
  35. package/dist/src/platforms/discordbot/index.d.ts +1 -1
  36. package/dist/src/platforms/discordbot/index.d.ts.map +1 -1
  37. package/dist/src/platforms/discordbot/index.js.map +1 -1
  38. package/dist/src/platforms/discordbot/types.d.ts +68 -17
  39. package/dist/src/platforms/discordbot/types.d.ts.map +1 -1
  40. package/dist/src/platforms/discordbot/types.js +20 -16
  41. package/dist/src/platforms/discordbot/types.js.map +1 -1
  42. package/dist/src/platforms/kakaotalk/client.d.ts.map +1 -1
  43. package/dist/src/platforms/kakaotalk/client.js +4 -32
  44. package/dist/src/platforms/kakaotalk/client.js.map +1 -1
  45. package/dist/src/platforms/kakaotalk/sync-state-store.d.ts +11 -0
  46. package/dist/src/platforms/kakaotalk/sync-state-store.d.ts.map +1 -0
  47. package/dist/src/platforms/kakaotalk/sync-state-store.js +89 -0
  48. package/dist/src/platforms/kakaotalk/sync-state-store.js.map +1 -0
  49. package/dist/src/platforms/slack/cli.d.ts.map +1 -1
  50. package/dist/src/platforms/slack/cli.js +5 -0
  51. package/dist/src/platforms/slack/cli.js.map +1 -1
  52. package/dist/src/platforms/slack/commands/auth.d.ts.map +1 -1
  53. package/dist/src/platforms/slack/commands/auth.js +9 -3
  54. package/dist/src/platforms/slack/commands/auth.js.map +1 -1
  55. package/dist/src/platforms/slack/credential-manager.d.ts +2 -0
  56. package/dist/src/platforms/slack/credential-manager.d.ts.map +1 -1
  57. package/dist/src/platforms/slack/credential-manager.js +17 -3
  58. package/dist/src/platforms/slack/credential-manager.js.map +1 -1
  59. package/docs/content/docs/cli/discordbot.mdx +60 -27
  60. package/docs/content/docs/cli/slack.mdx +8 -1
  61. package/docs/content/docs/sdk/discordbot.mdx +101 -3
  62. package/e2e/README.md +3 -1
  63. package/e2e/config.ts +1 -1
  64. package/e2e/discordbot.e2e.test.ts +206 -11
  65. package/package.json +1 -1
  66. package/skills/agent-channeltalk/SKILL.md +1 -1
  67. package/skills/agent-channeltalkbot/SKILL.md +1 -1
  68. package/skills/agent-discord/SKILL.md +1 -1
  69. package/skills/agent-discordbot/SKILL.md +57 -11
  70. package/skills/agent-discordbot/references/authentication.md +13 -11
  71. package/skills/agent-discordbot/references/common-patterns.md +36 -8
  72. package/skills/agent-imessage/SKILL.md +1 -1
  73. package/skills/agent-instagram/SKILL.md +1 -1
  74. package/skills/agent-kakaotalk/SKILL.md +1 -1
  75. package/skills/agent-line/SKILL.md +1 -1
  76. package/skills/agent-slack/SKILL.md +9 -2
  77. package/skills/agent-slack/references/authentication.md +12 -0
  78. package/skills/agent-slack/references/common-patterns.md +18 -0
  79. package/skills/agent-slackbot/SKILL.md +1 -1
  80. package/skills/agent-teams/SKILL.md +1 -1
  81. package/skills/agent-telegram/SKILL.md +1 -1
  82. package/skills/agent-telegrambot/SKILL.md +1 -1
  83. package/skills/agent-webex/SKILL.md +1 -1
  84. package/skills/agent-webexbot/SKILL.md +1 -1
  85. package/skills/agent-wechatbot/SKILL.md +1 -1
  86. package/skills/agent-whatsapp/SKILL.md +1 -1
  87. package/skills/agent-whatsappbot/SKILL.md +1 -1
  88. package/src/platforms/discordbot/client-message.ts +91 -0
  89. package/src/platforms/discordbot/client-threads.test.ts +177 -0
  90. package/src/platforms/discordbot/client-threads.ts +69 -0
  91. package/src/platforms/discordbot/client.test.ts +277 -1
  92. package/src/platforms/discordbot/client.ts +60 -40
  93. package/src/platforms/discordbot/commands/file.test.ts +242 -90
  94. package/src/platforms/discordbot/commands/file.ts +78 -36
  95. package/src/platforms/discordbot/commands/message.test.ts +183 -26
  96. package/src/platforms/discordbot/commands/message.ts +13 -61
  97. package/src/platforms/discordbot/commands/reaction.test.ts +73 -1
  98. package/src/platforms/discordbot/commands/reaction.ts +46 -0
  99. package/src/platforms/discordbot/commands/shared.test.ts +98 -0
  100. package/src/platforms/discordbot/commands/shared.ts +67 -0
  101. package/src/platforms/discordbot/commands/thread.test.ts +58 -1
  102. package/src/platforms/discordbot/commands/thread.ts +58 -0
  103. package/src/platforms/discordbot/index.ts +3 -0
  104. package/src/platforms/discordbot/types.test.ts +46 -0
  105. package/src/platforms/discordbot/types.ts +44 -19
  106. package/src/platforms/kakaotalk/client-listener-integration.test.ts +21 -3
  107. package/src/platforms/kakaotalk/client.test.ts +42 -4
  108. package/src/platforms/kakaotalk/client.ts +4 -39
  109. package/src/platforms/kakaotalk/sync-state-store.test.ts +197 -0
  110. package/src/platforms/kakaotalk/sync-state-store.ts +99 -0
  111. package/src/platforms/slack/cli.test.ts +111 -1
  112. package/src/platforms/slack/cli.ts +5 -0
  113. package/src/platforms/slack/commands/auth.ts +14 -3
  114. package/src/platforms/slack/credential-manager.ts +21 -3
  115. package/src/platforms/slack/credential-manager.workspace-select.test.ts +70 -0
@@ -1,4 +1,7 @@
1
1
  import { describe, it, expect, beforeAll, afterEach } from 'bun:test'
2
+ import { rm } from 'node:fs/promises'
3
+ import { tmpdir } from 'node:os'
4
+ import { join } from 'node:path'
2
5
 
3
6
  import { DiscordBotClient } from '../src/platforms/discordbot/client'
4
7
  import { DiscordBotCredentialManager } from '../src/platforms/discordbot/credential-manager'
@@ -16,6 +19,7 @@ interface TrackedMessage {
16
19
  }
17
20
 
18
21
  let testMessages: TrackedMessage[] = []
22
+ let testThreads: string[] = []
19
23
  let cleanupClient: DiscordBotClient
20
24
 
21
25
  async function getClient(): Promise<DiscordBotClient> {
@@ -36,10 +40,29 @@ async function cleanupBotMessages(messages: TrackedMessage[]) {
36
40
  }
37
41
  }
38
42
 
43
+ async function cleanupBotThreads(threads: string[]) {
44
+ for (const threadId of threads) {
45
+ try {
46
+ await cleanupClient.archiveThread(threadId)
47
+ await waitForRateLimit(500)
48
+ } catch {
49
+ // best-effort cleanup
50
+ }
51
+ }
52
+ }
53
+
39
54
  function trackMessage(id: string, channelId: string = DISCORDBOT_TEST_CHANNEL_ID) {
40
55
  testMessages.push({ id, channelId })
41
56
  }
42
57
 
58
+ function trackThread(id: string) {
59
+ testThreads.push(id)
60
+ }
61
+
62
+ function untrackThread(id: string) {
63
+ testThreads = testThreads.filter((threadId) => threadId !== id)
64
+ }
65
+
43
66
  describe('DiscordBot E2E Tests', () => {
44
67
  beforeAll(async () => {
45
68
  await validateDiscordBotEnvironment()
@@ -51,6 +74,10 @@ describe('DiscordBot E2E Tests', () => {
51
74
  await cleanupBotMessages(testMessages)
52
75
  testMessages = []
53
76
  }
77
+ if (testThreads.length > 0) {
78
+ await cleanupBotThreads(testThreads)
79
+ testThreads = []
80
+ }
54
81
  await waitForRateLimit()
55
82
  })
56
83
 
@@ -161,12 +188,20 @@ describe('DiscordBot E2E Tests', () => {
161
188
  expect(result.exitCode).toBe(0)
162
189
  })
163
190
 
164
- it('message send with --thread creates reply', async () => {
191
+ it('message send with --thread creates reply in the thread', async () => {
165
192
  const testId = generateTestId()
166
- const sendResult = await runCLI('discordbot', ['message', 'send', DISCORDBOT_TEST_CHANNEL_ID, `Parent ${testId}`])
167
- const parent = parseJSON<{ id: string }>(sendResult.stdout)
168
- expect(parent?.id).toBeTruthy()
169
- if (parent?.id) trackMessage(parent.id)
193
+ const threadResult = await runCLI('discordbot', [
194
+ 'thread',
195
+ 'create',
196
+ DISCORDBOT_TEST_CHANNEL_ID,
197
+ `E2E Thread ${testId}`,
198
+ ])
199
+ expect(threadResult.exitCode).toBe(0)
200
+
201
+ const thread = parseJSON<{ thread: { id: string } }>(threadResult.stdout)
202
+ expect(thread?.thread?.id).toBeTruthy()
203
+ const threadId = thread!.thread.id
204
+ trackThread(threadId)
170
205
 
171
206
  await waitForRateLimit()
172
207
 
@@ -176,15 +211,14 @@ describe('DiscordBot E2E Tests', () => {
176
211
  DISCORDBOT_TEST_CHANNEL_ID,
177
212
  `Reply ${testId}`,
178
213
  '--thread',
179
- parent!.id,
214
+ threadId,
180
215
  ])
181
216
  expect(replyResult.exitCode).toBe(0)
182
217
 
183
- const reply = parseJSON<{ id: string; thread_id: string }>(replyResult.stdout)
218
+ const reply = parseJSON<{ id: string; channel_id: string }>(replyResult.stdout)
184
219
  expect(reply?.id).toBeTruthy()
185
-
186
- const replyChannel = reply?.thread_id || DISCORDBOT_TEST_CHANNEL_ID
187
- if (reply?.id) trackMessage(reply.id, replyChannel)
220
+ expect(reply?.channel_id).toBe(threadId)
221
+ if (reply?.id) trackMessage(reply.id, threadId)
188
222
  }, 30000)
189
223
 
190
224
  it('message replies gets thread messages', async () => {
@@ -201,6 +235,7 @@ describe('DiscordBot E2E Tests', () => {
201
235
  const thread = parseJSON<{ thread: { id: string } }>(threadResult.stdout)
202
236
  expect(thread?.thread?.id).toBeTruthy()
203
237
  const threadId = thread!.thread.id
238
+ trackThread(threadId)
204
239
 
205
240
  await waitForRateLimit()
206
241
 
@@ -222,9 +257,155 @@ describe('DiscordBot E2E Tests', () => {
222
257
 
223
258
  // cleanup: archive the thread
224
259
  await runCLI('discordbot', ['thread', 'archive', threadId])
260
+ untrackThread(threadId)
225
261
  })
226
262
  })
227
263
 
264
+ describe('file', () => {
265
+ it('uploads a file into a thread and round-trips its metadata', async () => {
266
+ const testId = generateTestId()
267
+ const filePath = join(tmpdir(), `e2e-${testId}.txt`)
268
+ await Bun.write(filePath, testId)
269
+
270
+ try {
271
+ const threadResult = await runCLI('discordbot', [
272
+ 'thread',
273
+ 'create',
274
+ DISCORDBOT_TEST_CHANNEL_ID,
275
+ `E2E Thread ${testId}`,
276
+ ])
277
+ expect(threadResult.exitCode).toBe(0)
278
+ const thread = parseJSON<{ thread: { id: string } }>(threadResult.stdout)
279
+ expect(thread?.thread?.id).toBeTruthy()
280
+ const threadId = thread!.thread.id
281
+ trackThread(threadId)
282
+
283
+ await waitForRateLimit()
284
+
285
+ const uploadResult = await runCLI('discordbot', [
286
+ 'file',
287
+ 'upload',
288
+ DISCORDBOT_TEST_CHANNEL_ID,
289
+ filePath,
290
+ '--text',
291
+ `File ${testId}`,
292
+ '--thread',
293
+ threadId,
294
+ ])
295
+ expect(uploadResult.exitCode).toBe(0)
296
+ const upload = parseJSON<{
297
+ channel_id: string
298
+ message_id: string
299
+ file: { id: string; filename: string }
300
+ files: Array<{ filename: string }>
301
+ }>(uploadResult.stdout)
302
+ expect(upload?.channel_id).toBe(threadId)
303
+ expect(upload?.message_id).toBeTruthy()
304
+ expect(upload?.file?.filename).toBe(`e2e-${testId}.txt`)
305
+ expect(upload?.files).toHaveLength(1)
306
+ trackMessage(upload!.message_id, threadId)
307
+
308
+ await waitForRateLimit()
309
+
310
+ const messageResult = await runCLI('discordbot', ['message', 'get', threadId, upload!.message_id])
311
+ expect(messageResult.exitCode).toBe(0)
312
+ const message = parseJSON<{
313
+ channel_id: string
314
+ content: string
315
+ attachments: Array<{ id: string; filename: string; size: number }>
316
+ }>(messageResult.stdout)
317
+ expect(message?.channel_id).toBe(threadId)
318
+ expect(message?.content).toBe(`File ${testId}`)
319
+ expect(message?.attachments).toHaveLength(1)
320
+ expect(message!.attachments[0].filename).toBe(`e2e-${testId}.txt`)
321
+ expect(message!.attachments[0].size).toBe(Buffer.byteLength(testId))
322
+
323
+ await waitForRateLimit()
324
+
325
+ const namedResult = await runCLI('discordbot', [
326
+ 'message',
327
+ 'send',
328
+ DISCORDBOT_TEST_CHANNEL_ID,
329
+ `Reply ${testId}`,
330
+ '--thread',
331
+ `E2E Thread ${testId}`,
332
+ ])
333
+ expect(namedResult.exitCode).toBe(0)
334
+ const named = parseJSON<{ id: string; channel_id: string }>(namedResult.stdout)
335
+ expect(named?.channel_id).toBe(threadId)
336
+ if (named?.id) trackMessage(named.id, threadId)
337
+
338
+ await waitForRateLimit()
339
+
340
+ const missingNameResult = await runCLI('discordbot', [
341
+ 'message',
342
+ 'send',
343
+ DISCORDBOT_TEST_CHANNEL_ID,
344
+ 'x',
345
+ '--thread',
346
+ `no-such-thread-${testId}`,
347
+ ])
348
+ expect(missingNameResult.exitCode).toBe(1)
349
+ const missingName = parseJSON<{ error: string }>(missingNameResult.stdout)
350
+ expect(missingName?.error).toContain('thread list')
351
+ expect(missingNameResult.stderr).toBe('')
352
+
353
+ await waitForRateLimit()
354
+
355
+ const activeListResult = await runCLI('discordbot', ['thread', 'list', DISCORDBOT_TEST_CHANNEL_ID])
356
+ expect(activeListResult.exitCode).toBe(0)
357
+ const activeList = parseJSON<{
358
+ threads: Array<{ id: string; parent_id?: string; archived?: boolean }>
359
+ }>(activeListResult.stdout)
360
+ expect(
361
+ activeList?.threads?.some(
362
+ (item) => item.id === threadId && item.parent_id === DISCORDBOT_TEST_CHANNEL_ID && item.archived === false,
363
+ ),
364
+ ).toBe(true)
365
+
366
+ await waitForRateLimit()
367
+
368
+ const infoResult = await runCLI('discordbot', ['file', 'info', threadId, upload!.file.id])
369
+ expect(infoResult.exitCode).toBe(0)
370
+ const info = parseJSON<{ id: string; filename: string }>(infoResult.stdout)
371
+ expect(info?.id).toBe(upload!.file.id)
372
+ expect(info?.filename).toBe(`e2e-${testId}.txt`)
373
+
374
+ const missingInfoResult = await runCLI('discordbot', [
375
+ 'file',
376
+ 'info',
377
+ DISCORDBOT_TEST_CHANNEL_ID,
378
+ '000000000000000000',
379
+ ])
380
+ expect(missingInfoResult.exitCode).toBe(0)
381
+ const missingInfo = parseJSON<{ error: string }>(missingInfoResult.stdout)
382
+ expect(missingInfo?.error).toContain('File not found')
383
+
384
+ await waitForRateLimit()
385
+
386
+ const parentMessagesResult = await runCLI('discordbot', [
387
+ 'message',
388
+ 'list',
389
+ DISCORDBOT_TEST_CHANNEL_ID,
390
+ '--limit',
391
+ '5',
392
+ ])
393
+ expect(parentMessagesResult.exitCode).toBe(0)
394
+ const parentMessages = parseJSON<{ messages: Array<{ content: string }> }>(parentMessagesResult.stdout)
395
+ expect(parentMessages?.messages?.some((item) => item.content === 'x')).toBe(false)
396
+
397
+ const archiveResult = await runCLI('discordbot', ['thread', 'archive', threadId])
398
+ expect(archiveResult.exitCode).toBe(0)
399
+ untrackThread(threadId)
400
+
401
+ // Discord's archived-thread index is eventually consistent; active listing above is the stable proof.
402
+ await waitForRateLimit(2000)
403
+ } finally {
404
+ await rm(filePath, { force: true })
405
+ }
406
+ }, 60000)
407
+ })
408
+
228
409
  describe('channel', () => {
229
410
  it('channel list returns channels array', async () => {
230
411
  const result = await runCLI('discordbot', ['channel', 'list'])
@@ -288,6 +469,11 @@ describe('DiscordBot E2E Tests', () => {
288
469
 
289
470
  await waitForRateLimit(2000)
290
471
 
472
+ const emptyListResult = await runCLI('discordbot', ['reaction', 'list', DISCORDBOT_TEST_CHANNEL_ID, sent!.id])
473
+ expect(emptyListResult.exitCode).toBe(0)
474
+ const emptyList = parseJSON<{ reactions: unknown[] }>(emptyListResult.stdout)
475
+ expect(emptyList?.reactions).toEqual([])
476
+
291
477
  // when: add reaction
292
478
  const addResult = await runCLI('discordbot', ['reaction', 'add', DISCORDBOT_TEST_CHANNEL_ID, sent!.id, '👍'])
293
479
  expect(addResult.exitCode).toBe(0)
@@ -297,6 +483,15 @@ describe('DiscordBot E2E Tests', () => {
297
483
 
298
484
  await waitForRateLimit(2000)
299
485
 
486
+ const listResult = await runCLI('discordbot', ['reaction', 'list', DISCORDBOT_TEST_CHANNEL_ID, sent!.id])
487
+ expect(listResult.exitCode).toBe(0)
488
+ const listed = parseJSON<{
489
+ reactions: Array<{ emoji: { name: string }; count: number; me: boolean }>
490
+ }>(listResult.stdout)
491
+ expect(listed?.reactions?.[0]?.emoji?.name).toBe('👍')
492
+ expect(listed?.reactions?.[0]?.count).toBe(1)
493
+ expect(listed?.reactions?.[0]?.me).toBe(true)
494
+
300
495
  // then: remove reaction
301
496
  const removeResult = await runCLI('discordbot', [
302
497
  'reaction',
@@ -309,7 +504,7 @@ describe('DiscordBot E2E Tests', () => {
309
504
 
310
505
  const removeData = parseJSON<{ success: boolean }>(removeResult.stdout)
311
506
  expect(removeData?.success).toBe(true)
312
- }, 15000)
507
+ }, 30000)
313
508
  })
314
509
 
315
510
  describe('server', () => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-messenger",
3
- "version": "2.38.0",
3
+ "version": "2.39.0",
4
4
  "description": "Multi-platform messaging CLI for AI agents (Slack, Discord, Teams, Webex, Telegram, Telegram Bot, WhatsApp, LINE, iMessage, Instagram, KakaoTalk, Channel Talk)",
5
5
  "repository": {
6
6
  "type": "git",
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: agent-channeltalk
3
3
  description: Interact with Channel Talk using extracted desktop app or browser credentials - read chats, send messages, search messages, manage groups
4
- version: 2.38.0
4
+ version: 2.39.0
5
5
  allowed-tools: Bash(agent-channeltalk:*)
6
6
  metadata:
7
7
  openclaw:
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: agent-channeltalkbot
3
3
  description: Interact with Channel Talk workspaces using API credentials - send messages, read chats, manage groups and bots
4
- version: 2.38.0
4
+ version: 2.39.0
5
5
  allowed-tools: Bash(agent-channeltalkbot:*)
6
6
  metadata:
7
7
  openclaw:
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: agent-discord
3
3
  description: Interact with Discord servers - send messages, read channels, manage reactions
4
- version: 2.38.0
4
+ version: 2.39.0
5
5
  allowed-tools: Bash(agent-discord:*)
6
6
  metadata:
7
7
  openclaw:
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: agent-discordbot
3
3
  description: Interact with Discord servers using bot tokens - send messages, read channels, manage reactions
4
- version: 2.38.0
4
+ version: 2.39.0
5
5
  allowed-tools: Bash(agent-discordbot:*)
6
6
  metadata:
7
7
  openclaw:
@@ -27,7 +27,9 @@ Before diving in, a few things about Discord Bot integration:
27
27
  - **Privileged intents** — `MessageContent`, `GuildMembers`, and `GuildPresences` are privileged and must be enabled in the Developer Portal before they can be used by the SDK listener.
28
28
  - **Permission gates** — Bot capabilities depend on the role's permission flags in each server. Missing permissions return 403 errors.
29
29
  - **Real-time events** — Available via the SDK's Gateway listener, not via the CLI.
30
- - **Channel resolution** — Use channel IDs (snowflake numbers) directly. The CLI does not resolve `#channel-name` syntax.
30
+ - **Channel resolution** — Every `<channel>` argument accepts a channel ID (snowflake) or an exact channel name (`general` or `#general`). IDs skip the lookup and are faster; names fail with `Channel not found` when nothing matches exactly.
31
+ - **Thread targeting** — `--thread <id|name>` on `message send` and `file upload` posts into a thread. A numeric value is used as the thread ID; a name is matched against the active threads of that channel (`thread_not_found` / `thread_ambiguous` otherwise). Archived threads must be targeted by ID (find it with `thread list <channel> --archived`); posting to an archived thread unarchives it unless it's locked.
32
+ - **`thread_id` vs `channel_id` in output** — For a message posted inside a thread, `channel_id` IS the thread ID. `thread_id` is only set when a thread was started from that message; otherwise it's `null`.
31
33
 
32
34
  ## Quick Start
33
35
 
@@ -56,7 +58,7 @@ agent-discordbot uses Discord Bot tokens which you create in the Discord Develop
56
58
  agent-discordbot auth set your-bot-token
57
59
 
58
60
  # Set with a custom bot identifier
59
- agent-discordbot auth set your-bot-token --bot deploy --name "Deploy Bot"
61
+ agent-discordbot auth set your-bot-token --bot deploy
60
62
 
61
63
  # Check auth status
62
64
  agent-discordbot auth status
@@ -151,7 +153,7 @@ If a memorized ID returns an error (channel not found, server not found), remove
151
153
  ```bash
152
154
  # Set bot token
153
155
  agent-discordbot auth set <token>
154
- agent-discordbot auth set <token> --bot deploy --name "Deploy Bot"
156
+ agent-discordbot auth set <token> --bot deploy
155
157
 
156
158
  # Check auth status
157
159
  agent-discordbot auth status
@@ -205,9 +207,13 @@ agent-discordbot message send 1234567890123456789 "Hello world"
205
207
  agent-discordbot message send <channel-id> <content> --reply <message-id>
206
208
  agent-discordbot message send 1234567890123456789 "On it!" --reply 9876543210987654321
207
209
 
208
- # Send a message into a thread
209
- agent-discordbot message send <channel-id> <content> --thread <thread-id>
210
+ # Send a message into a thread (by ID or by active-thread name); the returned channel_id equals the thread ID
211
+ agent-discordbot message send <channel-id> <content> --thread <thread-id|thread-name>
210
212
  agent-discordbot message send 1234567890123456789 "Hello world" --thread 9876543210987654321
213
+ agent-discordbot message send 1234567890123456789 "Hello world" --thread "Deployment Progress"
214
+
215
+ # Sending directly to a thread ID also works (a thread is a channel)
216
+ agent-discordbot message send 9876543210987654321 "Hello thread"
211
217
 
212
218
  # List messages
213
219
  agent-discordbot message list <channel-id>
@@ -243,7 +249,6 @@ agent-discordbot channel info 1234567890123456789
243
249
  ```bash
244
250
  # List server members
245
251
  agent-discordbot user list
246
- agent-discordbot user list --limit 50
247
252
 
248
253
  # Get user info
249
254
  agent-discordbot user info <user-id>
@@ -258,19 +263,36 @@ agent-discordbot reaction add 1234567890123456789 9876543210987654321 thumbsup
258
263
 
259
264
  # Remove reaction
260
265
  agent-discordbot reaction remove <channel-id> <message-id> <emoji>
266
+
267
+ # List reactions on a message: [{ emoji: {id, name}, count, me }]
268
+ agent-discordbot reaction list <channel-id> <message-id>
261
269
  ```
262
270
 
263
271
  ### File Commands
264
272
 
265
273
  ```bash
266
- # Upload file to a channel
267
- agent-discordbot file upload <channel-id> <path>
274
+ # Upload one or more files (max 10) to a channel, optionally with text
275
+ agent-discordbot file upload <channel-id> <path...> [--text <text>] [--thread <id|name>] [--reply <message-id>] [--filename <name>]
268
276
  agent-discordbot file upload 1234567890123456789 ./report.pdf
277
+ agent-discordbot file upload 1234567890123456789 ./report.pdf ./chart.png --text "Weekly numbers"
278
+
279
+ # Upload into a thread, or as a reply
280
+ agent-discordbot file upload 1234567890123456789 ./log.txt --thread 9876543210987654321
281
+ agent-discordbot file upload 1234567890123456789 ./log.txt --text "Full log attached" --reply 9876543210987654321
269
282
 
270
- # List files in channel
283
+ # Override the stored filename (single file only)
284
+ agent-discordbot file upload 1234567890123456789 ./tmp-8x1.txt --filename build.log
285
+
286
+ # List files in channel (attachments of the latest 100 messages, one request)
271
287
  agent-discordbot file list <channel-id>
288
+
289
+ # Get info for one attachment (id, filename, size, url, content_type)
290
+ # Searches back through the latest 1,000 messages and stops at the first match
291
+ agent-discordbot file info <channel-id> <file-id>
272
292
  ```
273
293
 
294
+ `file upload` returns `{ success, file, files, message_id, channel_id }`. `file` is the first attachment, `files` has all of them, and `channel_id` is the thread ID when `--thread` was used. Every message read path (`message send|list|get|replies`) includes `attachments: [{ id, filename, size, url, content_type }]`, `[]` when there are none. There is no fixed size limit in the CLI; Discord decides per server and reports `40005` (or `http_413`) when a file is too big.
295
+
274
296
  ### Thread Commands
275
297
 
276
298
  ```bash
@@ -278,10 +300,19 @@ agent-discordbot file list <channel-id>
278
300
  agent-discordbot thread create <channel-id> <name>
279
301
  agent-discordbot thread create 1234567890123456789 "Discussion" --auto-archive-duration 1440
280
302
 
303
+ # List active threads (whole server, or one channel's threads)
304
+ agent-discordbot thread list
305
+ agent-discordbot thread list <channel-id>
306
+
307
+ # List archived threads of a channel (100 most recently archived; has_more tells you if there are older ones)
308
+ agent-discordbot thread list <channel-id> --archived
309
+
281
310
  # Archive a thread
282
311
  agent-discordbot thread archive <thread-id>
283
312
  ```
284
313
 
314
+ `thread list` returns `{ threads: [{ id, name, type, parent_id, archived }], has_more }`. `--archived` needs a channel argument (`parent_required` otherwise).
315
+
285
316
  ### Snapshot Command
286
317
 
287
318
  Get server overview for AI agents (brief by default):
@@ -369,6 +400,16 @@ All commands return consistent error format:
369
400
 
370
401
  Common errors: `missing_token`, `invalid_token`, `Missing Access`, `Unknown Channel`, `Missing Permissions`.
371
402
 
403
+ Errors specific to sending files and targeting threads:
404
+
405
+ - `empty_message`: `file upload` needs at least one file, or `--text` alongside it.
406
+ - `too_many_files`: more than 10 files in one upload.
407
+ - `invalid_filename`: `--filename` is empty, `.`, `..`, or contains a path separator.
408
+ - `thread_not_found`: `--thread <name>` matched no active thread in that channel. Run `thread list <channel>` (add `--archived` for archived ones) or pass the thread ID.
409
+ - `thread_ambiguous`: several active threads share that name; the message lists their IDs. Use the ID.
410
+ - `parent_required`: `thread list --archived` was called without a channel.
411
+ - `40005` / `http_413`: Discord rejected the upload as too large for that server's limit. The error message says so; the CLI doesn't preflight sizes.
412
+
372
413
  ## Configuration
373
414
 
374
415
  Credentials stored in `~/.config/agent-messenger/discordbot-credentials.json` (0600 permissions). See [references/authentication.md](references/authentication.md) for format and security details.
@@ -384,7 +425,9 @@ Credentials stored in `~/.config/agent-messenger/discordbot-credentials.json` (0
384
425
  | Mentions | Yes | No |
385
426
  | Friends/Notes | Yes | No |
386
427
  | Edit/delete messages | Any message | Bot's own messages only |
387
- | File upload | Yes | Yes |
428
+ | File upload | Yes | Yes (text, threads, replies) |
429
+ | Thread list | No | Yes (active + archived) |
430
+ | Reaction list | Yes | Yes |
388
431
  | Snapshot | Yes | Yes |
389
432
  | CI/CD friendly | Requires desktop app | Yes (just set token) |
390
433
 
@@ -401,6 +444,9 @@ Credentials stored in `~/.config/agent-messenger/discordbot-credentials.json` (0
401
444
  - Bot must be invited to the server and have appropriate permissions
402
445
  - Message Content intent required for verified bots (100+ servers)
403
446
  - Plain text messages only (no embeds in v1)
447
+ - Thread names resolve against active threads only; archived threads need their ID
448
+ - `thread list --archived` returns one page (100 threads) with `has_more`; no pagination beyond that
449
+ - No file download or delete
404
450
 
405
451
  ## Troubleshooting
406
452
 
@@ -25,6 +25,7 @@ agent-discordbot uses Discord Bot tokens obtained from the Discord Developer Por
25
25
  2. Under **Scopes**, select `bot`
26
26
  3. Under **Bot Permissions**, select the permissions you need:
27
27
  - Send Messages
28
+ - Send Messages in Threads
28
29
  - Read Message History
29
30
  - View Channels
30
31
  - Add Reactions
@@ -49,7 +50,7 @@ Even for unverified bots, enabling this intent is recommended to avoid surprises
49
50
  ```bash
50
51
  agent-discordbot auth set your-bot-token
51
52
 
52
- agent-discordbot auth set your-bot-token --bot deploy --name "Deploy Bot"
53
+ agent-discordbot auth set your-bot-token --bot deploy
53
54
  ```
54
55
 
55
56
  This command:
@@ -102,8 +103,8 @@ This command:
102
103
  Store and switch between multiple bot tokens:
103
104
 
104
105
  ```bash
105
- agent-discordbot auth set deploy-bot-token --bot deploy --name "Deploy Bot"
106
- agent-discordbot auth set alert-bot-token --bot alert --name "Alert Bot"
106
+ agent-discordbot auth set deploy-bot-token --bot deploy
107
+ agent-discordbot auth set alert-bot-token --bot alert
107
108
 
108
109
  agent-discordbot auth list
109
110
 
@@ -171,14 +172,15 @@ agent-discordbot auth status
171
172
 
172
173
  ## Required Bot Permissions
173
174
 
174
- | Permission | Used For |
175
- | -------------------- | ----------------------------------- |
176
- | Send Messages | Sending messages to channels |
177
- | Read Message History | Reading channel messages |
178
- | View Channels | Listing and accessing channels |
179
- | Add Reactions | Adding and removing emoji reactions |
180
- | Attach Files | Uploading files to channels |
181
- | Manage Threads | Creating and archiving threads |
175
+ | Permission | Used For |
176
+ | ------------------------ | ---------------------------------------------------- |
177
+ | Send Messages | Sending messages to channels |
178
+ | Send Messages in Threads | Posting messages and files into threads (`--thread`) |
179
+ | Read Message History | Reading channel messages |
180
+ | View Channels | Listing and accessing channels |
181
+ | Add Reactions | Adding and removing emoji reactions |
182
+ | Attach Files | Uploading files to channels and threads |
183
+ | Manage Threads | Creating and archiving threads |
182
184
 
183
185
  ## Troubleshooting
184
186
 
@@ -39,14 +39,17 @@ CHANNEL="1234567890123456789"
39
39
  RESULT=$(agent-discordbot thread create "$CHANNEL" "Deployment Progress")
40
40
  THREAD_ID=$(echo "$RESULT" | jq -r '.thread.id')
41
41
 
42
- # Send updates in the thread
42
+ # Send updates in the thread (either target the thread ID directly, or use --thread from the parent channel)
43
43
  agent-discordbot message send "$THREAD_ID" "Building application..."
44
44
  sleep 2
45
- agent-discordbot message send "$THREAD_ID" "Running tests..."
45
+ agent-discordbot message send "$CHANNEL" "Running tests..." --thread "$THREAD_ID"
46
46
  sleep 2
47
- agent-discordbot message send "$THREAD_ID" "Deploying to production..."
47
+ agent-discordbot message send "$CHANNEL" "Deploying to production..." --thread "Deployment Progress"
48
48
  sleep 2
49
- agent-discordbot message send "$THREAD_ID" "Deployment complete!"
49
+
50
+ # Attach the build log to the thread; channel_id in the result equals the thread ID
51
+ RESULT=$(agent-discordbot file upload "$CHANNEL" ./build.log --text "Deployment complete!" --thread "$THREAD_ID")
52
+ echo "$RESULT" | jq -r '.channel_id, .file.filename'
50
53
 
51
54
  # Add reaction to the original channel message
52
55
  agent-discordbot reaction add "$CHANNEL" "$THREAD_ID" white_check_mark
@@ -54,6 +57,29 @@ agent-discordbot reaction add "$CHANNEL" "$THREAD_ID" white_check_mark
54
57
 
55
58
  **When to use**: Multi-step processes, CI/CD pipelines, progress tracking.
56
59
 
60
+ ## Pattern 2b: Find an Existing Thread
61
+
62
+ **Use case**: Continue a conversation in a thread you didn't create in this session
63
+
64
+ ```bash
65
+ #!/bin/bash
66
+
67
+ CHANNEL="1234567890123456789"
68
+
69
+ # Active threads in the channel
70
+ THREAD_ID=$(agent-discordbot thread list "$CHANNEL" | jq -r '.threads[] | select(.name=="Deployment Progress") | .id')
71
+
72
+ # Not active? Look through the 100 most recently archived threads (has_more flags older ones)
73
+ if [ -z "$THREAD_ID" ]; then
74
+ THREAD_ID=$(agent-discordbot thread list "$CHANNEL" --archived | jq -r '.threads[] | select(.name=="Deployment Progress") | .id')
75
+ fi
76
+
77
+ # Posting by ID unarchives the thread (unless it's locked). Names only resolve for active threads.
78
+ agent-discordbot message send "$CHANNEL" "Picking this back up" --thread "$THREAD_ID"
79
+ ```
80
+
81
+ **When to use**: Resuming work across sessions, replying in threads other people opened.
82
+
57
83
  ## Pattern 3: Monitor Channel for New Messages
58
84
 
59
85
  **Use case**: Poll a channel and respond to new messages
@@ -187,21 +213,23 @@ echo "Channels:"
187
213
  agent-discordbot channel list --pretty
188
214
 
189
215
  echo "Members:"
190
- agent-discordbot user list --pretty --limit 20
216
+ agent-discordbot user list --pretty
191
217
  ```
192
218
 
193
219
  ## Best Practices
194
220
 
195
- ### 1. Always Use Channel IDs
221
+ ### 1. Prefer Channel IDs, Names Work Too
196
222
 
197
- Discord requires Snowflake IDs (large numbers), not channel names:
223
+ Every `<channel>` argument accepts an exact channel name (`general` or `#general`), so `agent-discordbot message send general "Hello"` works. An ID skips the channel lookup on every call and can't hit a rename, so resolve once and reuse the ID:
198
224
 
199
225
  ```bash
200
226
  CHANNELS=$(agent-discordbot channel list)
201
- CHANNEL_ID=$(echo "$CHANNELS" | jq -r '.[] | select(.name=="general") | .id')
227
+ CHANNEL_ID=$(echo "$CHANNELS" | jq -r '.channels[] | select(.name=="general") | .id')
202
228
  agent-discordbot message send "$CHANNEL_ID" "Hello"
203
229
  ```
204
230
 
231
+ The same goes for `--thread`: a name resolves against the channel's active threads, an ID is used as-is.
232
+
205
233
  ### 2. Rate Limit Your Requests
206
234
 
207
235
  ```bash
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: agent-imessage
3
3
  description: Interact with iMessage on a Mac via the imsg tool - send messages, read chats, watch for new messages
4
- version: 2.38.0
4
+ version: 2.39.0
5
5
  allowed-tools: Bash(agent-imessage:*)
6
6
  metadata:
7
7
  openclaw:
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: agent-instagram
3
3
  description: Interact with Instagram DMs - send messages, read conversations, manage accounts
4
- version: 2.38.0
4
+ version: 2.39.0
5
5
  allowed-tools: Bash(agent-instagram:*)
6
6
  metadata:
7
7
  openclaw:
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: agent-kakaotalk
3
3
  description: Interact with KakaoTalk - send messages, read chats, manage conversations
4
- version: 2.38.0
4
+ version: 2.39.0
5
5
  allowed-tools: Bash(agent-kakaotalk:*)
6
6
  metadata:
7
7
  openclaw:
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: agent-line
3
3
  description: Interact with LINE - send messages, read chats, manage conversations
4
- version: 2.38.0
4
+ version: 2.39.0
5
5
  allowed-tools: Bash(agent-line:*)
6
6
  metadata:
7
7
  openclaw: