agent-messenger 2.27.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/plugin.json +1 -1
- package/dist/package.json +1 -1
- package/dist/src/platforms/discord/listener.d.ts +9 -1
- package/dist/src/platforms/discord/listener.d.ts.map +1 -1
- package/dist/src/platforms/discord/listener.js +79 -3
- 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/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 +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 +1 -1
- 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 +175 -143
- package/src/platforms/discord/listener.ts +87 -3
- package/src/platforms/discordbot/listener.test.ts +186 -184
- package/src/platforms/discordbot/listener.ts +83 -2
|
@@ -13,6 +13,11 @@ 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
|
+
// 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
|
+
|
|
16
21
|
const DEFAULT_INTENTS =
|
|
17
22
|
DiscordIntent.Guilds |
|
|
18
23
|
DiscordIntent.GuildMessages |
|
|
@@ -26,11 +31,13 @@ type EventKey = keyof DiscordBotListenerEventMap
|
|
|
26
31
|
|
|
27
32
|
export interface DiscordBotListenerOptions {
|
|
28
33
|
intents?: number
|
|
34
|
+
connectTimeoutMs?: number
|
|
29
35
|
}
|
|
30
36
|
|
|
31
37
|
export class DiscordBotListener {
|
|
32
38
|
private client: DiscordBotClient
|
|
33
39
|
private intents: number
|
|
40
|
+
private connectTimeoutMs: number
|
|
34
41
|
private running = false
|
|
35
42
|
private ws: WebSocket | null = null
|
|
36
43
|
private emitter = new EventEmitter()
|
|
@@ -39,6 +46,7 @@ export class DiscordBotListener {
|
|
|
39
46
|
private heartbeatJitterTimer: ReturnType<typeof setTimeout> | null = null
|
|
40
47
|
private invalidSessionTimer: ReturnType<typeof setTimeout> | null = null
|
|
41
48
|
private reconnectTimer: ReturnType<typeof setTimeout> | null = null
|
|
49
|
+
private connectTimeoutTimer: ReturnType<typeof setTimeout> | null = null
|
|
42
50
|
private reconnectAttempts = 0
|
|
43
51
|
private sequence: number | null = null
|
|
44
52
|
private sessionId: string | null = null
|
|
@@ -46,24 +54,91 @@ export class DiscordBotListener {
|
|
|
46
54
|
private token: string | null = null
|
|
47
55
|
private cachedUser: { id: string; username: string } | null = null
|
|
48
56
|
private generation = 0
|
|
57
|
+
private startPromise: Promise<void> | null = null
|
|
58
|
+
private pendingStartReject: ((error: Error) => void) | null = null
|
|
49
59
|
|
|
50
60
|
constructor(client: DiscordBotClient, options?: DiscordBotListenerOptions) {
|
|
51
61
|
this.client = client
|
|
52
62
|
this.intents = options?.intents ?? DEFAULT_INTENTS
|
|
63
|
+
this.connectTimeoutMs = options?.connectTimeoutMs ?? DEFAULT_CONNECT_TIMEOUT_MS
|
|
53
64
|
}
|
|
54
65
|
|
|
55
66
|
async start(): Promise<void> {
|
|
67
|
+
if (this.startPromise) return this.startPromise
|
|
56
68
|
if (this.running) return
|
|
69
|
+
|
|
57
70
|
this.running = true
|
|
58
71
|
this.reconnectAttempts = 0
|
|
59
|
-
this.generation
|
|
60
|
-
|
|
72
|
+
const generation = ++this.generation
|
|
73
|
+
|
|
74
|
+
const ready = new Promise<void>((resolve, reject) => {
|
|
75
|
+
let settled = false
|
|
76
|
+
|
|
77
|
+
const cleanup = () => {
|
|
78
|
+
this.emitter.off('connected', onConnected)
|
|
79
|
+
this.emitter.off('error', onError)
|
|
80
|
+
this.pendingStartReject = null
|
|
81
|
+
if (this.connectTimeoutTimer) {
|
|
82
|
+
clearTimeout(this.connectTimeoutTimer)
|
|
83
|
+
this.connectTimeoutTimer = null
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const onConnected = () => {
|
|
88
|
+
if (settled || !this.isCurrent(generation)) return
|
|
89
|
+
settled = true
|
|
90
|
+
cleanup()
|
|
91
|
+
resolve()
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const onError = (error: Error) => {
|
|
95
|
+
if (settled || !this.isCurrent(generation)) return
|
|
96
|
+
settled = true
|
|
97
|
+
cleanup()
|
|
98
|
+
this.teardownFailedStart(generation)
|
|
99
|
+
reject(error)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
this.emitter.once('connected', onConnected)
|
|
103
|
+
this.emitter.once('error', onError)
|
|
104
|
+
|
|
105
|
+
// Generation-agnostic on purpose: stop() invokes this to reject an in-flight start
|
|
106
|
+
// (after bumping generation) without leaking the once() handlers.
|
|
107
|
+
this.pendingStartReject = (error: Error) => {
|
|
108
|
+
if (settled) return
|
|
109
|
+
settled = true
|
|
110
|
+
cleanup()
|
|
111
|
+
reject(error)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
this.connectTimeoutTimer = setTimeout(() => {
|
|
115
|
+
onError(new Error(`Discord gateway did not become ready within ${this.connectTimeoutMs}ms`))
|
|
116
|
+
}, this.connectTimeoutMs)
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
const run = async (): Promise<void> => {
|
|
120
|
+
try {
|
|
121
|
+
await Promise.all([this.connect(generation), ready])
|
|
122
|
+
} finally {
|
|
123
|
+
if (this.generation === generation) this.startPromise = null
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
const startPromise = run()
|
|
127
|
+
this.startPromise = startPromise
|
|
128
|
+
return startPromise
|
|
61
129
|
}
|
|
62
130
|
|
|
63
131
|
stop(): void {
|
|
64
132
|
this.running = false
|
|
65
133
|
this.generation++
|
|
66
134
|
this.clearTimers()
|
|
135
|
+
if (this.connectTimeoutTimer) {
|
|
136
|
+
clearTimeout(this.connectTimeoutTimer)
|
|
137
|
+
this.connectTimeoutTimer = null
|
|
138
|
+
}
|
|
139
|
+
const rejectStart = this.pendingStartReject
|
|
140
|
+
this.pendingStartReject = null
|
|
141
|
+
this.startPromise = null
|
|
67
142
|
if (this.ws) {
|
|
68
143
|
this.ws.close()
|
|
69
144
|
this.ws = null
|
|
@@ -73,6 +148,12 @@ export class DiscordBotListener {
|
|
|
73
148
|
this.resumeGatewayUrl = null
|
|
74
149
|
this.token = null
|
|
75
150
|
this.cachedUser = null
|
|
151
|
+
rejectStart?.(new Error('Discord gateway start was stopped before becoming ready'))
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
private teardownFailedStart(generation: number): void {
|
|
155
|
+
if (!this.isCurrent(generation)) return
|
|
156
|
+
this.stop()
|
|
76
157
|
}
|
|
77
158
|
|
|
78
159
|
on<K extends EventKey>(event: K, listener: (...args: DiscordBotListenerEventMap[K]) => void): this {
|