kimaki 0.4.38 → 0.4.40
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/cli.js +27 -23
- package/dist/commands/abort.js +15 -6
- package/dist/commands/add-project.js +9 -0
- package/dist/commands/agent.js +13 -1
- package/dist/commands/fork.js +13 -2
- package/dist/commands/model.js +12 -0
- package/dist/commands/remove-project.js +26 -16
- package/dist/commands/resume.js +9 -0
- package/dist/commands/session.js +14 -1
- package/dist/commands/share.js +10 -1
- package/dist/commands/undo-redo.js +13 -4
- package/dist/commands/worktree.js +180 -0
- package/dist/database.js +57 -5
- package/dist/discord-bot.js +48 -10
- package/dist/discord-utils.js +36 -0
- package/dist/errors.js +109 -0
- package/dist/genai-worker.js +18 -16
- package/dist/interaction-handler.js +6 -2
- package/dist/markdown.js +100 -85
- package/dist/markdown.test.js +10 -3
- package/dist/message-formatting.js +50 -37
- package/dist/opencode.js +43 -46
- package/dist/session-handler.js +100 -2
- package/dist/system-message.js +2 -0
- package/dist/tools.js +18 -8
- package/dist/voice-handler.js +48 -25
- package/dist/voice.js +159 -131
- package/package.json +4 -2
- package/src/cli.ts +31 -32
- package/src/commands/abort.ts +17 -7
- package/src/commands/add-project.ts +9 -0
- package/src/commands/agent.ts +13 -1
- package/src/commands/fork.ts +18 -7
- package/src/commands/model.ts +12 -0
- package/src/commands/remove-project.ts +28 -16
- package/src/commands/resume.ts +9 -0
- package/src/commands/session.ts +14 -1
- package/src/commands/share.ts +11 -1
- package/src/commands/undo-redo.ts +15 -6
- package/src/commands/worktree.ts +243 -0
- package/src/database.ts +104 -4
- package/src/discord-bot.ts +49 -9
- package/src/discord-utils.ts +50 -0
- package/src/errors.ts +138 -0
- package/src/genai-worker.ts +20 -17
- package/src/interaction-handler.ts +7 -2
- package/src/markdown.test.ts +13 -3
- package/src/markdown.ts +112 -95
- package/src/message-formatting.ts +55 -38
- package/src/opencode.ts +52 -49
- package/src/session-handler.ts +118 -3
- package/src/system-message.ts +2 -0
- package/src/tools.ts +18 -8
- package/src/voice-handler.ts +48 -23
- package/src/voice.ts +195 -148
package/dist/session-handler.js
CHANGED
|
@@ -11,6 +11,7 @@ import { createLogger } from './logger.js';
|
|
|
11
11
|
import { isAbortError } from './utils.js';
|
|
12
12
|
import { showAskUserQuestionDropdowns, cancelPendingQuestion, pendingQuestionContexts, } from './commands/ask-question.js';
|
|
13
13
|
import { showPermissionDropdown, cleanupPermissionContext } from './commands/permissions.js';
|
|
14
|
+
import * as errore from 'errore';
|
|
14
15
|
const sessionLogger = createLogger('SESSION');
|
|
15
16
|
const voiceLogger = createLogger('VOICE');
|
|
16
17
|
const discordLogger = createLogger('DISCORD');
|
|
@@ -48,6 +49,10 @@ export async function abortAndRetrySession({ sessionId, thread, projectDirectory
|
|
|
48
49
|
controller.abort('model-change');
|
|
49
50
|
// Also call the API abort endpoint
|
|
50
51
|
const getClient = await initializeOpencodeForDirectory(projectDirectory);
|
|
52
|
+
if (getClient instanceof Error) {
|
|
53
|
+
sessionLogger.error(`[ABORT+RETRY] Failed to initialize OpenCode client:`, getClient.message);
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
51
56
|
try {
|
|
52
57
|
await getClient().session.abort({ path: { id: sessionId } });
|
|
53
58
|
}
|
|
@@ -93,6 +98,10 @@ export async function handleOpencodeSession({ prompt, thread, projectDirectory,
|
|
|
93
98
|
const directory = projectDirectory || process.cwd();
|
|
94
99
|
sessionLogger.log(`Using directory: ${directory}`);
|
|
95
100
|
const getClient = await initializeOpencodeForDirectory(directory);
|
|
101
|
+
if (getClient instanceof Error) {
|
|
102
|
+
await sendThreadMessage(thread, `✗ ${getClient.message}`);
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
96
105
|
const serverEntry = getOpencodeServers().get(directory);
|
|
97
106
|
const port = serverEntry?.port;
|
|
98
107
|
const row = getDatabase()
|
|
@@ -258,11 +267,20 @@ export async function handleOpencodeSession({ prompt, thread, projectDirectory,
|
|
|
258
267
|
}
|
|
259
268
|
};
|
|
260
269
|
const eventHandler = async () => {
|
|
270
|
+
// Subtask tracking: child sessionId → { label, assistantMessageId }
|
|
271
|
+
const subtaskSessions = new Map();
|
|
272
|
+
// Counts spawned tasks per agent type: "explore" → 2
|
|
273
|
+
const agentSpawnCounts = {};
|
|
261
274
|
try {
|
|
262
275
|
let assistantMessageId;
|
|
263
276
|
for await (const event of events) {
|
|
264
277
|
if (event.type === 'message.updated') {
|
|
265
278
|
const msg = event.properties.info;
|
|
279
|
+
// Track assistant message IDs for subtask sessions
|
|
280
|
+
const subtaskInfo = subtaskSessions.get(msg.sessionID);
|
|
281
|
+
if (subtaskInfo && msg.role === 'assistant') {
|
|
282
|
+
subtaskInfo.assistantMessageId = msg.id;
|
|
283
|
+
}
|
|
266
284
|
if (msg.sessionID !== session.id) {
|
|
267
285
|
continue;
|
|
268
286
|
}
|
|
@@ -309,9 +327,48 @@ export async function handleOpencodeSession({ prompt, thread, projectDirectory,
|
|
|
309
327
|
}
|
|
310
328
|
else if (event.type === 'message.part.updated') {
|
|
311
329
|
const part = event.properties.part;
|
|
312
|
-
if (
|
|
330
|
+
// Check if this is a subtask event (child session we're tracking)
|
|
331
|
+
const subtaskInfo = subtaskSessions.get(part.sessionID);
|
|
332
|
+
const isSubtaskEvent = Boolean(subtaskInfo);
|
|
333
|
+
// Accept events from main session OR tracked subtask sessions
|
|
334
|
+
if (part.sessionID !== session.id && !isSubtaskEvent) {
|
|
335
|
+
continue;
|
|
336
|
+
}
|
|
337
|
+
// For subtask events, send them immediately with prefix (don't buffer in currentParts)
|
|
338
|
+
if (isSubtaskEvent && subtaskInfo) {
|
|
339
|
+
// Skip parts that aren't useful to show (step-start, step-finish, pending tools)
|
|
340
|
+
if (part.type === 'step-start' || part.type === 'step-finish') {
|
|
341
|
+
continue;
|
|
342
|
+
}
|
|
343
|
+
if (part.type === 'tool' && part.state.status === 'pending') {
|
|
344
|
+
continue;
|
|
345
|
+
}
|
|
346
|
+
// Skip text parts - the outer agent will report the task result anyway
|
|
347
|
+
if (part.type === 'text') {
|
|
348
|
+
continue;
|
|
349
|
+
}
|
|
350
|
+
// Only show parts from assistant messages (not user prompts sent to subtask)
|
|
351
|
+
// Skip if we haven't seen an assistant message yet, or if this part is from a different message
|
|
352
|
+
if (!subtaskInfo.assistantMessageId ||
|
|
353
|
+
part.messageID !== subtaskInfo.assistantMessageId) {
|
|
354
|
+
continue;
|
|
355
|
+
}
|
|
356
|
+
const content = formatPart(part, subtaskInfo.label);
|
|
357
|
+
if (content.trim() && !sentPartIds.has(part.id)) {
|
|
358
|
+
try {
|
|
359
|
+
const msg = await sendThreadMessage(thread, content + '\n\n');
|
|
360
|
+
sentPartIds.add(part.id);
|
|
361
|
+
getDatabase()
|
|
362
|
+
.prepare('INSERT OR REPLACE INTO part_messages (part_id, message_id, thread_id) VALUES (?, ?, ?)')
|
|
363
|
+
.run(part.id, msg.id, thread.id);
|
|
364
|
+
}
|
|
365
|
+
catch (error) {
|
|
366
|
+
discordLogger.error(`ERROR: Failed to send subtask part ${part.id}:`, error);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
313
369
|
continue;
|
|
314
370
|
}
|
|
371
|
+
// Main session events: require matching assistantMessageId
|
|
315
372
|
if (part.messageID !== assistantMessageId) {
|
|
316
373
|
continue;
|
|
317
374
|
}
|
|
@@ -339,6 +396,20 @@ export async function handleOpencodeSession({ prompt, thread, projectDirectory,
|
|
|
339
396
|
}
|
|
340
397
|
}
|
|
341
398
|
await sendPartMessage(part);
|
|
399
|
+
// Track task tool and register child session when sessionId is available
|
|
400
|
+
if (part.tool === 'task' && !sentPartIds.has(part.id)) {
|
|
401
|
+
const description = part.state.input?.description || '';
|
|
402
|
+
const agent = part.state.input?.subagent_type || 'task';
|
|
403
|
+
const childSessionId = part.state.metadata?.sessionId || '';
|
|
404
|
+
if (description && childSessionId) {
|
|
405
|
+
agentSpawnCounts[agent] = (agentSpawnCounts[agent] || 0) + 1;
|
|
406
|
+
const label = `${agent}-${agentSpawnCounts[agent]}`;
|
|
407
|
+
subtaskSessions.set(childSessionId, { label, assistantMessageId: undefined });
|
|
408
|
+
const taskDisplay = `┣ task **${label}** _${description}_`;
|
|
409
|
+
await sendThreadMessage(thread, taskDisplay + '\n\n');
|
|
410
|
+
sentPartIds.add(part.id);
|
|
411
|
+
}
|
|
412
|
+
}
|
|
342
413
|
}
|
|
343
414
|
// Show token usage for completed tools with large output (>5k tokens)
|
|
344
415
|
if (part.type === 'tool' && part.state.status === 'completed') {
|
|
@@ -499,11 +570,18 @@ export async function handleOpencodeSession({ prompt, thread, projectDirectory,
|
|
|
499
570
|
}
|
|
500
571
|
}
|
|
501
572
|
else if (event.type === 'session.idle') {
|
|
573
|
+
const idleSessionId = event.properties.sessionID;
|
|
502
574
|
// Session is done processing - abort to signal completion
|
|
503
|
-
if (
|
|
575
|
+
if (idleSessionId === session.id) {
|
|
504
576
|
sessionLogger.log(`[SESSION IDLE] Session ${session.id} is idle, aborting`);
|
|
505
577
|
abortController.abort('finished');
|
|
506
578
|
}
|
|
579
|
+
else if (subtaskSessions.has(idleSessionId)) {
|
|
580
|
+
// Child session completed - clean up tracking
|
|
581
|
+
const subtask = subtaskSessions.get(idleSessionId);
|
|
582
|
+
sessionLogger.log(`[SUBTASK IDLE] Subtask "${subtask?.label}" completed`);
|
|
583
|
+
subtaskSessions.delete(idleSessionId);
|
|
584
|
+
}
|
|
507
585
|
}
|
|
508
586
|
}
|
|
509
587
|
}
|
|
@@ -537,6 +615,26 @@ export async function handleOpencodeSession({ prompt, thread, projectDirectory,
|
|
|
537
615
|
const agentInfo = usedAgent && usedAgent.toLowerCase() !== 'build' ? ` ⋅ **${usedAgent}**` : '';
|
|
538
616
|
let contextInfo = '';
|
|
539
617
|
try {
|
|
618
|
+
// Fetch final token count from API since message.updated events can arrive
|
|
619
|
+
// after session.idle due to race conditions in event ordering
|
|
620
|
+
if (tokensUsedInSession === 0) {
|
|
621
|
+
const messagesResponse = await getClient().session.messages({
|
|
622
|
+
path: { id: session.id },
|
|
623
|
+
});
|
|
624
|
+
const messages = messagesResponse.data || [];
|
|
625
|
+
const lastAssistant = [...messages]
|
|
626
|
+
.reverse()
|
|
627
|
+
.find((m) => m.info.role === 'assistant');
|
|
628
|
+
if (lastAssistant && 'tokens' in lastAssistant.info) {
|
|
629
|
+
const tokens = lastAssistant.info.tokens;
|
|
630
|
+
tokensUsedInSession =
|
|
631
|
+
tokens.input +
|
|
632
|
+
tokens.output +
|
|
633
|
+
tokens.reasoning +
|
|
634
|
+
tokens.cache.read +
|
|
635
|
+
tokens.cache.write;
|
|
636
|
+
}
|
|
637
|
+
}
|
|
540
638
|
const providersResponse = await getClient().provider.list({ query: { directory } });
|
|
541
639
|
const provider = providersResponse.data?.all?.find((p) => p.id === usedProviderID);
|
|
542
640
|
const model = provider?.models?.[usedModel || ''];
|
package/dist/system-message.js
CHANGED
|
@@ -76,6 +76,8 @@ you can create diagrams wrapping them in code blocks.
|
|
|
76
76
|
|
|
77
77
|
IMPORTANT: At the end of each response, especially after completing a task or presenting a plan, use the question tool to offer the user clear options for what to do next.
|
|
78
78
|
|
|
79
|
+
IMPORTANT: The question tool must be called last, after all text parts. If it is called before your final text response, the user will not see the text.
|
|
80
|
+
|
|
79
81
|
Examples:
|
|
80
82
|
- After showing a plan: offer "Start implementing?" with Yes/No options
|
|
81
83
|
- After completing edits: offer "Commit changes?" with Yes/No options
|
package/dist/tools.js
CHANGED
|
@@ -7,6 +7,7 @@ import { spawn } from 'node:child_process';
|
|
|
7
7
|
import net from 'node:net';
|
|
8
8
|
import { createOpencodeClient, } from '@opencode-ai/sdk';
|
|
9
9
|
import { createLogger } from './logger.js';
|
|
10
|
+
import * as errore from 'errore';
|
|
10
11
|
const toolsLogger = createLogger('TOOLS');
|
|
11
12
|
import { ShareMarkdown } from './markdown.js';
|
|
12
13
|
import { formatDistanceToNow } from './utils.js';
|
|
@@ -14,6 +15,9 @@ import pc from 'picocolors';
|
|
|
14
15
|
import { initializeOpencodeForDirectory, getOpencodeSystemMessage } from './discord-bot.js';
|
|
15
16
|
export async function getTools({ onMessageCompleted, directory, }) {
|
|
16
17
|
const getClient = await initializeOpencodeForDirectory(directory);
|
|
18
|
+
if (getClient instanceof Error) {
|
|
19
|
+
throw new Error(getClient.message);
|
|
20
|
+
}
|
|
17
21
|
const client = getClient();
|
|
18
22
|
const markdownRenderer = new ShareMarkdown(client);
|
|
19
23
|
const providersResponse = await client.config.providers({});
|
|
@@ -55,7 +59,7 @@ export async function getTools({ onMessageCompleted, directory, }) {
|
|
|
55
59
|
},
|
|
56
60
|
})
|
|
57
61
|
.then(async (response) => {
|
|
58
|
-
const
|
|
62
|
+
const markdownResult = await markdownRenderer.generate({
|
|
59
63
|
sessionID: sessionId,
|
|
60
64
|
lastAssistantOnly: true,
|
|
61
65
|
});
|
|
@@ -63,7 +67,7 @@ export async function getTools({ onMessageCompleted, directory, }) {
|
|
|
63
67
|
sessionId,
|
|
64
68
|
messageId: '',
|
|
65
69
|
data: response.data,
|
|
66
|
-
markdown,
|
|
70
|
+
markdown: errore.unwrapOr(markdownResult, ''),
|
|
67
71
|
});
|
|
68
72
|
})
|
|
69
73
|
.catch((error) => {
|
|
@@ -116,7 +120,7 @@ export async function getTools({ onMessageCompleted, directory, }) {
|
|
|
116
120
|
},
|
|
117
121
|
})
|
|
118
122
|
.then(async (response) => {
|
|
119
|
-
const
|
|
123
|
+
const markdownResult = await markdownRenderer.generate({
|
|
120
124
|
sessionID: session.data.id,
|
|
121
125
|
lastAssistantOnly: true,
|
|
122
126
|
});
|
|
@@ -124,7 +128,7 @@ export async function getTools({ onMessageCompleted, directory, }) {
|
|
|
124
128
|
sessionId: session.data.id,
|
|
125
129
|
messageId: '',
|
|
126
130
|
data: response.data,
|
|
127
|
-
markdown,
|
|
131
|
+
markdown: errore.unwrapOr(markdownResult, ''),
|
|
128
132
|
});
|
|
129
133
|
})
|
|
130
134
|
.catch((error) => {
|
|
@@ -240,20 +244,26 @@ export async function getTools({ onMessageCompleted, directory, }) {
|
|
|
240
244
|
const status = 'completed' in lastMessage.info.time && lastMessage.info.time.completed
|
|
241
245
|
? 'completed'
|
|
242
246
|
: 'in_progress';
|
|
243
|
-
const
|
|
247
|
+
const markdownResult = await markdownRenderer.generate({
|
|
244
248
|
sessionID: sessionId,
|
|
245
249
|
lastAssistantOnly: true,
|
|
246
250
|
});
|
|
251
|
+
if (markdownResult instanceof Error) {
|
|
252
|
+
throw new Error(markdownResult.message);
|
|
253
|
+
}
|
|
247
254
|
return {
|
|
248
255
|
success: true,
|
|
249
|
-
markdown,
|
|
256
|
+
markdown: markdownResult,
|
|
250
257
|
status,
|
|
251
258
|
};
|
|
252
259
|
}
|
|
253
260
|
else {
|
|
254
|
-
const
|
|
261
|
+
const markdownResult = await markdownRenderer.generate({
|
|
255
262
|
sessionID: sessionId,
|
|
256
263
|
});
|
|
264
|
+
if (markdownResult instanceof Error) {
|
|
265
|
+
throw new Error(markdownResult.message);
|
|
266
|
+
}
|
|
257
267
|
const messages = await getClient().session.messages({
|
|
258
268
|
path: { id: sessionId },
|
|
259
269
|
});
|
|
@@ -266,7 +276,7 @@ export async function getTools({ onMessageCompleted, directory, }) {
|
|
|
266
276
|
: 'completed';
|
|
267
277
|
return {
|
|
268
278
|
success: true,
|
|
269
|
-
markdown,
|
|
279
|
+
markdown: markdownResult,
|
|
270
280
|
status,
|
|
271
281
|
};
|
|
272
282
|
}
|
package/dist/voice-handler.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// Discord voice channel connection and audio stream handler.
|
|
2
2
|
// Manages joining/leaving voice channels, captures user audio, resamples to 16kHz,
|
|
3
3
|
// and routes audio to the GenAI worker for real-time voice assistant interactions.
|
|
4
|
+
import * as errore from 'errore';
|
|
4
5
|
import { VoiceConnectionStatus, EndBehaviorType, joinVoiceChannel, entersState, } from '@discordjs/voice';
|
|
5
6
|
import { exec } from 'node:child_process';
|
|
6
7
|
import fs, { createWriteStream } from 'node:fs';
|
|
@@ -15,6 +16,7 @@ import { createGenAIWorker } from './genai-worker-wrapper.js';
|
|
|
15
16
|
import { getDatabase } from './database.js';
|
|
16
17
|
import { sendThreadMessage, escapeDiscordFormatting, SILENT_MESSAGE_FLAGS, } from './discord-utils.js';
|
|
17
18
|
import { transcribeAudio } from './voice.js';
|
|
19
|
+
import { FetchError } from './errors.js';
|
|
18
20
|
import { createLogger } from './logger.js';
|
|
19
21
|
const voiceLogger = createLogger('VOICE');
|
|
20
22
|
export const voiceConnections = new Map();
|
|
@@ -320,7 +322,15 @@ export async function processVoiceAttachment({ message, thread, projectDirectory
|
|
|
320
322
|
return null;
|
|
321
323
|
voiceLogger.log(`Detected audio attachment: ${audioAttachment.name} (${audioAttachment.contentType})`);
|
|
322
324
|
await sendThreadMessage(thread, '🎤 Transcribing voice message...');
|
|
323
|
-
const audioResponse = await
|
|
325
|
+
const audioResponse = await errore.tryAsync({
|
|
326
|
+
try: () => fetch(audioAttachment.url),
|
|
327
|
+
catch: (e) => new FetchError({ url: audioAttachment.url, cause: e }),
|
|
328
|
+
});
|
|
329
|
+
if (audioResponse instanceof Error) {
|
|
330
|
+
voiceLogger.error(`Failed to download audio attachment:`, audioResponse.message);
|
|
331
|
+
await sendThreadMessage(thread, `⚠️ Failed to download audio: ${audioResponse.message}`);
|
|
332
|
+
return null;
|
|
333
|
+
}
|
|
324
334
|
const audioBuffer = Buffer.from(await audioResponse.arrayBuffer());
|
|
325
335
|
voiceLogger.log(`Downloaded ${audioBuffer.length} bytes, transcribing...`);
|
|
326
336
|
let transcriptionPrompt = 'Discord voice message transcription';
|
|
@@ -331,9 +341,8 @@ export async function processVoiceAttachment({ message, thread, projectDirectory
|
|
|
331
341
|
const { stdout } = await execAsync('git ls-files | tree --fromfile -a', {
|
|
332
342
|
cwd: projectDirectory,
|
|
333
343
|
});
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
transcriptionPrompt = `Discord voice message transcription. Project file structure:\n${result}\n\nPlease transcribe file names and paths accurately based on this context.`;
|
|
344
|
+
if (stdout) {
|
|
345
|
+
transcriptionPrompt = `Discord voice message transcription. Project file structure:\n${stdout}\n\nPlease transcribe file names and paths accurately based on this context.`;
|
|
337
346
|
voiceLogger.log(`Added project context to transcription prompt`);
|
|
338
347
|
}
|
|
339
348
|
}
|
|
@@ -350,20 +359,24 @@ export async function processVoiceAttachment({ message, thread, projectDirectory
|
|
|
350
359
|
geminiApiKey = apiKeys.gemini_api_key;
|
|
351
360
|
}
|
|
352
361
|
}
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
+
const transcription = await transcribeAudio({
|
|
363
|
+
audio: audioBuffer,
|
|
364
|
+
prompt: transcriptionPrompt,
|
|
365
|
+
geminiApiKey,
|
|
366
|
+
directory: projectDirectory,
|
|
367
|
+
currentSessionContext,
|
|
368
|
+
lastSessionContext,
|
|
369
|
+
});
|
|
370
|
+
if (transcription instanceof Error) {
|
|
371
|
+
const errMsg = errore.matchError(transcription, {
|
|
372
|
+
ApiKeyMissingError: (e) => e.message,
|
|
373
|
+
InvalidAudioFormatError: (e) => e.message,
|
|
374
|
+
TranscriptionError: (e) => e.message,
|
|
375
|
+
EmptyTranscriptionError: (e) => e.message,
|
|
376
|
+
NoResponseContentError: (e) => e.message,
|
|
377
|
+
NoToolResponseError: (e) => e.message,
|
|
362
378
|
});
|
|
363
|
-
|
|
364
|
-
catch (error) {
|
|
365
|
-
const errMsg = error instanceof Error ? error.message : String(error);
|
|
366
|
-
voiceLogger.error(`Transcription failed:`, error);
|
|
379
|
+
voiceLogger.error(`Transcription failed:`, transcription);
|
|
367
380
|
await sendThreadMessage(thread, `⚠️ Transcription failed: ${errMsg}`);
|
|
368
381
|
return null;
|
|
369
382
|
}
|
|
@@ -371,15 +384,25 @@ export async function processVoiceAttachment({ message, thread, projectDirectory
|
|
|
371
384
|
if (isNewThread) {
|
|
372
385
|
const threadName = transcription.replace(/\s+/g, ' ').trim().slice(0, 80);
|
|
373
386
|
if (threadName) {
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
thread.setName(threadName),
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
387
|
+
const renamed = await Promise.race([
|
|
388
|
+
errore.tryAsync({
|
|
389
|
+
try: () => thread.setName(threadName),
|
|
390
|
+
catch: (e) => e,
|
|
391
|
+
}),
|
|
392
|
+
new Promise((resolve) => {
|
|
393
|
+
setTimeout(() => {
|
|
394
|
+
resolve(null);
|
|
395
|
+
}, 2000);
|
|
396
|
+
}),
|
|
397
|
+
]);
|
|
398
|
+
if (renamed === null) {
|
|
399
|
+
voiceLogger.log(`Thread name update timed out`);
|
|
380
400
|
}
|
|
381
|
-
|
|
382
|
-
voiceLogger.log(`Could not update thread name:`,
|
|
401
|
+
else if (renamed instanceof Error) {
|
|
402
|
+
voiceLogger.log(`Could not update thread name:`, renamed.message);
|
|
403
|
+
}
|
|
404
|
+
else {
|
|
405
|
+
voiceLogger.log(`Updated thread name to: "${threadName}"`);
|
|
383
406
|
}
|
|
384
407
|
}
|
|
385
408
|
}
|