kimaki 0.4.12 → 0.4.13
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/dist/discordBot.js +81 -59
- package/package.json +1 -1
- package/src/discordBot.ts +101 -74
package/dist/discordBot.js
CHANGED
|
@@ -129,7 +129,7 @@ async function createUserAudioLogStream(guildId, channelId) {
|
|
|
129
129
|
}
|
|
130
130
|
}
|
|
131
131
|
// Set up voice handling for a connection (called once per connection)
|
|
132
|
-
async function setupVoiceHandling({ connection, guildId, channelId, appId, }) {
|
|
132
|
+
async function setupVoiceHandling({ connection, guildId, channelId, appId, discordClient, }) {
|
|
133
133
|
voiceLogger.log(`Setting up voice handling for guild ${guildId}, channel ${channelId}`);
|
|
134
134
|
// Check if this voice channel has an associated directory
|
|
135
135
|
const channelDirRow = getDatabase()
|
|
@@ -227,8 +227,24 @@ async function setupVoiceHandling({ connection, guildId, channelId, appId, }) {
|
|
|
227
227
|
: `<systemMessage>\nThe coding agent finished working on session ${params.sessionId}\n\nHere's what the assistant wrote:\n${params.markdown}\n</systemMessage>`;
|
|
228
228
|
genAiWorker.sendTextInput(text);
|
|
229
229
|
},
|
|
230
|
-
onError(error) {
|
|
230
|
+
async onError(error) {
|
|
231
231
|
voiceLogger.error('GenAI worker error:', error);
|
|
232
|
+
const textChannelRow = getDatabase()
|
|
233
|
+
.prepare(`SELECT cd2.channel_id FROM channel_directories cd1
|
|
234
|
+
JOIN channel_directories cd2 ON cd1.directory = cd2.directory
|
|
235
|
+
WHERE cd1.channel_id = ? AND cd1.channel_type = 'voice' AND cd2.channel_type = 'text'`)
|
|
236
|
+
.get(channelId);
|
|
237
|
+
if (textChannelRow) {
|
|
238
|
+
try {
|
|
239
|
+
const textChannel = await discordClient.channels.fetch(textChannelRow.channel_id);
|
|
240
|
+
if (textChannel?.isTextBased() && 'send' in textChannel) {
|
|
241
|
+
await textChannel.send(`⚠️ Voice session error: ${error}`);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
catch (e) {
|
|
245
|
+
voiceLogger.error('Failed to send error to text channel:', e);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
232
248
|
},
|
|
233
249
|
});
|
|
234
250
|
// Stop any existing GenAI worker before storing new one
|
|
@@ -912,24 +928,27 @@ function getToolOutputToDisplay(part) {
|
|
|
912
928
|
if (part.state.status === 'error') {
|
|
913
929
|
return part.state.error || 'Unknown error';
|
|
914
930
|
}
|
|
915
|
-
if (part.tool === 'todowrite') {
|
|
916
|
-
const todos = part.state.input?.todos || [];
|
|
917
|
-
return todos
|
|
918
|
-
.map((todo) => {
|
|
919
|
-
let statusIcon = '▢';
|
|
920
|
-
if (todo.status === 'in_progress') {
|
|
921
|
-
statusIcon = '●';
|
|
922
|
-
}
|
|
923
|
-
if (todo.status === 'completed' || todo.status === 'cancelled') {
|
|
924
|
-
statusIcon = '■';
|
|
925
|
-
}
|
|
926
|
-
return `\`${statusIcon}\` ${todo.content}`;
|
|
927
|
-
})
|
|
928
|
-
.filter(Boolean)
|
|
929
|
-
.join('\n');
|
|
930
|
-
}
|
|
931
931
|
return '';
|
|
932
932
|
}
|
|
933
|
+
function formatTodoList(part) {
|
|
934
|
+
if (part.type !== 'tool' || part.tool !== 'todowrite')
|
|
935
|
+
return '';
|
|
936
|
+
const todos = part.state.input?.todos || [];
|
|
937
|
+
if (todos.length === 0)
|
|
938
|
+
return '';
|
|
939
|
+
return todos
|
|
940
|
+
.map((todo, i) => {
|
|
941
|
+
const num = `${i + 1}.`;
|
|
942
|
+
if (todo.status === 'in_progress') {
|
|
943
|
+
return `${num} **${todo.content}**`;
|
|
944
|
+
}
|
|
945
|
+
if (todo.status === 'completed' || todo.status === 'cancelled') {
|
|
946
|
+
return `${num} ~~${todo.content}~~`;
|
|
947
|
+
}
|
|
948
|
+
return `${num} ${todo.content}`;
|
|
949
|
+
})
|
|
950
|
+
.join('\n');
|
|
951
|
+
}
|
|
933
952
|
function formatPart(part) {
|
|
934
953
|
if (part.type === 'text') {
|
|
935
954
|
return part.text || '';
|
|
@@ -952,14 +971,31 @@ function formatPart(part) {
|
|
|
952
971
|
return `◼︎ snapshot ${part.snapshot}`;
|
|
953
972
|
}
|
|
954
973
|
if (part.type === 'tool') {
|
|
974
|
+
if (part.tool === 'todowrite') {
|
|
975
|
+
return formatTodoList(part);
|
|
976
|
+
}
|
|
955
977
|
if (part.state.status !== 'completed' && part.state.status !== 'error') {
|
|
956
978
|
return '';
|
|
957
979
|
}
|
|
958
980
|
const summaryText = getToolSummaryText(part);
|
|
959
981
|
const outputToDisplay = getToolOutputToDisplay(part);
|
|
960
|
-
let toolTitle =
|
|
961
|
-
if (
|
|
962
|
-
toolTitle =
|
|
982
|
+
let toolTitle = '';
|
|
983
|
+
if (part.state.status === 'error') {
|
|
984
|
+
toolTitle = 'error';
|
|
985
|
+
}
|
|
986
|
+
else if (part.tool === 'bash') {
|
|
987
|
+
const command = part.state.input?.command || '';
|
|
988
|
+
const isSingleLine = !command.includes('\n');
|
|
989
|
+
const hasBackticks = command.includes('`');
|
|
990
|
+
if (isSingleLine && command.length <= 120 && !hasBackticks) {
|
|
991
|
+
toolTitle = `\`${command}\``;
|
|
992
|
+
}
|
|
993
|
+
else {
|
|
994
|
+
toolTitle = part.state.title ? `*${part.state.title}*` : '';
|
|
995
|
+
}
|
|
996
|
+
}
|
|
997
|
+
else if (part.state.title) {
|
|
998
|
+
toolTitle = `*${part.state.title}*`;
|
|
963
999
|
}
|
|
964
1000
|
const icon = part.state.status === 'completed' ? '◼︎' : part.state.status === 'error' ? '⨯' : '';
|
|
965
1001
|
const title = `${icon} ${part.tool} ${toolTitle} ${summaryText}`;
|
|
@@ -1937,52 +1973,37 @@ export async function startDiscordBot({ token, appId, discordClient, }) {
|
|
|
1937
1973
|
await command.editReply(`Resumed session "${sessionTitle}" in ${thread.toString()}`);
|
|
1938
1974
|
// Send initial message to thread
|
|
1939
1975
|
await sendThreadMessage(thread, `📂 **Resumed session:** ${sessionTitle}\n📅 **Created:** ${new Date(sessionResponse.data.time.created).toLocaleString()}\n\n*Loading ${messages.length} messages...*`);
|
|
1940
|
-
//
|
|
1941
|
-
|
|
1976
|
+
// Collect all assistant parts first, then only render the last 30
|
|
1977
|
+
const allAssistantParts = [];
|
|
1942
1978
|
for (const message of messages) {
|
|
1943
|
-
if (message.info.role === '
|
|
1944
|
-
// Render user messages
|
|
1945
|
-
const userParts = message.parts.filter((p) => p.type === 'text' && !p.synthetic);
|
|
1946
|
-
const userTexts = userParts
|
|
1947
|
-
.map((p) => {
|
|
1948
|
-
if (p.type === 'text') {
|
|
1949
|
-
return p.text;
|
|
1950
|
-
}
|
|
1951
|
-
return '';
|
|
1952
|
-
})
|
|
1953
|
-
.filter((t) => t.trim());
|
|
1954
|
-
const userText = userTexts.join('\n\n');
|
|
1955
|
-
if (userText) {
|
|
1956
|
-
// Escape backticks in user messages to prevent formatting issues
|
|
1957
|
-
const escapedText = escapeDiscordFormatting(userText);
|
|
1958
|
-
await sendThreadMessage(thread, `**User:**\n${escapedText}`);
|
|
1959
|
-
}
|
|
1960
|
-
}
|
|
1961
|
-
else if (message.info.role === 'assistant') {
|
|
1962
|
-
// Render assistant parts
|
|
1963
|
-
const partsToRender = [];
|
|
1979
|
+
if (message.info.role === 'assistant') {
|
|
1964
1980
|
for (const part of message.parts) {
|
|
1965
1981
|
const content = formatPart(part);
|
|
1966
1982
|
if (content.trim()) {
|
|
1967
|
-
|
|
1983
|
+
allAssistantParts.push({ id: part.id, content });
|
|
1968
1984
|
}
|
|
1969
1985
|
}
|
|
1970
|
-
if (partsToRender.length > 0) {
|
|
1971
|
-
const combinedContent = partsToRender
|
|
1972
|
-
.map((p) => p.content)
|
|
1973
|
-
.join('\n\n');
|
|
1974
|
-
const discordMessage = await sendThreadMessage(thread, combinedContent);
|
|
1975
|
-
const stmt = getDatabase().prepare('INSERT OR REPLACE INTO part_messages (part_id, message_id, thread_id) VALUES (?, ?, ?)');
|
|
1976
|
-
const transaction = getDatabase().transaction((parts) => {
|
|
1977
|
-
for (const part of parts) {
|
|
1978
|
-
stmt.run(part.id, discordMessage.id, thread.id);
|
|
1979
|
-
}
|
|
1980
|
-
});
|
|
1981
|
-
transaction(partsToRender);
|
|
1982
|
-
}
|
|
1983
1986
|
}
|
|
1984
|
-
messageCount++;
|
|
1985
1987
|
}
|
|
1988
|
+
const partsToRender = allAssistantParts.slice(-30);
|
|
1989
|
+
const skippedCount = allAssistantParts.length - partsToRender.length;
|
|
1990
|
+
if (skippedCount > 0) {
|
|
1991
|
+
await sendThreadMessage(thread, `*Skipped ${skippedCount} older assistant parts...*`);
|
|
1992
|
+
}
|
|
1993
|
+
if (partsToRender.length > 0) {
|
|
1994
|
+
const combinedContent = partsToRender
|
|
1995
|
+
.map((p) => p.content)
|
|
1996
|
+
.join('\n\n');
|
|
1997
|
+
const discordMessage = await sendThreadMessage(thread, combinedContent);
|
|
1998
|
+
const stmt = getDatabase().prepare('INSERT OR REPLACE INTO part_messages (part_id, message_id, thread_id) VALUES (?, ?, ?)');
|
|
1999
|
+
const transaction = getDatabase().transaction((parts) => {
|
|
2000
|
+
for (const part of parts) {
|
|
2001
|
+
stmt.run(part.id, discordMessage.id, thread.id);
|
|
2002
|
+
}
|
|
2003
|
+
});
|
|
2004
|
+
transaction(partsToRender);
|
|
2005
|
+
}
|
|
2006
|
+
const messageCount = messages.length;
|
|
1986
2007
|
await sendThreadMessage(thread, `✅ **Session resumed!** Loaded ${messageCount} messages.\n\nYou can now continue the conversation by sending messages in this thread.`);
|
|
1987
2008
|
}
|
|
1988
2009
|
catch (error) {
|
|
@@ -2321,6 +2342,7 @@ export async function startDiscordBot({ token, appId, discordClient, }) {
|
|
|
2321
2342
|
guildId: newState.guild.id,
|
|
2322
2343
|
channelId: voiceChannel.id,
|
|
2323
2344
|
appId: currentAppId,
|
|
2345
|
+
discordClient,
|
|
2324
2346
|
});
|
|
2325
2347
|
// Handle connection state changes
|
|
2326
2348
|
connection.on(VoiceConnectionStatus.Disconnected, async () => {
|
package/package.json
CHANGED
package/src/discordBot.ts
CHANGED
|
@@ -213,11 +213,13 @@ async function setupVoiceHandling({
|
|
|
213
213
|
guildId,
|
|
214
214
|
channelId,
|
|
215
215
|
appId,
|
|
216
|
+
discordClient,
|
|
216
217
|
}: {
|
|
217
218
|
connection: VoiceConnection
|
|
218
219
|
guildId: string
|
|
219
220
|
channelId: string
|
|
220
221
|
appId: string
|
|
222
|
+
discordClient: Client
|
|
221
223
|
}) {
|
|
222
224
|
voiceLogger.log(
|
|
223
225
|
`Setting up voice handling for guild ${guildId}, channel ${channelId}`,
|
|
@@ -330,8 +332,28 @@ async function setupVoiceHandling({
|
|
|
330
332
|
|
|
331
333
|
genAiWorker.sendTextInput(text)
|
|
332
334
|
},
|
|
333
|
-
onError(error) {
|
|
335
|
+
async onError(error) {
|
|
334
336
|
voiceLogger.error('GenAI worker error:', error)
|
|
337
|
+
const textChannelRow = getDatabase()
|
|
338
|
+
.prepare(
|
|
339
|
+
`SELECT cd2.channel_id FROM channel_directories cd1
|
|
340
|
+
JOIN channel_directories cd2 ON cd1.directory = cd2.directory
|
|
341
|
+
WHERE cd1.channel_id = ? AND cd1.channel_type = 'voice' AND cd2.channel_type = 'text'`,
|
|
342
|
+
)
|
|
343
|
+
.get(channelId) as { channel_id: string } | undefined
|
|
344
|
+
|
|
345
|
+
if (textChannelRow) {
|
|
346
|
+
try {
|
|
347
|
+
const textChannel = await discordClient.channels.fetch(
|
|
348
|
+
textChannelRow.channel_id,
|
|
349
|
+
)
|
|
350
|
+
if (textChannel?.isTextBased() && 'send' in textChannel) {
|
|
351
|
+
await textChannel.send(`⚠️ Voice session error: ${error}`)
|
|
352
|
+
}
|
|
353
|
+
} catch (e) {
|
|
354
|
+
voiceLogger.error('Failed to send error to text channel:', e)
|
|
355
|
+
}
|
|
356
|
+
}
|
|
335
357
|
},
|
|
336
358
|
})
|
|
337
359
|
|
|
@@ -1212,30 +1234,31 @@ function getToolOutputToDisplay(part: Part): string {
|
|
|
1212
1234
|
return part.state.error || 'Unknown error'
|
|
1213
1235
|
}
|
|
1214
1236
|
|
|
1215
|
-
if (part.tool === 'todowrite') {
|
|
1216
|
-
const todos =
|
|
1217
|
-
(part.state.input?.todos as {
|
|
1218
|
-
content: string
|
|
1219
|
-
status: 'pending' | 'in_progress' | 'completed' | 'cancelled'
|
|
1220
|
-
}[]) || []
|
|
1221
|
-
return todos
|
|
1222
|
-
.map((todo) => {
|
|
1223
|
-
let statusIcon = '▢'
|
|
1224
|
-
if (todo.status === 'in_progress') {
|
|
1225
|
-
statusIcon = '●'
|
|
1226
|
-
}
|
|
1227
|
-
if (todo.status === 'completed' || todo.status === 'cancelled') {
|
|
1228
|
-
statusIcon = '■'
|
|
1229
|
-
}
|
|
1230
|
-
return `\`${statusIcon}\` ${todo.content}`
|
|
1231
|
-
})
|
|
1232
|
-
.filter(Boolean)
|
|
1233
|
-
.join('\n')
|
|
1234
|
-
}
|
|
1235
|
-
|
|
1236
1237
|
return ''
|
|
1237
1238
|
}
|
|
1238
1239
|
|
|
1240
|
+
function formatTodoList(part: Part): string {
|
|
1241
|
+
if (part.type !== 'tool' || part.tool !== 'todowrite') return ''
|
|
1242
|
+
const todos =
|
|
1243
|
+
(part.state.input?.todos as {
|
|
1244
|
+
content: string
|
|
1245
|
+
status: 'pending' | 'in_progress' | 'completed' | 'cancelled'
|
|
1246
|
+
}[]) || []
|
|
1247
|
+
if (todos.length === 0) return ''
|
|
1248
|
+
return todos
|
|
1249
|
+
.map((todo, i) => {
|
|
1250
|
+
const num = `${i + 1}.`
|
|
1251
|
+
if (todo.status === 'in_progress') {
|
|
1252
|
+
return `${num} **${todo.content}**`
|
|
1253
|
+
}
|
|
1254
|
+
if (todo.status === 'completed' || todo.status === 'cancelled') {
|
|
1255
|
+
return `${num} ~~${todo.content}~~`
|
|
1256
|
+
}
|
|
1257
|
+
return `${num} ${todo.content}`
|
|
1258
|
+
})
|
|
1259
|
+
.join('\n')
|
|
1260
|
+
}
|
|
1261
|
+
|
|
1239
1262
|
function formatPart(part: Part): string {
|
|
1240
1263
|
if (part.type === 'text') {
|
|
1241
1264
|
return part.text || ''
|
|
@@ -1263,6 +1286,10 @@ function formatPart(part: Part): string {
|
|
|
1263
1286
|
}
|
|
1264
1287
|
|
|
1265
1288
|
if (part.type === 'tool') {
|
|
1289
|
+
if (part.tool === 'todowrite') {
|
|
1290
|
+
return formatTodoList(part)
|
|
1291
|
+
}
|
|
1292
|
+
|
|
1266
1293
|
if (part.state.status !== 'completed' && part.state.status !== 'error') {
|
|
1267
1294
|
return ''
|
|
1268
1295
|
}
|
|
@@ -1270,9 +1297,20 @@ function formatPart(part: Part): string {
|
|
|
1270
1297
|
const summaryText = getToolSummaryText(part)
|
|
1271
1298
|
const outputToDisplay = getToolOutputToDisplay(part)
|
|
1272
1299
|
|
|
1273
|
-
let toolTitle =
|
|
1274
|
-
if (
|
|
1275
|
-
toolTitle =
|
|
1300
|
+
let toolTitle = ''
|
|
1301
|
+
if (part.state.status === 'error') {
|
|
1302
|
+
toolTitle = 'error'
|
|
1303
|
+
} else if (part.tool === 'bash') {
|
|
1304
|
+
const command = (part.state.input?.command as string) || ''
|
|
1305
|
+
const isSingleLine = !command.includes('\n')
|
|
1306
|
+
const hasBackticks = command.includes('`')
|
|
1307
|
+
if (isSingleLine && command.length <= 120 && !hasBackticks) {
|
|
1308
|
+
toolTitle = `\`${command}\``
|
|
1309
|
+
} else {
|
|
1310
|
+
toolTitle = part.state.title ? `*${part.state.title}*` : ''
|
|
1311
|
+
}
|
|
1312
|
+
} else if (part.state.title) {
|
|
1313
|
+
toolTitle = `*${part.state.title}*`
|
|
1276
1314
|
}
|
|
1277
1315
|
|
|
1278
1316
|
const icon = part.state.status === 'completed' ? '◼︎' : part.state.status === 'error' ? '⨯' : ''
|
|
@@ -2600,68 +2638,56 @@ export async function startDiscordBot({
|
|
|
2600
2638
|
`📂 **Resumed session:** ${sessionTitle}\n📅 **Created:** ${new Date(sessionResponse.data.time.created).toLocaleString()}\n\n*Loading ${messages.length} messages...*`,
|
|
2601
2639
|
)
|
|
2602
2640
|
|
|
2603
|
-
//
|
|
2604
|
-
|
|
2641
|
+
// Collect all assistant parts first, then only render the last 30
|
|
2642
|
+
const allAssistantParts: { id: string; content: string }[] = []
|
|
2605
2643
|
for (const message of messages) {
|
|
2606
|
-
if (message.info.role === '
|
|
2607
|
-
// Render user messages
|
|
2608
|
-
const userParts = message.parts.filter(
|
|
2609
|
-
(p) => p.type === 'text' && !p.synthetic,
|
|
2610
|
-
)
|
|
2611
|
-
const userTexts = userParts
|
|
2612
|
-
.map((p) => {
|
|
2613
|
-
if (p.type === 'text') {
|
|
2614
|
-
return p.text
|
|
2615
|
-
}
|
|
2616
|
-
return ''
|
|
2617
|
-
})
|
|
2618
|
-
.filter((t) => t.trim())
|
|
2619
|
-
|
|
2620
|
-
const userText = userTexts.join('\n\n')
|
|
2621
|
-
if (userText) {
|
|
2622
|
-
// Escape backticks in user messages to prevent formatting issues
|
|
2623
|
-
const escapedText = escapeDiscordFormatting(userText)
|
|
2624
|
-
await sendThreadMessage(thread, `**User:**\n${escapedText}`)
|
|
2625
|
-
}
|
|
2626
|
-
} else if (message.info.role === 'assistant') {
|
|
2627
|
-
// Render assistant parts
|
|
2628
|
-
const partsToRender: { id: string; content: string }[] = []
|
|
2629
|
-
|
|
2644
|
+
if (message.info.role === 'assistant') {
|
|
2630
2645
|
for (const part of message.parts) {
|
|
2631
2646
|
const content = formatPart(part)
|
|
2632
2647
|
if (content.trim()) {
|
|
2633
|
-
|
|
2648
|
+
allAssistantParts.push({ id: part.id, content })
|
|
2634
2649
|
}
|
|
2635
2650
|
}
|
|
2651
|
+
}
|
|
2652
|
+
}
|
|
2636
2653
|
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
.map((p) => p.content)
|
|
2640
|
-
.join('\n\n')
|
|
2654
|
+
const partsToRender = allAssistantParts.slice(-30)
|
|
2655
|
+
const skippedCount = allAssistantParts.length - partsToRender.length
|
|
2641
2656
|
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
|
|
2657
|
+
if (skippedCount > 0) {
|
|
2658
|
+
await sendThreadMessage(
|
|
2659
|
+
thread,
|
|
2660
|
+
`*Skipped ${skippedCount} older assistant parts...*`,
|
|
2661
|
+
)
|
|
2662
|
+
}
|
|
2646
2663
|
|
|
2647
|
-
|
|
2648
|
-
|
|
2649
|
-
|
|
2664
|
+
if (partsToRender.length > 0) {
|
|
2665
|
+
const combinedContent = partsToRender
|
|
2666
|
+
.map((p) => p.content)
|
|
2667
|
+
.join('\n\n')
|
|
2650
2668
|
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
}
|
|
2656
|
-
},
|
|
2657
|
-
)
|
|
2669
|
+
const discordMessage = await sendThreadMessage(
|
|
2670
|
+
thread,
|
|
2671
|
+
combinedContent,
|
|
2672
|
+
)
|
|
2658
2673
|
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
|
|
2674
|
+
const stmt = getDatabase().prepare(
|
|
2675
|
+
'INSERT OR REPLACE INTO part_messages (part_id, message_id, thread_id) VALUES (?, ?, ?)',
|
|
2676
|
+
)
|
|
2677
|
+
|
|
2678
|
+
const transaction = getDatabase().transaction(
|
|
2679
|
+
(parts: { id: string }[]) => {
|
|
2680
|
+
for (const part of parts) {
|
|
2681
|
+
stmt.run(part.id, discordMessage.id, thread.id)
|
|
2682
|
+
}
|
|
2683
|
+
},
|
|
2684
|
+
)
|
|
2685
|
+
|
|
2686
|
+
transaction(partsToRender)
|
|
2663
2687
|
}
|
|
2664
2688
|
|
|
2689
|
+
const messageCount = messages.length
|
|
2690
|
+
|
|
2665
2691
|
await sendThreadMessage(
|
|
2666
2692
|
thread,
|
|
2667
2693
|
`✅ **Session resumed!** Loaded ${messageCount} messages.\n\nYou can now continue the conversation by sending messages in this thread.`,
|
|
@@ -3101,6 +3127,7 @@ export async function startDiscordBot({
|
|
|
3101
3127
|
guildId: newState.guild.id,
|
|
3102
3128
|
channelId: voiceChannel.id,
|
|
3103
3129
|
appId: currentAppId!,
|
|
3130
|
+
discordClient,
|
|
3104
3131
|
})
|
|
3105
3132
|
|
|
3106
3133
|
// Handle connection state changes
|