kimaki 0.4.35 → 0.4.36
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/ai-tool-to-genai.js +1 -3
- package/dist/channel-management.js +1 -1
- package/dist/cli.js +135 -39
- package/dist/commands/abort.js +1 -1
- package/dist/commands/add-project.js +1 -1
- package/dist/commands/agent.js +6 -2
- package/dist/commands/ask-question.js +2 -1
- package/dist/commands/fork.js +7 -7
- package/dist/commands/queue.js +2 -2
- package/dist/commands/remove-project.js +109 -0
- package/dist/commands/resume.js +3 -5
- package/dist/commands/session.js +2 -2
- package/dist/commands/share.js +1 -1
- package/dist/commands/undo-redo.js +2 -2
- package/dist/commands/user-command.js +3 -6
- package/dist/config.js +1 -1
- package/dist/discord-bot.js +4 -10
- package/dist/discord-utils.js +33 -9
- package/dist/genai.js +4 -6
- package/dist/interaction-handler.js +8 -1
- package/dist/markdown.js +1 -3
- package/dist/message-formatting.js +7 -3
- package/dist/openai-realtime.js +3 -5
- package/dist/opencode.js +1 -1
- package/dist/session-handler.js +25 -15
- package/dist/system-message.js +5 -3
- package/dist/tools.js +9 -22
- package/dist/voice-handler.js +9 -12
- package/dist/voice.js +5 -3
- package/dist/xml.js +2 -4
- package/package.json +3 -2
- package/src/__snapshots__/compact-session-context-no-system.md +24 -24
- package/src/__snapshots__/compact-session-context.md +31 -31
- package/src/ai-tool-to-genai.ts +3 -11
- package/src/channel-management.ts +14 -25
- package/src/cli.ts +282 -195
- package/src/commands/abort.ts +1 -3
- package/src/commands/add-project.ts +8 -14
- package/src/commands/agent.ts +16 -9
- package/src/commands/ask-question.ts +8 -7
- package/src/commands/create-new-project.ts +8 -14
- package/src/commands/fork.ts +23 -27
- package/src/commands/model.ts +14 -11
- package/src/commands/permissions.ts +1 -1
- package/src/commands/queue.ts +6 -19
- package/src/commands/remove-project.ts +136 -0
- package/src/commands/resume.ts +11 -30
- package/src/commands/session.ts +4 -13
- package/src/commands/share.ts +1 -3
- package/src/commands/types.ts +1 -3
- package/src/commands/undo-redo.ts +6 -18
- package/src/commands/user-command.ts +8 -10
- package/src/config.ts +5 -5
- package/src/database.ts +10 -8
- package/src/discord-bot.ts +22 -46
- package/src/discord-utils.ts +35 -18
- package/src/escape-backticks.test.ts +0 -2
- package/src/format-tables.ts +1 -4
- package/src/genai-worker-wrapper.ts +3 -9
- package/src/genai-worker.ts +4 -19
- package/src/genai.ts +10 -42
- package/src/interaction-handler.ts +133 -121
- package/src/markdown.test.ts +10 -32
- package/src/markdown.ts +6 -14
- package/src/message-formatting.ts +13 -14
- package/src/openai-realtime.ts +25 -47
- package/src/opencode.ts +24 -34
- package/src/session-handler.ts +91 -61
- package/src/system-message.ts +13 -3
- package/src/tools.ts +13 -39
- package/src/utils.ts +1 -4
- package/src/voice-handler.ts +34 -78
- package/src/voice.ts +11 -19
- package/src/xml.test.ts +1 -1
- package/src/xml.ts +3 -12
package/src/tools.ts
CHANGED
|
@@ -19,10 +19,7 @@ const toolsLogger = createLogger('TOOLS')
|
|
|
19
19
|
import { ShareMarkdown } from './markdown.js'
|
|
20
20
|
import { formatDistanceToNow } from './utils.js'
|
|
21
21
|
import pc from 'picocolors'
|
|
22
|
-
import {
|
|
23
|
-
initializeOpencodeForDirectory,
|
|
24
|
-
getOpencodeSystemMessage,
|
|
25
|
-
} from './discord-bot.js'
|
|
22
|
+
import { initializeOpencodeForDirectory, getOpencodeSystemMessage } from './discord-bot.js'
|
|
26
23
|
|
|
27
24
|
export async function getTools({
|
|
28
25
|
onMessageCompleted,
|
|
@@ -116,25 +113,17 @@ export async function getTools({
|
|
|
116
113
|
description:
|
|
117
114
|
'Start a new chat session with an initial message. Does not wait for the message to complete',
|
|
118
115
|
inputSchema: z.object({
|
|
119
|
-
message: z
|
|
120
|
-
.string()
|
|
121
|
-
.describe('The initial message to start the chat with'),
|
|
116
|
+
message: z.string().describe('The initial message to start the chat with'),
|
|
122
117
|
title: z.string().optional().describe('Optional title for the session'),
|
|
123
118
|
model: z
|
|
124
119
|
.object({
|
|
125
|
-
providerId: z
|
|
126
|
-
|
|
127
|
-
.describe('The provider ID (e.g., "anthropic", "openai")'),
|
|
128
|
-
modelId: z
|
|
129
|
-
.string()
|
|
130
|
-
.describe(
|
|
131
|
-
'The model ID (e.g., "claude-opus-4-20250514", "gpt-5")',
|
|
132
|
-
),
|
|
120
|
+
providerId: z.string().describe('The provider ID (e.g., "anthropic", "openai")'),
|
|
121
|
+
modelId: z.string().describe('The model ID (e.g., "claude-opus-4-20250514", "gpt-5")'),
|
|
133
122
|
})
|
|
134
123
|
.optional()
|
|
135
124
|
.describe('Optional model to use for this session'),
|
|
136
125
|
}),
|
|
137
|
-
execute: async ({ message, title
|
|
126
|
+
execute: async ({ message, title }) => {
|
|
138
127
|
if (!message.trim()) {
|
|
139
128
|
throw new Error(`message must be a non empty string`)
|
|
140
129
|
}
|
|
@@ -187,18 +176,14 @@ export async function getTools({
|
|
|
187
176
|
} catch (error) {
|
|
188
177
|
return {
|
|
189
178
|
success: false,
|
|
190
|
-
error:
|
|
191
|
-
error instanceof Error
|
|
192
|
-
? error.message
|
|
193
|
-
: 'Failed to create chat session',
|
|
179
|
+
error: error instanceof Error ? error.message : 'Failed to create chat session',
|
|
194
180
|
}
|
|
195
181
|
}
|
|
196
182
|
},
|
|
197
183
|
}),
|
|
198
184
|
|
|
199
185
|
listChats: tool({
|
|
200
|
-
description:
|
|
201
|
-
'Get a list of available chat sessions sorted by most recent',
|
|
186
|
+
description: 'Get a list of available chat sessions sorted by most recent',
|
|
202
187
|
inputSchema: z.object({}),
|
|
203
188
|
execute: async () => {
|
|
204
189
|
toolsLogger.log(`Listing opencode sessions`)
|
|
@@ -223,10 +208,7 @@ export async function getTools({
|
|
|
223
208
|
})
|
|
224
209
|
const messages = messagesResponse.data || []
|
|
225
210
|
const lastMessage = messages[messages.length - 1]
|
|
226
|
-
if (
|
|
227
|
-
lastMessage?.info.role === 'assistant' &&
|
|
228
|
-
!lastMessage.info.time.completed
|
|
229
|
-
) {
|
|
211
|
+
if (lastMessage?.info.role === 'assistant' && !lastMessage.info.time.completed) {
|
|
230
212
|
return 'in_progress'
|
|
231
213
|
}
|
|
232
214
|
return 'finished'
|
|
@@ -281,10 +263,7 @@ export async function getTools({
|
|
|
281
263
|
description: 'Read messages from a chat session',
|
|
282
264
|
inputSchema: z.object({
|
|
283
265
|
sessionId: z.string().describe('The session ID to read messages from'),
|
|
284
|
-
lastAssistantOnly: z
|
|
285
|
-
.boolean()
|
|
286
|
-
.optional()
|
|
287
|
-
.describe('Only read the last assistant message'),
|
|
266
|
+
lastAssistantOnly: z.boolean().optional().describe('Only read the last assistant message'),
|
|
288
267
|
}),
|
|
289
268
|
execute: async ({ sessionId, lastAssistantOnly = false }) => {
|
|
290
269
|
if (lastAssistantOnly) {
|
|
@@ -296,9 +275,7 @@ export async function getTools({
|
|
|
296
275
|
return { success: false, error: 'No messages found' }
|
|
297
276
|
}
|
|
298
277
|
|
|
299
|
-
const assistantMessages = messages.data.filter(
|
|
300
|
-
(m) => m.info.role === 'assistant',
|
|
301
|
-
)
|
|
278
|
+
const assistantMessages = messages.data.filter((m) => m.info.role === 'assistant')
|
|
302
279
|
|
|
303
280
|
if (assistantMessages.length === 0) {
|
|
304
281
|
return {
|
|
@@ -309,8 +286,7 @@ export async function getTools({
|
|
|
309
286
|
|
|
310
287
|
const lastMessage = assistantMessages[assistantMessages.length - 1]
|
|
311
288
|
const status =
|
|
312
|
-
'completed' in lastMessage!.info.time &&
|
|
313
|
-
lastMessage!.info.time.completed
|
|
289
|
+
'completed' in lastMessage!.info.time && lastMessage!.info.time.completed
|
|
314
290
|
? 'completed'
|
|
315
291
|
: 'in_progress'
|
|
316
292
|
|
|
@@ -376,8 +352,7 @@ export async function getTools({
|
|
|
376
352
|
} catch (error) {
|
|
377
353
|
return {
|
|
378
354
|
success: false,
|
|
379
|
-
error:
|
|
380
|
-
error instanceof Error ? error.message : 'Unknown error occurred',
|
|
355
|
+
error: error instanceof Error ? error.message : 'Unknown error occurred',
|
|
381
356
|
}
|
|
382
357
|
}
|
|
383
358
|
},
|
|
@@ -412,8 +387,7 @@ export async function getTools({
|
|
|
412
387
|
} catch (error) {
|
|
413
388
|
return {
|
|
414
389
|
success: false,
|
|
415
|
-
error:
|
|
416
|
-
error instanceof Error ? error.message : 'Failed to fetch models',
|
|
390
|
+
error: error instanceof Error ? error.message : 'Failed to fetch models',
|
|
417
391
|
models: [],
|
|
418
392
|
}
|
|
419
393
|
}
|
package/src/utils.ts
CHANGED
|
@@ -66,10 +66,7 @@ export function deduplicateByKey<T, K>(arr: T[], keyFn: (item: T) => K): T[] {
|
|
|
66
66
|
})
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
-
export function isAbortError(
|
|
70
|
-
error: unknown,
|
|
71
|
-
signal?: AbortSignal,
|
|
72
|
-
): error is Error {
|
|
69
|
+
export function isAbortError(error: unknown, signal?: AbortSignal): error is Error {
|
|
73
70
|
return (
|
|
74
71
|
(error instanceof Error &&
|
|
75
72
|
(error.name === 'AbortError' ||
|
package/src/voice-handler.ts
CHANGED
|
@@ -28,7 +28,11 @@ import {
|
|
|
28
28
|
} from 'discord.js'
|
|
29
29
|
import { createGenAIWorker, type GenAIWorker } from './genai-worker-wrapper.js'
|
|
30
30
|
import { getDatabase } from './database.js'
|
|
31
|
-
import {
|
|
31
|
+
import {
|
|
32
|
+
sendThreadMessage,
|
|
33
|
+
escapeDiscordFormatting,
|
|
34
|
+
SILENT_MESSAGE_FLAGS,
|
|
35
|
+
} from './discord-utils.js'
|
|
32
36
|
import { transcribeAudio } from './voice.js'
|
|
33
37
|
import { createLogger } from './logger.js'
|
|
34
38
|
|
|
@@ -75,12 +79,7 @@ export async function createUserAudioLogStream(
|
|
|
75
79
|
if (!process.env.DEBUG) return undefined
|
|
76
80
|
|
|
77
81
|
const timestamp = new Date().toISOString().replace(/[:.]/g, '-')
|
|
78
|
-
const audioDir = path.join(
|
|
79
|
-
process.cwd(),
|
|
80
|
-
'discord-audio-logs',
|
|
81
|
-
guildId,
|
|
82
|
-
channelId,
|
|
83
|
-
)
|
|
82
|
+
const audioDir = path.join(process.cwd(), 'discord-audio-logs', guildId, channelId)
|
|
84
83
|
|
|
85
84
|
try {
|
|
86
85
|
await mkdir(audioDir, { recursive: true })
|
|
@@ -98,8 +97,7 @@ export async function createUserAudioLogStream(
|
|
|
98
97
|
}
|
|
99
98
|
|
|
100
99
|
export function frameMono16khz(): Transform {
|
|
101
|
-
const FRAME_BYTES =
|
|
102
|
-
(100 * 16_000 * 1 * 2) / 1000
|
|
100
|
+
const FRAME_BYTES = (100 * 16_000 * 1 * 2) / 1000
|
|
103
101
|
let stash: Buffer = Buffer.alloc(0)
|
|
104
102
|
let offset = 0
|
|
105
103
|
|
|
@@ -149,20 +147,14 @@ export async function setupVoiceHandling({
|
|
|
149
147
|
appId: string
|
|
150
148
|
discordClient: Client
|
|
151
149
|
}) {
|
|
152
|
-
voiceLogger.log(
|
|
153
|
-
`Setting up voice handling for guild ${guildId}, channel ${channelId}`,
|
|
154
|
-
)
|
|
150
|
+
voiceLogger.log(`Setting up voice handling for guild ${guildId}, channel ${channelId}`)
|
|
155
151
|
|
|
156
152
|
const channelDirRow = getDatabase()
|
|
157
|
-
.prepare(
|
|
158
|
-
'SELECT directory FROM channel_directories WHERE channel_id = ? AND channel_type = ?',
|
|
159
|
-
)
|
|
153
|
+
.prepare('SELECT directory FROM channel_directories WHERE channel_id = ? AND channel_type = ?')
|
|
160
154
|
.get(channelId, 'voice') as { directory: string } | undefined
|
|
161
155
|
|
|
162
156
|
if (!channelDirRow) {
|
|
163
|
-
voiceLogger.log(
|
|
164
|
-
`Voice channel ${channelId} has no associated directory, skipping setup`,
|
|
165
|
-
)
|
|
157
|
+
voiceLogger.log(`Voice channel ${channelId} has no associated directory, skipping setup`)
|
|
166
158
|
return
|
|
167
159
|
}
|
|
168
160
|
|
|
@@ -266,11 +258,12 @@ export async function setupVoiceHandling({
|
|
|
266
258
|
|
|
267
259
|
if (textChannelRow) {
|
|
268
260
|
try {
|
|
269
|
-
const textChannel = await discordClient.channels.fetch(
|
|
270
|
-
textChannelRow.channel_id,
|
|
271
|
-
)
|
|
261
|
+
const textChannel = await discordClient.channels.fetch(textChannelRow.channel_id)
|
|
272
262
|
if (textChannel?.isTextBased() && 'send' in textChannel) {
|
|
273
|
-
await textChannel.send({
|
|
263
|
+
await textChannel.send({
|
|
264
|
+
content: `⚠️ Voice session error: ${error}`,
|
|
265
|
+
flags: SILENT_MESSAGE_FLAGS,
|
|
266
|
+
})
|
|
274
267
|
}
|
|
275
268
|
} catch (e) {
|
|
276
269
|
voiceLogger.error('Failed to send error to text channel:', e)
|
|
@@ -330,10 +323,7 @@ export async function setupVoiceHandling({
|
|
|
330
323
|
|
|
331
324
|
const framer = frameMono16khz()
|
|
332
325
|
|
|
333
|
-
const pipeline = audioStream
|
|
334
|
-
.pipe(decoder)
|
|
335
|
-
.pipe(downsampleTransform)
|
|
336
|
-
.pipe(framer)
|
|
326
|
+
const pipeline = audioStream.pipe(decoder).pipe(downsampleTransform).pipe(framer)
|
|
337
327
|
|
|
338
328
|
pipeline
|
|
339
329
|
.on('data', (frame: Buffer) => {
|
|
@@ -359,9 +349,7 @@ export async function setupVoiceHandling({
|
|
|
359
349
|
})
|
|
360
350
|
.on('end', () => {
|
|
361
351
|
if (currentSessionCount === speakingSessionCount) {
|
|
362
|
-
voiceLogger.log(
|
|
363
|
-
`User ${userId} stopped speaking (session ${currentSessionCount})`,
|
|
364
|
-
)
|
|
352
|
+
voiceLogger.log(`User ${userId} stopped speaking (session ${currentSessionCount})`)
|
|
365
353
|
voiceData.genAiWorker?.sendRealtimeInput({
|
|
366
354
|
audioStreamEnd: true,
|
|
367
355
|
})
|
|
@@ -413,9 +401,7 @@ export async function cleanupVoiceConnection(guildId: string) {
|
|
|
413
401
|
})
|
|
414
402
|
}
|
|
415
403
|
|
|
416
|
-
if (
|
|
417
|
-
voiceData.connection.state.status !== VoiceConnectionStatus.Destroyed
|
|
418
|
-
) {
|
|
404
|
+
if (voiceData.connection.state.status !== VoiceConnectionStatus.Destroyed) {
|
|
419
405
|
voiceLogger.log(`Destroying voice connection...`)
|
|
420
406
|
voiceData.connection.destroy()
|
|
421
407
|
}
|
|
@@ -445,8 +431,8 @@ export async function processVoiceAttachment({
|
|
|
445
431
|
currentSessionContext?: string
|
|
446
432
|
lastSessionContext?: string
|
|
447
433
|
}): Promise<string | null> {
|
|
448
|
-
const audioAttachment = Array.from(message.attachments.values()).find(
|
|
449
|
-
|
|
434
|
+
const audioAttachment = Array.from(message.attachments.values()).find((attachment) =>
|
|
435
|
+
attachment.contentType?.startsWith('audio/'),
|
|
450
436
|
)
|
|
451
437
|
|
|
452
438
|
if (!audioAttachment) return null
|
|
@@ -550,15 +536,9 @@ export function registerVoiceStateHandler({
|
|
|
550
536
|
|
|
551
537
|
const guild = newState.guild || oldState.guild
|
|
552
538
|
const isOwner = member.id === guild.ownerId
|
|
553
|
-
const isAdmin = member.permissions.has(
|
|
554
|
-
|
|
555
|
-
)
|
|
556
|
-
const canManageServer = member.permissions.has(
|
|
557
|
-
PermissionsBitField.Flags.ManageGuild,
|
|
558
|
-
)
|
|
559
|
-
const hasKimakiRole = member.roles.cache.some(
|
|
560
|
-
(role) => role.name.toLowerCase() === 'kimaki',
|
|
561
|
-
)
|
|
539
|
+
const isAdmin = member.permissions.has(PermissionsBitField.Flags.Administrator)
|
|
540
|
+
const canManageServer = member.permissions.has(PermissionsBitField.Flags.ManageGuild)
|
|
541
|
+
const hasKimakiRole = member.roles.cache.some((role) => role.name.toLowerCase() === 'kimaki')
|
|
562
542
|
|
|
563
543
|
if (!isOwner && !isAdmin && !canManageServer && !hasKimakiRole) {
|
|
564
544
|
return
|
|
@@ -572,10 +552,7 @@ export function registerVoiceStateHandler({
|
|
|
572
552
|
const guildId = guild.id
|
|
573
553
|
const voiceData = voiceConnections.get(guildId)
|
|
574
554
|
|
|
575
|
-
if (
|
|
576
|
-
voiceData &&
|
|
577
|
-
voiceData.connection.joinConfig.channelId === oldState.channelId
|
|
578
|
-
) {
|
|
555
|
+
if (voiceData && voiceData.connection.joinConfig.channelId === oldState.channelId) {
|
|
579
556
|
const voiceChannel = oldState.channel as VoiceChannel
|
|
580
557
|
if (!voiceChannel) return
|
|
581
558
|
|
|
@@ -596,9 +573,7 @@ export function registerVoiceStateHandler({
|
|
|
596
573
|
|
|
597
574
|
await cleanupVoiceConnection(guildId)
|
|
598
575
|
} else {
|
|
599
|
-
voiceLogger.log(
|
|
600
|
-
`Other admins still in channel, bot staying in voice channel`,
|
|
601
|
-
)
|
|
576
|
+
voiceLogger.log(`Other admins still in channel, bot staying in voice channel`)
|
|
602
577
|
}
|
|
603
578
|
}
|
|
604
579
|
return
|
|
@@ -616,10 +591,7 @@ export function registerVoiceStateHandler({
|
|
|
616
591
|
const guildId = guild.id
|
|
617
592
|
const voiceData = voiceConnections.get(guildId)
|
|
618
593
|
|
|
619
|
-
if (
|
|
620
|
-
voiceData &&
|
|
621
|
-
voiceData.connection.joinConfig.channelId === oldState.channelId
|
|
622
|
-
) {
|
|
594
|
+
if (voiceData && voiceData.connection.joinConfig.channelId === oldState.channelId) {
|
|
623
595
|
const oldVoiceChannel = oldState.channel as VoiceChannel
|
|
624
596
|
if (oldVoiceChannel) {
|
|
625
597
|
const hasOtherAdmins = oldVoiceChannel.members.some((m) => {
|
|
@@ -633,9 +605,7 @@ export function registerVoiceStateHandler({
|
|
|
633
605
|
})
|
|
634
606
|
|
|
635
607
|
if (!hasOtherAdmins) {
|
|
636
|
-
voiceLogger.log(
|
|
637
|
-
`Following admin to new channel: ${newState.channel?.name}`,
|
|
638
|
-
)
|
|
608
|
+
voiceLogger.log(`Following admin to new channel: ${newState.channel?.name}`)
|
|
639
609
|
const voiceChannel = newState.channel as VoiceChannel
|
|
640
610
|
if (voiceChannel) {
|
|
641
611
|
voiceData.connection.rejoin({
|
|
@@ -645,9 +615,7 @@ export function registerVoiceStateHandler({
|
|
|
645
615
|
})
|
|
646
616
|
}
|
|
647
617
|
} else {
|
|
648
|
-
voiceLogger.log(
|
|
649
|
-
`Other admins still in old channel, bot staying put`,
|
|
650
|
-
)
|
|
618
|
+
voiceLogger.log(`Other admins still in old channel, bot staying put`)
|
|
651
619
|
}
|
|
652
620
|
}
|
|
653
621
|
}
|
|
@@ -667,16 +635,11 @@ export function registerVoiceStateHandler({
|
|
|
667
635
|
const existingVoiceData = voiceConnections.get(newState.guild.id)
|
|
668
636
|
if (
|
|
669
637
|
existingVoiceData &&
|
|
670
|
-
existingVoiceData.connection.state.status !==
|
|
671
|
-
VoiceConnectionStatus.Destroyed
|
|
638
|
+
existingVoiceData.connection.state.status !== VoiceConnectionStatus.Destroyed
|
|
672
639
|
) {
|
|
673
|
-
voiceLogger.log(
|
|
674
|
-
`Bot already connected to a voice channel in guild ${newState.guild.name}`,
|
|
675
|
-
)
|
|
640
|
+
voiceLogger.log(`Bot already connected to a voice channel in guild ${newState.guild.name}`)
|
|
676
641
|
|
|
677
|
-
if (
|
|
678
|
-
existingVoiceData.connection.joinConfig.channelId !== voiceChannel.id
|
|
679
|
-
) {
|
|
642
|
+
if (existingVoiceData.connection.joinConfig.channelId !== voiceChannel.id) {
|
|
680
643
|
voiceLogger.log(
|
|
681
644
|
`Moving bot from channel ${existingVoiceData.connection.joinConfig.channelId} to ${voiceChannel.id}`,
|
|
682
645
|
)
|
|
@@ -720,9 +683,7 @@ export function registerVoiceStateHandler({
|
|
|
720
683
|
})
|
|
721
684
|
|
|
722
685
|
connection.on(VoiceConnectionStatus.Disconnected, async () => {
|
|
723
|
-
voiceLogger.log(
|
|
724
|
-
`Disconnected from voice channel in guild: ${newState.guild.name}`,
|
|
725
|
-
)
|
|
686
|
+
voiceLogger.log(`Disconnected from voice channel in guild: ${newState.guild.name}`)
|
|
726
687
|
try {
|
|
727
688
|
await Promise.race([
|
|
728
689
|
entersState(connection, VoiceConnectionStatus.Signalling, 5_000),
|
|
@@ -737,17 +698,12 @@ export function registerVoiceStateHandler({
|
|
|
737
698
|
})
|
|
738
699
|
|
|
739
700
|
connection.on(VoiceConnectionStatus.Destroyed, async () => {
|
|
740
|
-
voiceLogger.log(
|
|
741
|
-
`Connection destroyed for guild: ${newState.guild.name}`,
|
|
742
|
-
)
|
|
701
|
+
voiceLogger.log(`Connection destroyed for guild: ${newState.guild.name}`)
|
|
743
702
|
await cleanupVoiceConnection(newState.guild.id)
|
|
744
703
|
})
|
|
745
704
|
|
|
746
705
|
connection.on('error', (error) => {
|
|
747
|
-
voiceLogger.error(
|
|
748
|
-
`Connection error in guild ${newState.guild.name}:`,
|
|
749
|
-
error,
|
|
750
|
-
)
|
|
706
|
+
voiceLogger.error(`Connection error in guild ${newState.guild.name}:`, error)
|
|
751
707
|
})
|
|
752
708
|
} catch (error) {
|
|
753
709
|
voiceLogger.error(`Failed to join voice channel:`, error)
|
package/src/voice.ts
CHANGED
|
@@ -2,13 +2,7 @@
|
|
|
2
2
|
// Transcribes voice messages with code-aware context, using grep/glob tools
|
|
3
3
|
// to verify technical terms, filenames, and function names in the codebase.
|
|
4
4
|
|
|
5
|
-
import {
|
|
6
|
-
GoogleGenAI,
|
|
7
|
-
Type,
|
|
8
|
-
type Content,
|
|
9
|
-
type Part,
|
|
10
|
-
type Tool,
|
|
11
|
-
} from '@google/genai'
|
|
5
|
+
import { GoogleGenAI, Type, type Content, type Part, type Tool } from '@google/genai'
|
|
12
6
|
import { createLogger } from './logger.js'
|
|
13
7
|
import { glob } from 'glob'
|
|
14
8
|
import { ripGrep } from 'ripgrep-js'
|
|
@@ -134,11 +128,7 @@ const transcriptionResultToolDeclaration = {
|
|
|
134
128
|
},
|
|
135
129
|
}
|
|
136
130
|
|
|
137
|
-
function createToolRunner({
|
|
138
|
-
directory,
|
|
139
|
-
}: {
|
|
140
|
-
directory?: string
|
|
141
|
-
}): TranscriptionToolRunner {
|
|
131
|
+
function createToolRunner({ directory }: { directory?: string }): TranscriptionToolRunner {
|
|
142
132
|
const hasDirectory = directory && directory.trim().length > 0
|
|
143
133
|
|
|
144
134
|
return async ({ name, args }) => {
|
|
@@ -242,9 +232,7 @@ export async function runTranscriptionLoop({
|
|
|
242
232
|
|
|
243
233
|
if (result.type === 'result') {
|
|
244
234
|
const transcription = result.transcription?.trim() || ''
|
|
245
|
-
voiceLogger.log(
|
|
246
|
-
`Transcription result received: "${transcription.slice(0, 100)}..."`,
|
|
247
|
-
)
|
|
235
|
+
voiceLogger.log(`Transcription result received: "${transcription.slice(0, 100)}..."`)
|
|
248
236
|
if (!transcription) {
|
|
249
237
|
throw new Error('Transcription failed: Model returned empty transcription')
|
|
250
238
|
}
|
|
@@ -292,7 +280,10 @@ export async function runTranscriptionLoop({
|
|
|
292
280
|
thinkingConfig: {
|
|
293
281
|
thinkingBudget: 512,
|
|
294
282
|
},
|
|
295
|
-
tools:
|
|
283
|
+
tools:
|
|
284
|
+
stepsRemaining <= 0
|
|
285
|
+
? [{ functionDeclarations: [transcriptionResultToolDeclaration] }]
|
|
286
|
+
: tools,
|
|
296
287
|
},
|
|
297
288
|
})
|
|
298
289
|
}
|
|
@@ -353,9 +344,10 @@ ${lastSessionContext}
|
|
|
353
344
|
${currentSessionContext}
|
|
354
345
|
</current_session>`)
|
|
355
346
|
}
|
|
356
|
-
const sessionContextSection =
|
|
357
|
-
|
|
358
|
-
|
|
347
|
+
const sessionContextSection =
|
|
348
|
+
sessionContextParts.length > 0
|
|
349
|
+
? `\nSession context (use to understand references to files, functions, tools used):\n${sessionContextParts.join('\n\n')}`
|
|
350
|
+
: ''
|
|
359
351
|
|
|
360
352
|
const transcriptionPrompt = `${languageHint}Transcribe this audio for a coding agent (like Claude Code or OpenCode).
|
|
361
353
|
|
package/src/xml.test.ts
CHANGED
package/src/xml.ts
CHANGED
|
@@ -40,15 +40,9 @@ export function extractTagsArrays<T extends string>({
|
|
|
40
40
|
// Extract content using original string positions
|
|
41
41
|
const extractContent = (): string => {
|
|
42
42
|
// Use element's own indices but exclude the tags
|
|
43
|
-
if (
|
|
44
|
-
element.startIndex !== null &&
|
|
45
|
-
element.endIndex !== null
|
|
46
|
-
) {
|
|
43
|
+
if (element.startIndex !== null && element.endIndex !== null) {
|
|
47
44
|
// Extract the full element including tags
|
|
48
|
-
const fullElement = xml.substring(
|
|
49
|
-
element.startIndex,
|
|
50
|
-
element.endIndex + 1,
|
|
51
|
-
)
|
|
45
|
+
const fullElement = xml.substring(element.startIndex, element.endIndex + 1)
|
|
52
46
|
// Find where content starts (after opening tag)
|
|
53
47
|
const contentStart = fullElement.indexOf('>') + 1
|
|
54
48
|
// Find where content ends (before this element's closing tag)
|
|
@@ -79,10 +73,7 @@ export function extractTagsArrays<T extends string>({
|
|
|
79
73
|
if (element.children) {
|
|
80
74
|
findTags(element.children, currentPath)
|
|
81
75
|
}
|
|
82
|
-
} else if (
|
|
83
|
-
node.type === ElementType.Text &&
|
|
84
|
-
node.parent?.type === ElementType.Root
|
|
85
|
-
) {
|
|
76
|
+
} else if (node.type === ElementType.Text && node.parent?.type === ElementType.Root) {
|
|
86
77
|
const textNode = node as Text
|
|
87
78
|
if (textNode.data.trim()) {
|
|
88
79
|
// console.log('node.parent',node.parent)
|