kimaki 0.4.37 → 0.4.39
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/channel-management.js +6 -2
- package/dist/cli.js +41 -15
- package/dist/commands/abort.js +15 -6
- package/dist/commands/add-project.js +9 -0
- package/dist/commands/agent.js +114 -20
- 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 +13 -0
- package/dist/commands/share.js +10 -1
- package/dist/commands/undo-redo.js +13 -4
- package/dist/database.js +24 -5
- package/dist/discord-bot.js +38 -31
- package/dist/errors.js +110 -0
- package/dist/genai-worker.js +18 -16
- package/dist/interaction-handler.js +6 -1
- package/dist/markdown.js +96 -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 +136 -8
- 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 +2 -1
- package/src/channel-management.ts +6 -2
- package/src/cli.ts +67 -19
- package/src/commands/abort.ts +17 -7
- package/src/commands/add-project.ts +9 -0
- package/src/commands/agent.ts +160 -25
- 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 +13 -0
- package/src/commands/share.ts +11 -1
- package/src/commands/undo-redo.ts +15 -6
- package/src/database.ts +26 -4
- package/src/discord-bot.ts +42 -34
- package/src/errors.ts +208 -0
- package/src/genai-worker.ts +20 -17
- package/src/interaction-handler.ts +7 -1
- package/src/markdown.test.ts +13 -3
- package/src/markdown.ts +111 -95
- package/src/message-formatting.ts +55 -38
- package/src/opencode.ts +52 -49
- package/src/session-handler.ts +164 -11
- 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
|
@@ -4,6 +4,7 @@ import { getDatabase } from '../database.js';
|
|
|
4
4
|
import { initializeOpencodeForDirectory } from '../opencode.js';
|
|
5
5
|
import { resolveTextChannel, getKimakiMetadata, SILENT_MESSAGE_FLAGS } from '../discord-utils.js';
|
|
6
6
|
import { createLogger } from '../logger.js';
|
|
7
|
+
import * as errore from 'errore';
|
|
7
8
|
const logger = createLogger('UNDO-REDO');
|
|
8
9
|
export async function handleUndoCommand({ command }) {
|
|
9
10
|
const channel = command.channel;
|
|
@@ -50,9 +51,13 @@ export async function handleUndoCommand({ command }) {
|
|
|
50
51
|
return;
|
|
51
52
|
}
|
|
52
53
|
const sessionId = row.session_id;
|
|
54
|
+
await command.deferReply({ flags: SILENT_MESSAGE_FLAGS });
|
|
55
|
+
const getClient = await initializeOpencodeForDirectory(directory);
|
|
56
|
+
if (errore.isError(getClient)) {
|
|
57
|
+
await command.editReply(`Failed to undo: ${getClient.message}`);
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
53
60
|
try {
|
|
54
|
-
await command.deferReply({ flags: SILENT_MESSAGE_FLAGS });
|
|
55
|
-
const getClient = await initializeOpencodeForDirectory(directory);
|
|
56
61
|
// Fetch messages to find the last assistant message
|
|
57
62
|
const messagesResponse = await getClient().session.messages({
|
|
58
63
|
path: { id: sessionId },
|
|
@@ -133,9 +138,13 @@ export async function handleRedoCommand({ command }) {
|
|
|
133
138
|
return;
|
|
134
139
|
}
|
|
135
140
|
const sessionId = row.session_id;
|
|
141
|
+
await command.deferReply({ flags: SILENT_MESSAGE_FLAGS });
|
|
142
|
+
const getClient = await initializeOpencodeForDirectory(directory);
|
|
143
|
+
if (errore.isError(getClient)) {
|
|
144
|
+
await command.editReply(`Failed to redo: ${getClient.message}`);
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
136
147
|
try {
|
|
137
|
-
await command.deferReply({ flags: SILENT_MESSAGE_FLAGS });
|
|
138
|
-
const getClient = await initializeOpencodeForDirectory(directory);
|
|
139
148
|
// Check if session has reverted state
|
|
140
149
|
const sessionResponse = await getClient().session.get({
|
|
141
150
|
path: { id: sessionId },
|
package/dist/database.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
import Database from 'better-sqlite3';
|
|
5
5
|
import fs from 'node:fs';
|
|
6
6
|
import path from 'node:path';
|
|
7
|
+
import * as errore from 'errore';
|
|
7
8
|
import { createLogger } from './logger.js';
|
|
8
9
|
import { getDataDir } from './config.js';
|
|
9
10
|
const dbLogger = createLogger('DB');
|
|
@@ -11,11 +12,14 @@ let db = null;
|
|
|
11
12
|
export function getDatabase() {
|
|
12
13
|
if (!db) {
|
|
13
14
|
const dataDir = getDataDir();
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
15
|
+
const mkdirError = errore.tryFn({
|
|
16
|
+
try: () => {
|
|
17
|
+
fs.mkdirSync(dataDir, { recursive: true });
|
|
18
|
+
},
|
|
19
|
+
catch: (e) => e,
|
|
20
|
+
});
|
|
21
|
+
if (errore.isError(mkdirError)) {
|
|
22
|
+
dbLogger.error(`Failed to create data directory ${dataDir}:`, mkdirError.message);
|
|
19
23
|
}
|
|
20
24
|
const dbPath = path.join(dataDir, 'discord-sessions.db');
|
|
21
25
|
dbLogger.log(`Opening database at: ${dbPath}`);
|
|
@@ -57,6 +61,13 @@ export function getDatabase() {
|
|
|
57
61
|
catch {
|
|
58
62
|
// Column already exists, ignore
|
|
59
63
|
}
|
|
64
|
+
// Table for threads that should auto-start a session (created by CLI without --notify-only)
|
|
65
|
+
db.exec(`
|
|
66
|
+
CREATE TABLE IF NOT EXISTS pending_auto_start (
|
|
67
|
+
thread_id TEXT PRIMARY KEY,
|
|
68
|
+
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
69
|
+
)
|
|
70
|
+
`);
|
|
60
71
|
db.exec(`
|
|
61
72
|
CREATE TABLE IF NOT EXISTS bot_api_keys (
|
|
62
73
|
app_id TEXT PRIMARY KEY,
|
|
@@ -147,6 +158,14 @@ export function setSessionModel(sessionId, modelId) {
|
|
|
147
158
|
const db = getDatabase();
|
|
148
159
|
db.prepare(`INSERT OR REPLACE INTO session_models (session_id, model_id) VALUES (?, ?)`).run(sessionId, modelId);
|
|
149
160
|
}
|
|
161
|
+
/**
|
|
162
|
+
* Clear the model preference for a session.
|
|
163
|
+
* Used when switching agents so the agent's model takes effect.
|
|
164
|
+
*/
|
|
165
|
+
export function clearSessionModel(sessionId) {
|
|
166
|
+
const db = getDatabase();
|
|
167
|
+
db.prepare('DELETE FROM session_models WHERE session_id = ?').run(sessionId);
|
|
168
|
+
}
|
|
150
169
|
/**
|
|
151
170
|
* Get the agent preference for a channel.
|
|
152
171
|
*/
|
package/dist/discord-bot.js
CHANGED
|
@@ -18,6 +18,7 @@ export { getOpencodeSystemMessage } from './system-message.js';
|
|
|
18
18
|
export { ensureKimakiCategory, ensureKimakiAudioCategory, createProjectChannels, getChannelsWithDescriptions, } from './channel-management.js';
|
|
19
19
|
import { ChannelType, Client, Events, GatewayIntentBits, Partials, PermissionsBitField, ThreadAutoArchiveDuration, } from 'discord.js';
|
|
20
20
|
import fs from 'node:fs';
|
|
21
|
+
import * as errore from 'errore';
|
|
21
22
|
import { extractTagsArrays } from './xml.js';
|
|
22
23
|
import { createLogger } from './logger.js';
|
|
23
24
|
import { setGlobalDispatcher, Agent } from 'undici';
|
|
@@ -89,11 +90,12 @@ export async function startDiscordBot({ token, appId, discordClient, }) {
|
|
|
89
90
|
}
|
|
90
91
|
if (message.partial) {
|
|
91
92
|
discordLogger.log(`Fetching partial message ${message.id}`);
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
93
|
+
const fetched = await errore.tryAsync({
|
|
94
|
+
try: () => message.fetch(),
|
|
95
|
+
catch: (e) => e,
|
|
96
|
+
});
|
|
97
|
+
if (errore.isError(fetched)) {
|
|
98
|
+
discordLogger.log(`Failed to fetch partial message ${message.id}:`, fetched.message);
|
|
97
99
|
return;
|
|
98
100
|
}
|
|
99
101
|
}
|
|
@@ -152,17 +154,11 @@ export async function startDiscordBot({ token, appId, discordClient, }) {
|
|
|
152
154
|
discordLogger.log(`Cannot start session: no project directory for thread ${thread.id}`);
|
|
153
155
|
return;
|
|
154
156
|
}
|
|
155
|
-
// Include starter message
|
|
157
|
+
// Include starter message as context for the session
|
|
156
158
|
let prompt = message.content;
|
|
157
159
|
const starterMessage = await thread.fetchStarterMessage().catch(() => null);
|
|
158
|
-
if (starterMessage?.content) {
|
|
159
|
-
|
|
160
|
-
const notificationContent = starterMessage.content
|
|
161
|
-
.replace(/^📢 \*\*Notification\*\*\n?/, '')
|
|
162
|
-
.trim();
|
|
163
|
-
if (notificationContent) {
|
|
164
|
-
prompt = `Context from notification:\n${notificationContent}\n\nUser request:\n${message.content}`;
|
|
165
|
-
}
|
|
160
|
+
if (starterMessage?.content && starterMessage.content !== message.content) {
|
|
161
|
+
prompt = `Context from thread:\n${starterMessage.content}\n\nUser request:\n${message.content}`;
|
|
166
162
|
}
|
|
167
163
|
await handleOpencodeSession({
|
|
168
164
|
prompt,
|
|
@@ -179,28 +175,39 @@ export async function startDiscordBot({ token, appId, discordClient, }) {
|
|
|
179
175
|
if (projectDirectory) {
|
|
180
176
|
try {
|
|
181
177
|
const getClient = await initializeOpencodeForDirectory(projectDirectory);
|
|
178
|
+
if (errore.isError(getClient)) {
|
|
179
|
+
voiceLogger.error(`[SESSION] Failed to initialize OpenCode client:`, getClient.message);
|
|
180
|
+
throw new Error(getClient.message);
|
|
181
|
+
}
|
|
182
182
|
const client = getClient();
|
|
183
183
|
// get current session context (without system prompt, it would be duplicated)
|
|
184
184
|
if (row.session_id) {
|
|
185
|
-
|
|
185
|
+
const result = await getCompactSessionContext({
|
|
186
186
|
client,
|
|
187
187
|
sessionId: row.session_id,
|
|
188
188
|
includeSystemPrompt: false,
|
|
189
189
|
maxMessages: 15,
|
|
190
190
|
});
|
|
191
|
+
if (errore.isOk(result)) {
|
|
192
|
+
currentSessionContext = result;
|
|
193
|
+
}
|
|
191
194
|
}
|
|
192
195
|
// get last session context (with system prompt for project context)
|
|
193
|
-
const
|
|
196
|
+
const lastSessionResult = await getLastSessionId({
|
|
194
197
|
client,
|
|
195
198
|
excludeSessionId: row.session_id,
|
|
196
199
|
});
|
|
200
|
+
const lastSessionId = errore.unwrapOr(lastSessionResult, null);
|
|
197
201
|
if (lastSessionId) {
|
|
198
|
-
|
|
202
|
+
const result = await getCompactSessionContext({
|
|
199
203
|
client,
|
|
200
204
|
sessionId: lastSessionId,
|
|
201
205
|
includeSystemPrompt: true,
|
|
202
206
|
maxMessages: 10,
|
|
203
207
|
});
|
|
208
|
+
if (errore.isOk(result)) {
|
|
209
|
+
lastSessionContext = result;
|
|
210
|
+
}
|
|
204
211
|
}
|
|
205
212
|
}
|
|
206
213
|
catch (e) {
|
|
@@ -316,35 +323,35 @@ export async function startDiscordBot({ token, appId, discordClient, }) {
|
|
|
316
323
|
}
|
|
317
324
|
}
|
|
318
325
|
});
|
|
319
|
-
//
|
|
320
|
-
const BOT_SESSION_PREFIX = '🤖 **Bot-initiated session**';
|
|
321
|
-
// Handle bot-initiated threads created by `kimaki send`
|
|
326
|
+
// Handle bot-initiated threads created by `kimaki send` (without --notify-only)
|
|
322
327
|
discordClient.on(Events.ThreadCreate, async (thread, newlyCreated) => {
|
|
323
328
|
try {
|
|
324
329
|
if (!newlyCreated) {
|
|
325
330
|
return;
|
|
326
331
|
}
|
|
332
|
+
// Check if this thread is marked for auto-start in the database
|
|
333
|
+
const db = getDatabase();
|
|
334
|
+
const pendingRow = db
|
|
335
|
+
.prepare('SELECT thread_id FROM pending_auto_start WHERE thread_id = ?')
|
|
336
|
+
.get(thread.id);
|
|
337
|
+
if (!pendingRow) {
|
|
338
|
+
return; // Not a CLI-initiated auto-start thread
|
|
339
|
+
}
|
|
340
|
+
// Remove from pending table
|
|
341
|
+
db.prepare('DELETE FROM pending_auto_start WHERE thread_id = ?').run(thread.id);
|
|
342
|
+
discordLogger.log(`[BOT_SESSION] Detected bot-initiated thread: ${thread.name}`);
|
|
327
343
|
// Only handle threads in text channels
|
|
328
344
|
const parent = thread.parent;
|
|
329
345
|
if (!parent || parent.type !== ChannelType.GuildText) {
|
|
330
346
|
return;
|
|
331
347
|
}
|
|
332
|
-
// Get the starter message
|
|
348
|
+
// Get the starter message for the prompt
|
|
333
349
|
const starterMessage = await thread.fetchStarterMessage().catch(() => null);
|
|
334
350
|
if (!starterMessage) {
|
|
335
351
|
discordLogger.log(`[THREAD_CREATE] Could not fetch starter message for thread ${thread.id}`);
|
|
336
352
|
return;
|
|
337
353
|
}
|
|
338
|
-
|
|
339
|
-
if (starterMessage.author.id !== discordClient.user?.id) {
|
|
340
|
-
return;
|
|
341
|
-
}
|
|
342
|
-
if (!starterMessage.content.startsWith(BOT_SESSION_PREFIX)) {
|
|
343
|
-
return;
|
|
344
|
-
}
|
|
345
|
-
discordLogger.log(`[BOT_SESSION] Detected bot-initiated thread: ${thread.name}`);
|
|
346
|
-
// Extract the prompt (everything after the prefix)
|
|
347
|
-
const prompt = starterMessage.content.slice(BOT_SESSION_PREFIX.length).trim();
|
|
354
|
+
const prompt = starterMessage.content.trim();
|
|
348
355
|
if (!prompt) {
|
|
349
356
|
discordLogger.log(`[BOT_SESSION] No prompt found in starter message`);
|
|
350
357
|
return;
|
package/dist/errors.js
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
// TaggedError definitions for type-safe error handling with errore.
|
|
2
|
+
// Errors are grouped by category: infrastructure, domain, and validation.
|
|
3
|
+
// Use errore.matchError() for exhaustive error handling in command handlers.
|
|
4
|
+
import * as errore from 'errore';
|
|
5
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
6
|
+
// INFRASTRUCTURE ERRORS - Server, filesystem, external services
|
|
7
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
8
|
+
export class DirectoryNotAccessibleError extends errore.TaggedError('DirectoryNotAccessibleError')() {
|
|
9
|
+
constructor(args) {
|
|
10
|
+
super({ ...args, message: `Directory does not exist or is not accessible: ${args.directory}` });
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
export class ServerStartError extends errore.TaggedError('ServerStartError')() {
|
|
14
|
+
constructor(args) {
|
|
15
|
+
super({ ...args, message: `Server failed to start on port ${args.port}: ${args.reason}` });
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
export class ServerNotFoundError extends errore.TaggedError('ServerNotFoundError')() {
|
|
19
|
+
constructor(args) {
|
|
20
|
+
super({ ...args, message: `OpenCode server not found for directory: ${args.directory}` });
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
export class ServerNotReadyError extends errore.TaggedError('ServerNotReadyError')() {
|
|
24
|
+
constructor(args) {
|
|
25
|
+
super({
|
|
26
|
+
...args,
|
|
27
|
+
message: `OpenCode server for directory "${args.directory}" is in an error state (no client available)`,
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
export class ApiKeyMissingError extends errore.TaggedError('ApiKeyMissingError')() {
|
|
32
|
+
constructor(args) {
|
|
33
|
+
super({ ...args, message: `${args.service} API key is required` });
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
37
|
+
// DOMAIN ERRORS - Sessions, messages, transcription
|
|
38
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
39
|
+
export class SessionNotFoundError extends errore.TaggedError('SessionNotFoundError')() {
|
|
40
|
+
constructor(args) {
|
|
41
|
+
super({ ...args, message: `Session ${args.sessionId} not found` });
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
export class SessionCreateError extends errore.TaggedError('SessionCreateError')() {
|
|
45
|
+
}
|
|
46
|
+
export class MessagesNotFoundError extends errore.TaggedError('MessagesNotFoundError')() {
|
|
47
|
+
constructor(args) {
|
|
48
|
+
super({ ...args, message: `No messages found for session ${args.sessionId}` });
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
export class TranscriptionError extends errore.TaggedError('TranscriptionError')() {
|
|
52
|
+
constructor(args) {
|
|
53
|
+
super({ ...args, message: `Transcription failed: ${args.reason}` });
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
export class GrepSearchError extends errore.TaggedError('GrepSearchError')() {
|
|
57
|
+
constructor(args) {
|
|
58
|
+
super({ ...args, message: `Grep search failed for pattern: ${args.pattern}` });
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
export class GlobSearchError extends errore.TaggedError('GlobSearchError')() {
|
|
62
|
+
constructor(args) {
|
|
63
|
+
super({ ...args, message: `Glob search failed for pattern: ${args.pattern}` });
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
67
|
+
// VALIDATION ERRORS - Input validation, format checks
|
|
68
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
69
|
+
export class InvalidAudioFormatError extends errore.TaggedError('InvalidAudioFormatError')() {
|
|
70
|
+
constructor() {
|
|
71
|
+
super({ message: 'Invalid audio format' });
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
export class EmptyTranscriptionError extends errore.TaggedError('EmptyTranscriptionError')() {
|
|
75
|
+
constructor() {
|
|
76
|
+
super({ message: 'Model returned empty transcription' });
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
export class NoResponseContentError extends errore.TaggedError('NoResponseContentError')() {
|
|
80
|
+
constructor() {
|
|
81
|
+
super({ message: 'No response content from model' });
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
export class NoToolResponseError extends errore.TaggedError('NoToolResponseError')() {
|
|
85
|
+
constructor() {
|
|
86
|
+
super({ message: 'No valid tool responses' });
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
90
|
+
// NETWORK ERRORS - Fetch and HTTP
|
|
91
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
92
|
+
export class FetchError extends errore.TaggedError('FetchError')() {
|
|
93
|
+
constructor(args) {
|
|
94
|
+
const causeMsg = args.cause instanceof Error ? args.cause.message : String(args.cause);
|
|
95
|
+
super({ ...args, message: `Fetch failed for ${args.url}: ${causeMsg}` });
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
99
|
+
// API ERRORS - External service responses
|
|
100
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
101
|
+
export class DiscordApiError extends errore.TaggedError('DiscordApiError')() {
|
|
102
|
+
constructor(args) {
|
|
103
|
+
super({ ...args, message: `Discord API error: ${args.status}${args.body ? ` - ${args.body}` : ''}` });
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
export class OpenCodeApiError extends errore.TaggedError('OpenCodeApiError')() {
|
|
107
|
+
constructor(args) {
|
|
108
|
+
super({ ...args, message: `OpenCode API error (${args.status})${args.body ? `: ${args.body}` : ''}` });
|
|
109
|
+
}
|
|
110
|
+
}
|
package/dist/genai-worker.js
CHANGED
|
@@ -3,12 +3,13 @@
|
|
|
3
3
|
// Resamples 24kHz GenAI output to 48kHz stereo Opus packets for Discord.
|
|
4
4
|
import { parentPort, threadId } from 'node:worker_threads';
|
|
5
5
|
import { createWriteStream } from 'node:fs';
|
|
6
|
-
import { mkdir } from 'node:fs/promises';
|
|
7
6
|
import path from 'node:path';
|
|
7
|
+
import * as errore from 'errore';
|
|
8
8
|
import { Resampler } from '@purinton/resampler';
|
|
9
9
|
import * as prism from 'prism-media';
|
|
10
10
|
import { startGenAiSession } from './genai.js';
|
|
11
11
|
import { getTools } from './tools.js';
|
|
12
|
+
import { mkdir } from 'node:fs/promises';
|
|
12
13
|
import { createLogger } from './logger.js';
|
|
13
14
|
if (!parentPort) {
|
|
14
15
|
throw new Error('This module must be run as a worker thread');
|
|
@@ -98,23 +99,24 @@ async function createAssistantAudioLogStream(guildId, channelId) {
|
|
|
98
99
|
return null;
|
|
99
100
|
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
100
101
|
const audioDir = path.join(process.cwd(), 'discord-audio-logs', guildId, channelId);
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
// Add error handler to prevent crashes
|
|
108
|
-
outputAudioStream.on('error', (error) => {
|
|
109
|
-
workerLogger.error(`Assistant audio log stream error:`, error);
|
|
110
|
-
});
|
|
111
|
-
workerLogger.log(`Created assistant audio log: ${outputFilePath}`);
|
|
112
|
-
return outputAudioStream;
|
|
113
|
-
}
|
|
114
|
-
catch (error) {
|
|
115
|
-
workerLogger.error(`Failed to create audio log directory:`, error);
|
|
102
|
+
const mkdirError = await errore.tryAsync({
|
|
103
|
+
try: () => mkdir(audioDir, { recursive: true }),
|
|
104
|
+
catch: (e) => e,
|
|
105
|
+
});
|
|
106
|
+
if (errore.isError(mkdirError)) {
|
|
107
|
+
workerLogger.error(`Failed to create audio log directory:`, mkdirError.message);
|
|
116
108
|
return null;
|
|
117
109
|
}
|
|
110
|
+
// Create stream for assistant audio (24kHz mono s16le PCM)
|
|
111
|
+
const outputFileName = `assistant_${timestamp}.24.pcm`;
|
|
112
|
+
const outputFilePath = path.join(audioDir, outputFileName);
|
|
113
|
+
const outputAudioStream = createWriteStream(outputFilePath);
|
|
114
|
+
// Add error handler to prevent crashes
|
|
115
|
+
outputAudioStream.on('error', (error) => {
|
|
116
|
+
workerLogger.error(`Assistant audio log stream error:`, error);
|
|
117
|
+
});
|
|
118
|
+
workerLogger.log(`Created assistant audio log: ${outputFilePath}`);
|
|
119
|
+
return outputAudioStream;
|
|
118
120
|
}
|
|
119
121
|
// Handle encoded Opus packets
|
|
120
122
|
opusEncoder.on('data', (packet) => {
|
|
@@ -12,7 +12,7 @@ import { handleAbortCommand } from './commands/abort.js';
|
|
|
12
12
|
import { handleShareCommand } from './commands/share.js';
|
|
13
13
|
import { handleForkCommand, handleForkSelectMenu } from './commands/fork.js';
|
|
14
14
|
import { handleModelCommand, handleProviderSelectMenu, handleModelSelectMenu, } from './commands/model.js';
|
|
15
|
-
import { handleAgentCommand, handleAgentSelectMenu } from './commands/agent.js';
|
|
15
|
+
import { handleAgentCommand, handleAgentSelectMenu, handleQuickAgentCommand } from './commands/agent.js';
|
|
16
16
|
import { handleAskQuestionSelectMenu } from './commands/ask-question.js';
|
|
17
17
|
import { handleQueueCommand, handleClearQueueCommand } from './commands/queue.js';
|
|
18
18
|
import { handleUndoCommand, handleRedoCommand } from './commands/undo-redo.js';
|
|
@@ -94,6 +94,11 @@ export function registerInteractionHandler({ discordClient, appId, }) {
|
|
|
94
94
|
await handleRedoCommand({ command: interaction, appId });
|
|
95
95
|
return;
|
|
96
96
|
}
|
|
97
|
+
// Handle quick agent commands (ending with -agent suffix, but not the base /agent command)
|
|
98
|
+
if (interaction.commandName.endsWith('-agent') && interaction.commandName !== 'agent') {
|
|
99
|
+
await handleQuickAgentCommand({ command: interaction, appId });
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
97
102
|
// Handle user-defined commands (ending with -cmd suffix)
|
|
98
103
|
if (interaction.commandName.endsWith('-cmd')) {
|
|
99
104
|
await handleUserCommand({ command: interaction, appId });
|