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.
- package/.claude-plugin/plugin.json +1 -1
- package/dist/package.json +1 -1
- package/dist/src/platforms/discordbot/client-message.d.ts +15 -0
- package/dist/src/platforms/discordbot/client-message.d.ts.map +1 -0
- package/dist/src/platforms/discordbot/client-message.js +70 -0
- package/dist/src/platforms/discordbot/client-message.js.map +1 -0
- package/dist/src/platforms/discordbot/client-threads.d.ts +10 -0
- package/dist/src/platforms/discordbot/client-threads.d.ts.map +1 -0
- package/dist/src/platforms/discordbot/client-threads.js +31 -0
- package/dist/src/platforms/discordbot/client-threads.js.map +1 -0
- package/dist/src/platforms/discordbot/client.d.ts +14 -2
- package/dist/src/platforms/discordbot/client.d.ts.map +1 -1
- package/dist/src/platforms/discordbot/client.js +21 -28
- package/dist/src/platforms/discordbot/client.js.map +1 -1
- package/dist/src/platforms/discordbot/commands/file.d.ts +14 -10
- package/dist/src/platforms/discordbot/commands/file.d.ts.map +1 -1
- package/dist/src/platforms/discordbot/commands/file.js +58 -24
- package/dist/src/platforms/discordbot/commands/file.js.map +1 -1
- package/dist/src/platforms/discordbot/commands/message.d.ts +3 -9
- package/dist/src/platforms/discordbot/commands/message.d.ts.map +1 -1
- package/dist/src/platforms/discordbot/commands/message.js +9 -51
- package/dist/src/platforms/discordbot/commands/message.js.map +1 -1
- package/dist/src/platforms/discordbot/commands/reaction.d.ts +9 -0
- package/dist/src/platforms/discordbot/commands/reaction.d.ts.map +1 -1
- package/dist/src/platforms/discordbot/commands/reaction.js +39 -0
- package/dist/src/platforms/discordbot/commands/reaction.js.map +1 -1
- package/dist/src/platforms/discordbot/commands/shared.d.ts +36 -0
- package/dist/src/platforms/discordbot/commands/shared.d.ts.map +1 -1
- package/dist/src/platforms/discordbot/commands/shared.js +36 -0
- package/dist/src/platforms/discordbot/commands/shared.js.map +1 -1
- package/dist/src/platforms/discordbot/commands/thread.d.ts +14 -0
- package/dist/src/platforms/discordbot/commands/thread.d.ts.map +1 -1
- package/dist/src/platforms/discordbot/commands/thread.js +40 -0
- package/dist/src/platforms/discordbot/commands/thread.js.map +1 -1
- package/dist/src/platforms/discordbot/index.d.ts +1 -1
- package/dist/src/platforms/discordbot/index.d.ts.map +1 -1
- package/dist/src/platforms/discordbot/index.js.map +1 -1
- package/dist/src/platforms/discordbot/types.d.ts +68 -17
- package/dist/src/platforms/discordbot/types.d.ts.map +1 -1
- package/dist/src/platforms/discordbot/types.js +20 -16
- package/dist/src/platforms/discordbot/types.js.map +1 -1
- package/dist/src/platforms/kakaotalk/client.d.ts.map +1 -1
- package/dist/src/platforms/kakaotalk/client.js +4 -32
- package/dist/src/platforms/kakaotalk/client.js.map +1 -1
- package/dist/src/platforms/kakaotalk/sync-state-store.d.ts +11 -0
- package/dist/src/platforms/kakaotalk/sync-state-store.d.ts.map +1 -0
- package/dist/src/platforms/kakaotalk/sync-state-store.js +89 -0
- package/dist/src/platforms/kakaotalk/sync-state-store.js.map +1 -0
- package/dist/src/platforms/slack/cli.d.ts.map +1 -1
- package/dist/src/platforms/slack/cli.js +5 -0
- package/dist/src/platforms/slack/cli.js.map +1 -1
- package/dist/src/platforms/slack/commands/auth.d.ts.map +1 -1
- package/dist/src/platforms/slack/commands/auth.js +9 -3
- package/dist/src/platforms/slack/commands/auth.js.map +1 -1
- package/dist/src/platforms/slack/credential-manager.d.ts +2 -0
- package/dist/src/platforms/slack/credential-manager.d.ts.map +1 -1
- package/dist/src/platforms/slack/credential-manager.js +17 -3
- package/dist/src/platforms/slack/credential-manager.js.map +1 -1
- package/docs/content/docs/cli/discordbot.mdx +60 -27
- package/docs/content/docs/cli/slack.mdx +8 -1
- package/docs/content/docs/sdk/discordbot.mdx +101 -3
- package/e2e/README.md +3 -1
- package/e2e/config.ts +1 -1
- package/e2e/discordbot.e2e.test.ts +206 -11
- package/package.json +1 -1
- package/skills/agent-channeltalk/SKILL.md +1 -1
- package/skills/agent-channeltalkbot/SKILL.md +1 -1
- package/skills/agent-discord/SKILL.md +1 -1
- package/skills/agent-discordbot/SKILL.md +57 -11
- package/skills/agent-discordbot/references/authentication.md +13 -11
- package/skills/agent-discordbot/references/common-patterns.md +36 -8
- package/skills/agent-imessage/SKILL.md +1 -1
- package/skills/agent-instagram/SKILL.md +1 -1
- package/skills/agent-kakaotalk/SKILL.md +1 -1
- package/skills/agent-line/SKILL.md +1 -1
- package/skills/agent-slack/SKILL.md +9 -2
- package/skills/agent-slack/references/authentication.md +12 -0
- package/skills/agent-slack/references/common-patterns.md +18 -0
- package/skills/agent-slackbot/SKILL.md +1 -1
- package/skills/agent-teams/SKILL.md +1 -1
- package/skills/agent-telegram/SKILL.md +1 -1
- package/skills/agent-telegrambot/SKILL.md +1 -1
- package/skills/agent-webex/SKILL.md +1 -1
- package/skills/agent-webexbot/SKILL.md +1 -1
- package/skills/agent-wechatbot/SKILL.md +1 -1
- package/skills/agent-whatsapp/SKILL.md +1 -1
- package/skills/agent-whatsappbot/SKILL.md +1 -1
- package/src/platforms/discordbot/client-message.ts +91 -0
- package/src/platforms/discordbot/client-threads.test.ts +177 -0
- package/src/platforms/discordbot/client-threads.ts +69 -0
- package/src/platforms/discordbot/client.test.ts +277 -1
- package/src/platforms/discordbot/client.ts +60 -40
- package/src/platforms/discordbot/commands/file.test.ts +242 -90
- package/src/platforms/discordbot/commands/file.ts +78 -36
- package/src/platforms/discordbot/commands/message.test.ts +183 -26
- package/src/platforms/discordbot/commands/message.ts +13 -61
- package/src/platforms/discordbot/commands/reaction.test.ts +73 -1
- package/src/platforms/discordbot/commands/reaction.ts +46 -0
- package/src/platforms/discordbot/commands/shared.test.ts +98 -0
- package/src/platforms/discordbot/commands/shared.ts +67 -0
- package/src/platforms/discordbot/commands/thread.test.ts +58 -1
- package/src/platforms/discordbot/commands/thread.ts +58 -0
- package/src/platforms/discordbot/index.ts +3 -0
- package/src/platforms/discordbot/types.test.ts +46 -0
- package/src/platforms/discordbot/types.ts +44 -19
- package/src/platforms/kakaotalk/client-listener-integration.test.ts +21 -3
- package/src/platforms/kakaotalk/client.test.ts +42 -4
- package/src/platforms/kakaotalk/client.ts +4 -39
- package/src/platforms/kakaotalk/sync-state-store.test.ts +197 -0
- package/src/platforms/kakaotalk/sync-state-store.ts +99 -0
- package/src/platforms/slack/cli.test.ts +111 -1
- package/src/platforms/slack/cli.ts +5 -0
- package/src/platforms/slack/commands/auth.ts +14 -3
- package/src/platforms/slack/credential-manager.ts +21 -3
- 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
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
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
|
-
|
|
214
|
+
threadId,
|
|
180
215
|
])
|
|
181
216
|
expect(replyResult.exitCode).toBe(0)
|
|
182
217
|
|
|
183
|
-
const reply = parseJSON<{ id: string;
|
|
218
|
+
const reply = parseJSON<{ id: string; channel_id: string }>(replyResult.stdout)
|
|
184
219
|
expect(reply?.id).toBeTruthy()
|
|
185
|
-
|
|
186
|
-
|
|
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
|
-
},
|
|
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.
|
|
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.
|
|
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-discordbot
|
|
3
3
|
description: Interact with Discord servers using bot tokens - send messages, read channels, manage reactions
|
|
4
|
-
version: 2.
|
|
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** —
|
|
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
|
|
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
|
|
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
|
|
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
|
-
#
|
|
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
|
|
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
|
|
106
|
-
agent-discordbot auth set alert-bot-token --bot alert
|
|
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
|
|
175
|
-
|
|
|
176
|
-
| Send Messages
|
|
177
|
-
|
|
|
178
|
-
|
|
|
179
|
-
|
|
|
180
|
-
|
|
|
181
|
-
|
|
|
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 "$
|
|
45
|
+
agent-discordbot message send "$CHANNEL" "Running tests..." --thread "$THREAD_ID"
|
|
46
46
|
sleep 2
|
|
47
|
-
agent-discordbot message send "$
|
|
47
|
+
agent-discordbot message send "$CHANNEL" "Deploying to production..." --thread "Deployment Progress"
|
|
48
48
|
sleep 2
|
|
49
|
-
|
|
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
|
|
216
|
+
agent-discordbot user list --pretty
|
|
191
217
|
```
|
|
192
218
|
|
|
193
219
|
## Best Practices
|
|
194
220
|
|
|
195
|
-
### 1.
|
|
221
|
+
### 1. Prefer Channel IDs, Names Work Too
|
|
196
222
|
|
|
197
|
-
|
|
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
|