agent-messenger 2.26.0 → 2.27.1
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/README.md +16 -2
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +27 -0
- package/dist/package.json +1 -1
- package/dist/src/platforms/discord/listener.d.ts +9 -4
- package/dist/src/platforms/discord/listener.d.ts.map +1 -1
- package/dist/src/platforms/discord/listener.js +110 -16
- package/dist/src/platforms/discord/listener.js.map +1 -1
- package/dist/src/platforms/discordbot/listener.d.ts +6 -0
- package/dist/src/platforms/discordbot/listener.d.ts.map +1 -1
- package/dist/src/platforms/discordbot/listener.js +78 -2
- package/dist/src/platforms/discordbot/listener.js.map +1 -1
- package/dist/src/platforms/slack/qr-http-login.d.ts.map +1 -1
- package/dist/src/platforms/slack/qr-http-login.js +105 -6
- package/dist/src/platforms/slack/qr-http-login.js.map +1 -1
- package/docs/content/docs/sdk/discord.mdx +1 -9
- package/e2e/README.md +3 -1
- 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 +37 -8
- package/skills/agent-discord/references/authentication.md +46 -8
- package/skills/agent-discordbot/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 +7 -4
- package/skills/agent-slack/references/authentication.md +8 -3
- 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/discord/listener.test.ts +187 -147
- package/src/platforms/discord/listener.ts +122 -18
- package/src/platforms/discordbot/listener.test.ts +186 -184
- package/src/platforms/discordbot/listener.ts +83 -2
- package/src/platforms/slack/qr-http-login.test.ts +47 -0
- package/src/platforms/slack/qr-http-login.ts +112 -7
|
@@ -4,7 +4,7 @@ import WebSocket from 'ws'
|
|
|
4
4
|
|
|
5
5
|
import type { DiscordClient } from './client'
|
|
6
6
|
import type { DiscordListenerEventMap, DiscordGatewayGenericEvent } from './types'
|
|
7
|
-
import { DiscordGatewayOpcode
|
|
7
|
+
import { DiscordGatewayOpcode } from './types'
|
|
8
8
|
|
|
9
9
|
const GATEWAY_URL = 'wss://gateway.discord.gg/?v=10&encoding=json'
|
|
10
10
|
const GATEWAY_QUERY = '?v=10&encoding=json'
|
|
@@ -13,20 +13,35 @@ const RECONNECT_MAX_DELAY = 30_000
|
|
|
13
13
|
const NON_RECOVERABLE_CLOSE_CODES = [4004, 4010, 4011, 4012, 4013, 4014]
|
|
14
14
|
const SESSION_RESET_CLOSE_CODES = [4007, 4009]
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
16
|
+
// Hello arrives within milliseconds and READY within a few seconds on a healthy gateway;
|
|
17
|
+
// 15s is generous for a successful handshake while still failing fast on bad tokens,
|
|
18
|
+
// network stalls, or a gateway that accepts the socket but never delivers READY.
|
|
19
|
+
const DEFAULT_CONNECT_TIMEOUT_MS = 15_000
|
|
20
|
+
|
|
21
|
+
// User (non-bot) gateway capabilities bitmask, mirroring discord.py-self's default set.
|
|
22
|
+
// Capabilities shape the READY payload; bit 10 (client_state_v2) requires client_state.guild_versions.
|
|
23
|
+
const USER_GATEWAY_CAPABILITIES = 16381
|
|
24
|
+
|
|
25
|
+
// Without MESSAGE_CONTENT (1<<15), Discord blanks `content`/`embeds`/`attachments` on messages
|
|
26
|
+
// from OTHER users (self/DM/mention content still arrives). User sessions get all intents only
|
|
27
|
+
// when `intents` is omitted OR explicitly set, so we send an all-intents value to guarantee content.
|
|
28
|
+
const USER_GATEWAY_INTENTS = 33_554_431
|
|
29
|
+
|
|
30
|
+
// Discord validates client_build_number against recent web-client builds; a stale value can
|
|
31
|
+
// yield a connected-but-degraded session that fires READY yet delivers no message events.
|
|
32
|
+
// Overridable via env so it can be refreshed without a release when it eventually goes stale.
|
|
33
|
+
const DEFAULT_CLIENT_BUILD_NUMBER = 648814
|
|
34
|
+
const USER_GATEWAY_BUILD_NUMBER = Number(process.env.AGENT_DISCORD_BUILD_NUMBER) || DEFAULT_CLIENT_BUILD_NUMBER
|
|
24
35
|
|
|
25
36
|
type EventKey = keyof DiscordListenerEventMap
|
|
26
37
|
|
|
38
|
+
export interface DiscordListenerOptions {
|
|
39
|
+
connectTimeoutMs?: number
|
|
40
|
+
}
|
|
41
|
+
|
|
27
42
|
export class DiscordListener {
|
|
28
43
|
private client: DiscordClient
|
|
29
|
-
private
|
|
44
|
+
private connectTimeoutMs: number
|
|
30
45
|
private running = false
|
|
31
46
|
private ws: WebSocket | null = null
|
|
32
47
|
private emitter = new EventEmitter()
|
|
@@ -35,6 +50,7 @@ export class DiscordListener {
|
|
|
35
50
|
private heartbeatJitterTimer: ReturnType<typeof setTimeout> | null = null
|
|
36
51
|
private invalidSessionTimer: ReturnType<typeof setTimeout> | null = null
|
|
37
52
|
private reconnectTimer: ReturnType<typeof setTimeout> | null = null
|
|
53
|
+
private connectTimeoutTimer: ReturnType<typeof setTimeout> | null = null
|
|
38
54
|
private reconnectAttempts = 0
|
|
39
55
|
private sequence: number | null = null
|
|
40
56
|
private sessionId: string | null = null
|
|
@@ -42,24 +58,90 @@ export class DiscordListener {
|
|
|
42
58
|
private token: string | null = null
|
|
43
59
|
private cachedUser: { id: string; username: string } | null = null
|
|
44
60
|
private generation = 0
|
|
61
|
+
private startPromise: Promise<void> | null = null
|
|
62
|
+
private pendingStartReject: ((error: Error) => void) | null = null
|
|
45
63
|
|
|
46
|
-
constructor(client: DiscordClient, options?:
|
|
64
|
+
constructor(client: DiscordClient, options?: DiscordListenerOptions) {
|
|
47
65
|
this.client = client
|
|
48
|
-
this.
|
|
66
|
+
this.connectTimeoutMs = options?.connectTimeoutMs ?? DEFAULT_CONNECT_TIMEOUT_MS
|
|
49
67
|
}
|
|
50
68
|
|
|
51
69
|
async start(): Promise<void> {
|
|
70
|
+
if (this.startPromise) return this.startPromise
|
|
52
71
|
if (this.running) return
|
|
72
|
+
|
|
53
73
|
this.running = true
|
|
54
74
|
this.reconnectAttempts = 0
|
|
55
|
-
this.generation
|
|
56
|
-
|
|
75
|
+
const generation = ++this.generation
|
|
76
|
+
|
|
77
|
+
const ready = new Promise<void>((resolve, reject) => {
|
|
78
|
+
let settled = false
|
|
79
|
+
|
|
80
|
+
const cleanup = () => {
|
|
81
|
+
this.emitter.off('connected', onConnected)
|
|
82
|
+
this.emitter.off('error', onError)
|
|
83
|
+
this.pendingStartReject = null
|
|
84
|
+
if (this.connectTimeoutTimer) {
|
|
85
|
+
clearTimeout(this.connectTimeoutTimer)
|
|
86
|
+
this.connectTimeoutTimer = null
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const onConnected = () => {
|
|
91
|
+
if (settled || !this.isCurrent(generation)) return
|
|
92
|
+
settled = true
|
|
93
|
+
cleanup()
|
|
94
|
+
resolve()
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const onError = (error: Error) => {
|
|
98
|
+
if (settled || !this.isCurrent(generation)) return
|
|
99
|
+
settled = true
|
|
100
|
+
cleanup()
|
|
101
|
+
this.teardownFailedStart(generation)
|
|
102
|
+
reject(error)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
this.emitter.once('connected', onConnected)
|
|
106
|
+
this.emitter.once('error', onError)
|
|
107
|
+
|
|
108
|
+
// Generation-agnostic on purpose: stop() invokes this to reject an in-flight start
|
|
109
|
+
// (after bumping generation) without leaking the once() handlers.
|
|
110
|
+
this.pendingStartReject = (error: Error) => {
|
|
111
|
+
if (settled) return
|
|
112
|
+
settled = true
|
|
113
|
+
cleanup()
|
|
114
|
+
reject(error)
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
this.connectTimeoutTimer = setTimeout(() => {
|
|
118
|
+
onError(new Error(`Discord gateway did not become ready within ${this.connectTimeoutMs}ms`))
|
|
119
|
+
}, this.connectTimeoutMs)
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
const run = async (): Promise<void> => {
|
|
123
|
+
try {
|
|
124
|
+
await Promise.all([this.connect(generation), ready])
|
|
125
|
+
} finally {
|
|
126
|
+
if (this.generation === generation) this.startPromise = null
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
const startPromise = run()
|
|
130
|
+
this.startPromise = startPromise
|
|
131
|
+
return startPromise
|
|
57
132
|
}
|
|
58
133
|
|
|
59
134
|
stop(): void {
|
|
60
135
|
this.running = false
|
|
61
136
|
this.generation++
|
|
62
137
|
this.clearTimers()
|
|
138
|
+
if (this.connectTimeoutTimer) {
|
|
139
|
+
clearTimeout(this.connectTimeoutTimer)
|
|
140
|
+
this.connectTimeoutTimer = null
|
|
141
|
+
}
|
|
142
|
+
const rejectStart = this.pendingStartReject
|
|
143
|
+
this.pendingStartReject = null
|
|
144
|
+
this.startPromise = null
|
|
63
145
|
if (this.ws) {
|
|
64
146
|
this.ws.close()
|
|
65
147
|
this.ws = null
|
|
@@ -69,6 +151,12 @@ export class DiscordListener {
|
|
|
69
151
|
this.resumeGatewayUrl = null
|
|
70
152
|
this.token = null
|
|
71
153
|
this.cachedUser = null
|
|
154
|
+
rejectStart?.(new Error('Discord gateway start was stopped before becoming ready'))
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
private teardownFailedStart(generation: number): void {
|
|
158
|
+
if (!this.isCurrent(generation)) return
|
|
159
|
+
this.stop()
|
|
72
160
|
}
|
|
73
161
|
|
|
74
162
|
on<K extends EventKey>(event: K, listener: (...args: DiscordListenerEventMap[K]) => void): this {
|
|
@@ -233,12 +321,28 @@ export class DiscordListener {
|
|
|
233
321
|
op: DiscordGatewayOpcode.Identify,
|
|
234
322
|
d: {
|
|
235
323
|
token: this.token,
|
|
236
|
-
|
|
324
|
+
capabilities: USER_GATEWAY_CAPABILITIES,
|
|
325
|
+
intents: USER_GATEWAY_INTENTS,
|
|
237
326
|
properties: {
|
|
238
|
-
os: '
|
|
239
|
-
browser: '
|
|
240
|
-
device: '
|
|
327
|
+
os: 'Linux',
|
|
328
|
+
browser: 'Chrome',
|
|
329
|
+
device: '',
|
|
330
|
+
system_locale: 'en-US',
|
|
331
|
+
browser_user_agent:
|
|
332
|
+
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36',
|
|
333
|
+
browser_version: '131.0.0.0',
|
|
334
|
+
os_version: '',
|
|
335
|
+
referrer: '',
|
|
336
|
+
referring_domain: '',
|
|
337
|
+
referrer_current: '',
|
|
338
|
+
referring_domain_current: '',
|
|
339
|
+
release_channel: 'stable',
|
|
340
|
+
client_build_number: USER_GATEWAY_BUILD_NUMBER,
|
|
341
|
+
client_event_source: null,
|
|
241
342
|
},
|
|
343
|
+
presence: { status: 'online', since: 0, activities: [], afk: false },
|
|
344
|
+
compress: false,
|
|
345
|
+
client_state: { guild_versions: {} },
|
|
242
346
|
},
|
|
243
347
|
}),
|
|
244
348
|
)
|