agent-messenger 2.27.0 → 2.27.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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/dist/src/platforms/kakaotalk/client.d.ts.map +1 -1
- package/dist/src/platforms/kakaotalk/client.js +4 -4
- package/dist/src/platforms/kakaotalk/client.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
- package/src/platforms/kakaotalk/client.test.ts +40 -0
- package/src/platforms/kakaotalk/client.ts +6 -4
|
@@ -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
|
// User (non-bot) gateway capabilities bitmask, mirroring discord.py-self's default set.
|
|
17
22
|
// Capabilities shape the READY payload; bit 10 (client_state_v2) requires client_state.guild_versions.
|
|
18
23
|
const USER_GATEWAY_CAPABILITIES = 16381
|
|
@@ -30,8 +35,13 @@ const USER_GATEWAY_BUILD_NUMBER = Number(process.env.AGENT_DISCORD_BUILD_NUMBER)
|
|
|
30
35
|
|
|
31
36
|
type EventKey = keyof DiscordListenerEventMap
|
|
32
37
|
|
|
38
|
+
export interface DiscordListenerOptions {
|
|
39
|
+
connectTimeoutMs?: number
|
|
40
|
+
}
|
|
41
|
+
|
|
33
42
|
export class DiscordListener {
|
|
34
43
|
private client: DiscordClient
|
|
44
|
+
private connectTimeoutMs: number
|
|
35
45
|
private running = false
|
|
36
46
|
private ws: WebSocket | null = null
|
|
37
47
|
private emitter = new EventEmitter()
|
|
@@ -40,6 +50,7 @@ export class DiscordListener {
|
|
|
40
50
|
private heartbeatJitterTimer: ReturnType<typeof setTimeout> | null = null
|
|
41
51
|
private invalidSessionTimer: ReturnType<typeof setTimeout> | null = null
|
|
42
52
|
private reconnectTimer: ReturnType<typeof setTimeout> | null = null
|
|
53
|
+
private connectTimeoutTimer: ReturnType<typeof setTimeout> | null = null
|
|
43
54
|
private reconnectAttempts = 0
|
|
44
55
|
private sequence: number | null = null
|
|
45
56
|
private sessionId: string | null = null
|
|
@@ -47,23 +58,90 @@ export class DiscordListener {
|
|
|
47
58
|
private token: string | null = null
|
|
48
59
|
private cachedUser: { id: string; username: string } | null = null
|
|
49
60
|
private generation = 0
|
|
61
|
+
private startPromise: Promise<void> | null = null
|
|
62
|
+
private pendingStartReject: ((error: Error) => void) | null = null
|
|
50
63
|
|
|
51
|
-
constructor(client: DiscordClient) {
|
|
64
|
+
constructor(client: DiscordClient, options?: DiscordListenerOptions) {
|
|
52
65
|
this.client = client
|
|
66
|
+
this.connectTimeoutMs = options?.connectTimeoutMs ?? DEFAULT_CONNECT_TIMEOUT_MS
|
|
53
67
|
}
|
|
54
68
|
|
|
55
69
|
async start(): Promise<void> {
|
|
70
|
+
if (this.startPromise) return this.startPromise
|
|
56
71
|
if (this.running) return
|
|
72
|
+
|
|
57
73
|
this.running = true
|
|
58
74
|
this.reconnectAttempts = 0
|
|
59
|
-
this.generation
|
|
60
|
-
|
|
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
|
|
61
132
|
}
|
|
62
133
|
|
|
63
134
|
stop(): void {
|
|
64
135
|
this.running = false
|
|
65
136
|
this.generation++
|
|
66
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
|
|
67
145
|
if (this.ws) {
|
|
68
146
|
this.ws.close()
|
|
69
147
|
this.ws = null
|
|
@@ -73,6 +151,12 @@ export class DiscordListener {
|
|
|
73
151
|
this.resumeGatewayUrl = null
|
|
74
152
|
this.token = null
|
|
75
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()
|
|
76
160
|
}
|
|
77
161
|
|
|
78
162
|
on<K extends EventKey>(event: K, listener: (...args: DiscordListenerEventMap[K]) => void): this {
|