kimaki 0.4.38 → 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/cli.js +9 -3
- 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 +13 -0
- package/dist/commands/share.js +10 -1
- package/dist/commands/undo-redo.js +13 -4
- package/dist/database.js +9 -5
- package/dist/discord-bot.js +21 -8
- package/dist/errors.js +110 -0
- package/dist/genai-worker.js +18 -16
- 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 +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 +2 -1
- package/src/cli.ts +12 -3
- 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 +13 -0
- package/src/commands/share.ts +11 -1
- package/src/commands/undo-redo.ts +15 -6
- package/src/database.ts +9 -4
- package/src/discord-bot.ts +21 -7
- package/src/errors.ts +208 -0
- package/src/genai-worker.ts +20 -17
- 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 +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/voice.js
CHANGED
|
@@ -1,49 +1,51 @@
|
|
|
1
1
|
// Audio transcription service using Google Gemini.
|
|
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
|
+
// Uses errore for type-safe error handling.
|
|
4
5
|
import { GoogleGenAI, Type } from '@google/genai';
|
|
6
|
+
import * as errore from 'errore';
|
|
5
7
|
import { createLogger } from './logger.js';
|
|
6
8
|
import { glob } from 'glob';
|
|
7
9
|
import { ripGrep } from 'ripgrep-js';
|
|
10
|
+
import { ApiKeyMissingError, InvalidAudioFormatError, TranscriptionError, EmptyTranscriptionError, NoResponseContentError, NoToolResponseError, GrepSearchError, GlobSearchError, } from './errors.js';
|
|
8
11
|
const voiceLogger = createLogger('VOICE');
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
}
|
|
12
|
+
function runGrep({ pattern, directory }) {
|
|
13
|
+
return errore.tryAsync({
|
|
14
|
+
try: async () => {
|
|
15
|
+
const results = await ripGrep(directory, {
|
|
16
|
+
string: pattern,
|
|
17
|
+
globs: ['!node_modules/**', '!.git/**', '!dist/**', '!build/**'],
|
|
18
|
+
});
|
|
19
|
+
if (results.length === 0) {
|
|
20
|
+
return 'No matches found';
|
|
21
|
+
}
|
|
22
|
+
const output = results
|
|
23
|
+
.slice(0, 10)
|
|
24
|
+
.map((match) => {
|
|
25
|
+
return `${match.path.text}:${match.line_number}: ${match.lines.text.trim()}`;
|
|
26
|
+
})
|
|
27
|
+
.join('\n');
|
|
28
|
+
return output.slice(0, 2000);
|
|
29
|
+
},
|
|
30
|
+
catch: (e) => new GrepSearchError({ pattern, cause: e }),
|
|
31
|
+
});
|
|
30
32
|
}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
}
|
|
33
|
+
function runGlob({ pattern, directory }) {
|
|
34
|
+
return errore.tryAsync({
|
|
35
|
+
try: async () => {
|
|
36
|
+
const files = await glob(pattern, {
|
|
37
|
+
cwd: directory,
|
|
38
|
+
nodir: false,
|
|
39
|
+
ignore: ['node_modules/**', '.git/**', 'dist/**', 'build/**'],
|
|
40
|
+
maxDepth: 10,
|
|
41
|
+
});
|
|
42
|
+
if (files.length === 0) {
|
|
43
|
+
return 'No files found';
|
|
44
|
+
}
|
|
45
|
+
return files.slice(0, 30).join('\n');
|
|
46
|
+
},
|
|
47
|
+
catch: (e) => new GlobSearchError({ pattern, cause: e }),
|
|
48
|
+
});
|
|
47
49
|
}
|
|
48
50
|
const grepToolDeclaration = {
|
|
49
51
|
name: 'grep',
|
|
@@ -99,14 +101,28 @@ function createToolRunner({ directory }) {
|
|
|
99
101
|
if (name === 'grep' && hasDirectory) {
|
|
100
102
|
const pattern = args?.pattern || '';
|
|
101
103
|
voiceLogger.log(`Grep search: "${pattern}"`);
|
|
102
|
-
const
|
|
104
|
+
const result = await runGrep({ pattern, directory });
|
|
105
|
+
const output = (() => {
|
|
106
|
+
if (errore.isError(result)) {
|
|
107
|
+
voiceLogger.error('grep search failed:', result);
|
|
108
|
+
return 'grep search failed';
|
|
109
|
+
}
|
|
110
|
+
return result;
|
|
111
|
+
})();
|
|
103
112
|
voiceLogger.log(`Grep result: ${output.slice(0, 100)}...`);
|
|
104
113
|
return { type: 'toolResponse', name: 'grep', output };
|
|
105
114
|
}
|
|
106
115
|
if (name === 'glob' && hasDirectory) {
|
|
107
116
|
const pattern = args?.pattern || '';
|
|
108
117
|
voiceLogger.log(`Glob search: "${pattern}"`);
|
|
109
|
-
const
|
|
118
|
+
const result = await runGlob({ pattern, directory });
|
|
119
|
+
const output = (() => {
|
|
120
|
+
if (errore.isError(result)) {
|
|
121
|
+
voiceLogger.error('glob search failed:', result);
|
|
122
|
+
return 'glob search failed';
|
|
123
|
+
}
|
|
124
|
+
return result;
|
|
125
|
+
})();
|
|
110
126
|
voiceLogger.log(`Glob result: ${output.slice(0, 100)}...`);
|
|
111
127
|
return { type: 'toolResponse', name: 'glob', output };
|
|
112
128
|
}
|
|
@@ -114,17 +130,25 @@ function createToolRunner({ directory }) {
|
|
|
114
130
|
};
|
|
115
131
|
}
|
|
116
132
|
export async function runTranscriptionLoop({ genAI, model, initialContents, tools, temperature, toolRunner, maxSteps = 10, }) {
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
133
|
+
// Wrap external API call that can throw
|
|
134
|
+
const initialResponse = await errore.tryAsync({
|
|
135
|
+
try: () => genAI.models.generateContent({
|
|
136
|
+
model,
|
|
137
|
+
contents: initialContents,
|
|
138
|
+
config: {
|
|
139
|
+
temperature,
|
|
140
|
+
thinkingConfig: {
|
|
141
|
+
thinkingBudget: 1024,
|
|
142
|
+
},
|
|
143
|
+
tools,
|
|
124
144
|
},
|
|
125
|
-
|
|
126
|
-
},
|
|
145
|
+
}),
|
|
146
|
+
catch: (e) => new TranscriptionError({ reason: `API call failed: ${String(e)}`, cause: e }),
|
|
127
147
|
});
|
|
148
|
+
if (errore.isError(initialResponse)) {
|
|
149
|
+
return initialResponse;
|
|
150
|
+
}
|
|
151
|
+
let response = initialResponse;
|
|
128
152
|
const conversationHistory = [...initialContents];
|
|
129
153
|
let stepsRemaining = maxSteps;
|
|
130
154
|
while (true) {
|
|
@@ -135,7 +159,7 @@ export async function runTranscriptionLoop({ genAI, model, initialContents, tool
|
|
|
135
159
|
voiceLogger.log(`No parts but got text response: "${text.slice(0, 100)}..."`);
|
|
136
160
|
return text;
|
|
137
161
|
}
|
|
138
|
-
|
|
162
|
+
return new NoResponseContentError();
|
|
139
163
|
}
|
|
140
164
|
const functionCalls = candidate.content.parts.filter((part) => 'functionCall' in part && !!part.functionCall);
|
|
141
165
|
if (functionCalls.length === 0) {
|
|
@@ -144,7 +168,7 @@ export async function runTranscriptionLoop({ genAI, model, initialContents, tool
|
|
|
144
168
|
voiceLogger.log(`No function calls but got text: "${text.slice(0, 100)}..."`);
|
|
145
169
|
return text;
|
|
146
170
|
}
|
|
147
|
-
|
|
171
|
+
return new TranscriptionError({ reason: 'Model did not produce a transcription' });
|
|
148
172
|
}
|
|
149
173
|
conversationHistory.push({
|
|
150
174
|
role: 'model',
|
|
@@ -159,7 +183,7 @@ export async function runTranscriptionLoop({ genAI, model, initialContents, tool
|
|
|
159
183
|
const transcription = result.transcription?.trim() || '';
|
|
160
184
|
voiceLogger.log(`Transcription result received: "${transcription.slice(0, 100)}..."`);
|
|
161
185
|
if (!transcription) {
|
|
162
|
-
|
|
186
|
+
return new EmptyTranscriptionError();
|
|
163
187
|
}
|
|
164
188
|
return transcription;
|
|
165
189
|
}
|
|
@@ -186,67 +210,76 @@ export async function runTranscriptionLoop({ genAI, model, initialContents, tool
|
|
|
186
210
|
}
|
|
187
211
|
}
|
|
188
212
|
if (functionResponseParts.length === 0) {
|
|
189
|
-
|
|
213
|
+
return new NoToolResponseError();
|
|
190
214
|
}
|
|
191
215
|
conversationHistory.push({
|
|
192
216
|
role: 'user',
|
|
193
217
|
parts: functionResponseParts,
|
|
194
218
|
});
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
219
|
+
// Wrap external API call that can throw
|
|
220
|
+
const nextResponse = await errore.tryAsync({
|
|
221
|
+
try: () => genAI.models.generateContent({
|
|
222
|
+
model,
|
|
223
|
+
contents: conversationHistory,
|
|
224
|
+
config: {
|
|
225
|
+
temperature,
|
|
226
|
+
thinkingConfig: {
|
|
227
|
+
thinkingBudget: 512,
|
|
228
|
+
},
|
|
229
|
+
tools: stepsRemaining <= 0
|
|
230
|
+
? [{ functionDeclarations: [transcriptionResultToolDeclaration] }]
|
|
231
|
+
: tools,
|
|
202
232
|
},
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
: tools,
|
|
206
|
-
},
|
|
233
|
+
}),
|
|
234
|
+
catch: (e) => new TranscriptionError({ reason: `API call failed: ${String(e)}`, cause: e }),
|
|
207
235
|
});
|
|
236
|
+
if (errore.isError(nextResponse)) {
|
|
237
|
+
return nextResponse;
|
|
238
|
+
}
|
|
239
|
+
response = nextResponse;
|
|
208
240
|
}
|
|
209
241
|
}
|
|
210
|
-
export
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
let audioBase64;
|
|
242
|
+
export function transcribeAudio({ audio, prompt, language, temperature, geminiApiKey, directory, currentSessionContext, lastSessionContext, }) {
|
|
243
|
+
const apiKey = geminiApiKey || process.env.GEMINI_API_KEY;
|
|
244
|
+
if (!apiKey) {
|
|
245
|
+
return Promise.resolve(new ApiKeyMissingError({ service: 'Gemini' }));
|
|
246
|
+
}
|
|
247
|
+
const genAI = new GoogleGenAI({ apiKey });
|
|
248
|
+
const audioBase64 = (() => {
|
|
218
249
|
if (typeof audio === 'string') {
|
|
219
|
-
|
|
220
|
-
}
|
|
221
|
-
else if (audio instanceof Buffer) {
|
|
222
|
-
audioBase64 = audio.toString('base64');
|
|
250
|
+
return audio;
|
|
223
251
|
}
|
|
224
|
-
|
|
225
|
-
|
|
252
|
+
if (audio instanceof Buffer) {
|
|
253
|
+
return audio.toString('base64');
|
|
226
254
|
}
|
|
227
|
-
|
|
228
|
-
|
|
255
|
+
if (audio instanceof Uint8Array) {
|
|
256
|
+
return Buffer.from(audio).toString('base64');
|
|
229
257
|
}
|
|
230
|
-
|
|
231
|
-
|
|
258
|
+
if (audio instanceof ArrayBuffer) {
|
|
259
|
+
return Buffer.from(audio).toString('base64');
|
|
232
260
|
}
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
261
|
+
return '';
|
|
262
|
+
})();
|
|
263
|
+
if (!audioBase64) {
|
|
264
|
+
return Promise.resolve(new InvalidAudioFormatError());
|
|
265
|
+
}
|
|
266
|
+
const languageHint = language ? `The audio is in ${language}.\n\n` : '';
|
|
267
|
+
// build session context section
|
|
268
|
+
const sessionContextParts = [];
|
|
269
|
+
if (lastSessionContext) {
|
|
270
|
+
sessionContextParts.push(`<last_session>
|
|
238
271
|
${lastSessionContext}
|
|
239
272
|
</last_session>`);
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
273
|
+
}
|
|
274
|
+
if (currentSessionContext) {
|
|
275
|
+
sessionContextParts.push(`<current_session>
|
|
243
276
|
${currentSessionContext}
|
|
244
277
|
</current_session>`);
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
278
|
+
}
|
|
279
|
+
const sessionContextSection = sessionContextParts.length > 0
|
|
280
|
+
? `\nSession context (use to understand references to files, functions, tools used):\n${sessionContextParts.join('\n\n')}`
|
|
281
|
+
: '';
|
|
282
|
+
const transcriptionPrompt = `${languageHint}Transcribe this audio for a coding agent (like Claude Code or OpenCode).
|
|
250
283
|
|
|
251
284
|
CRITICAL REQUIREMENT: You MUST call the "transcriptionResult" tool to complete this task.
|
|
252
285
|
- The transcriptionResult tool is the ONLY way to return results
|
|
@@ -275,42 +308,37 @@ ${sessionContextSection}
|
|
|
275
308
|
REMEMBER: Call "transcriptionResult" tool with your transcription. This is mandatory.
|
|
276
309
|
|
|
277
310
|
Note: "critique" is a CLI tool for showing diffs in the browser.`;
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
},
|
|
311
|
+
// const hasDirectory = directory && directory.trim().length > 0
|
|
312
|
+
const tools = [
|
|
313
|
+
{
|
|
314
|
+
functionDeclarations: [
|
|
315
|
+
transcriptionResultToolDeclaration,
|
|
316
|
+
// grep/glob disabled - was causing transcription to hang
|
|
317
|
+
// ...(hasDirectory ? [grepToolDeclaration, globToolDeclaration] : []),
|
|
318
|
+
],
|
|
319
|
+
},
|
|
320
|
+
];
|
|
321
|
+
const initialContents = [
|
|
322
|
+
{
|
|
323
|
+
role: 'user',
|
|
324
|
+
parts: [
|
|
325
|
+
{ text: transcriptionPrompt },
|
|
326
|
+
{
|
|
327
|
+
inlineData: {
|
|
328
|
+
data: audioBase64,
|
|
329
|
+
mimeType: 'audio/mpeg',
|
|
298
330
|
},
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
}
|
|
312
|
-
catch (error) {
|
|
313
|
-
voiceLogger.error('Failed to transcribe audio:', error);
|
|
314
|
-
throw new Error(`Audio transcription failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
315
|
-
}
|
|
331
|
+
},
|
|
332
|
+
],
|
|
333
|
+
},
|
|
334
|
+
];
|
|
335
|
+
const toolRunner = createToolRunner({ directory });
|
|
336
|
+
return runTranscriptionLoop({
|
|
337
|
+
genAI,
|
|
338
|
+
model: 'gemini-2.5-flash',
|
|
339
|
+
initialContents,
|
|
340
|
+
tools,
|
|
341
|
+
temperature: temperature ?? 0.3,
|
|
342
|
+
toolRunner,
|
|
343
|
+
});
|
|
316
344
|
}
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "kimaki",
|
|
3
3
|
"module": "index.ts",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"version": "0.4.
|
|
5
|
+
"version": "0.4.39",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"dev": "tsx --env-file .env src/cli.ts",
|
|
8
8
|
"prepublishOnly": "pnpm tsc",
|
|
@@ -41,6 +41,7 @@
|
|
|
41
41
|
"cac": "^6.7.14",
|
|
42
42
|
"discord.js": "^14.16.3",
|
|
43
43
|
"domhandler": "^5.0.3",
|
|
44
|
+
"errore": "^0.5.2",
|
|
44
45
|
"glob": "^13.0.0",
|
|
45
46
|
"htmlparser2": "^10.0.0",
|
|
46
47
|
"js-yaml": "^4.1.0",
|
package/src/cli.ts
CHANGED
|
@@ -40,6 +40,7 @@ import {
|
|
|
40
40
|
} from 'discord.js'
|
|
41
41
|
import path from 'node:path'
|
|
42
42
|
import fs from 'node:fs'
|
|
43
|
+
import * as errore from 'errore'
|
|
43
44
|
|
|
44
45
|
import { createLogger } from './logger.js'
|
|
45
46
|
import { spawn, spawnSync, execSync, type ExecSyncOptions } from 'node:child_process'
|
|
@@ -181,6 +182,7 @@ type AgentInfo = {
|
|
|
181
182
|
name: string
|
|
182
183
|
description?: string
|
|
183
184
|
mode: string
|
|
185
|
+
hidden?: boolean
|
|
184
186
|
}
|
|
185
187
|
|
|
186
188
|
async function registerCommands({
|
|
@@ -343,8 +345,10 @@ async function registerCommands({
|
|
|
343
345
|
}
|
|
344
346
|
|
|
345
347
|
// Add agent-specific quick commands like /plan-agent, /build-agent
|
|
346
|
-
// Filter to primary/all mode agents (same as /agent command shows)
|
|
347
|
-
const primaryAgents = agents.filter(
|
|
348
|
+
// Filter to primary/all mode agents (same as /agent command shows), excluding hidden agents
|
|
349
|
+
const primaryAgents = agents.filter(
|
|
350
|
+
(a) => (a.mode === 'primary' || a.mode === 'all') && !a.hidden,
|
|
351
|
+
)
|
|
348
352
|
for (const agent of primaryAgents) {
|
|
349
353
|
const sanitizedName = sanitizeAgentName(agent.name)
|
|
350
354
|
const commandName = `${sanitizedName}-agent`
|
|
@@ -577,7 +581,12 @@ async function run({ restart, addChannels }: CliOptions) {
|
|
|
577
581
|
// This is the biggest startup bottleneck (can take 1-30 seconds to spawn and wait for ready)
|
|
578
582
|
const currentDir = process.cwd()
|
|
579
583
|
s.start('Starting OpenCode server...')
|
|
580
|
-
const opencodePromise = initializeOpencodeForDirectory(currentDir)
|
|
584
|
+
const opencodePromise = initializeOpencodeForDirectory(currentDir).then((result) => {
|
|
585
|
+
if (errore.isError(result)) {
|
|
586
|
+
throw new Error(result.message)
|
|
587
|
+
}
|
|
588
|
+
return result
|
|
589
|
+
})
|
|
581
590
|
|
|
582
591
|
s.message('Connecting to Discord...')
|
|
583
592
|
const discordClient = await createDiscordClient()
|
package/src/commands/abort.ts
CHANGED
|
@@ -7,6 +7,7 @@ import { initializeOpencodeForDirectory } from '../opencode.js'
|
|
|
7
7
|
import { resolveTextChannel, getKimakiMetadata, SILENT_MESSAGE_FLAGS } from '../discord-utils.js'
|
|
8
8
|
import { abortControllers } from '../session-handler.js'
|
|
9
9
|
import { createLogger } from '../logger.js'
|
|
10
|
+
import * as errore from 'errore'
|
|
10
11
|
|
|
11
12
|
const logger = createLogger('ABORT')
|
|
12
13
|
|
|
@@ -64,14 +65,23 @@ export async function handleAbortCommand({ command }: CommandContext): Promise<v
|
|
|
64
65
|
|
|
65
66
|
const sessionId = row.session_id
|
|
66
67
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
68
|
+
const existingController = abortControllers.get(sessionId)
|
|
69
|
+
if (existingController) {
|
|
70
|
+
existingController.abort(new Error('User requested abort'))
|
|
71
|
+
abortControllers.delete(sessionId)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const getClient = await initializeOpencodeForDirectory(directory)
|
|
75
|
+
if (errore.isError(getClient)) {
|
|
76
|
+
await command.reply({
|
|
77
|
+
content: `Failed to abort: ${getClient.message}`,
|
|
78
|
+
ephemeral: true,
|
|
79
|
+
flags: SILENT_MESSAGE_FLAGS,
|
|
80
|
+
})
|
|
81
|
+
return
|
|
82
|
+
}
|
|
73
83
|
|
|
74
|
-
|
|
84
|
+
try {
|
|
75
85
|
await getClient().session.abort({
|
|
76
86
|
path: { id: sessionId },
|
|
77
87
|
})
|
|
@@ -8,6 +8,7 @@ import { initializeOpencodeForDirectory } from '../opencode.js'
|
|
|
8
8
|
import { createProjectChannels } from '../channel-management.js'
|
|
9
9
|
import { createLogger } from '../logger.js'
|
|
10
10
|
import { abbreviatePath } from '../utils.js'
|
|
11
|
+
import * as errore from 'errore'
|
|
11
12
|
|
|
12
13
|
const logger = createLogger('ADD-PROJECT')
|
|
13
14
|
|
|
@@ -25,6 +26,10 @@ export async function handleAddProjectCommand({ command, appId }: CommandContext
|
|
|
25
26
|
try {
|
|
26
27
|
const currentDir = process.cwd()
|
|
27
28
|
const getClient = await initializeOpencodeForDirectory(currentDir)
|
|
29
|
+
if (errore.isError(getClient)) {
|
|
30
|
+
await command.editReply(getClient.message)
|
|
31
|
+
return
|
|
32
|
+
}
|
|
28
33
|
|
|
29
34
|
const projectsResponse = await getClient().project.list({})
|
|
30
35
|
if (!projectsResponse.data) {
|
|
@@ -89,6 +94,10 @@ export async function handleAddProjectAutocomplete({
|
|
|
89
94
|
try {
|
|
90
95
|
const currentDir = process.cwd()
|
|
91
96
|
const getClient = await initializeOpencodeForDirectory(currentDir)
|
|
97
|
+
if (errore.isError(getClient)) {
|
|
98
|
+
await interaction.respond([])
|
|
99
|
+
return
|
|
100
|
+
}
|
|
92
101
|
|
|
93
102
|
const projectsResponse = await getClient().project.list({})
|
|
94
103
|
if (!projectsResponse.data) {
|
package/src/commands/agent.ts
CHANGED
|
@@ -15,6 +15,7 @@ import { getDatabase, setChannelAgent, setSessionAgent, clearSessionModel, runMo
|
|
|
15
15
|
import { initializeOpencodeForDirectory } from '../opencode.js'
|
|
16
16
|
import { resolveTextChannel, getKimakiMetadata } from '../discord-utils.js'
|
|
17
17
|
import { createLogger } from '../logger.js'
|
|
18
|
+
import * as errore from 'errore'
|
|
18
19
|
|
|
19
20
|
const agentLogger = createLogger('AGENT')
|
|
20
21
|
|
|
@@ -161,6 +162,10 @@ export async function handleAgentCommand({
|
|
|
161
162
|
|
|
162
163
|
try {
|
|
163
164
|
const getClient = await initializeOpencodeForDirectory(context.dir)
|
|
165
|
+
if (errore.isError(getClient)) {
|
|
166
|
+
await interaction.editReply({ content: getClient.message })
|
|
167
|
+
return
|
|
168
|
+
}
|
|
164
169
|
|
|
165
170
|
const agentsResponse = await getClient().app.agents({
|
|
166
171
|
query: { directory: context.dir },
|
|
@@ -172,7 +177,10 @@ export async function handleAgentCommand({
|
|
|
172
177
|
}
|
|
173
178
|
|
|
174
179
|
const agents = agentsResponse.data
|
|
175
|
-
.filter((
|
|
180
|
+
.filter((agent) => {
|
|
181
|
+
const hidden = (agent as { hidden?: boolean }).hidden
|
|
182
|
+
return (agent.mode === 'primary' || agent.mode === 'all') && !hidden
|
|
183
|
+
})
|
|
176
184
|
.slice(0, 25)
|
|
177
185
|
|
|
178
186
|
if (agents.length === 0) {
|
|
@@ -289,6 +297,10 @@ export async function handleQuickAgentCommand({
|
|
|
289
297
|
|
|
290
298
|
try {
|
|
291
299
|
const getClient = await initializeOpencodeForDirectory(context.dir)
|
|
300
|
+
if (errore.isError(getClient)) {
|
|
301
|
+
await command.editReply({ content: getClient.message })
|
|
302
|
+
return
|
|
303
|
+
}
|
|
292
304
|
|
|
293
305
|
const agentsResponse = await getClient().app.agents({
|
|
294
306
|
query: { directory: context.dir },
|
package/src/commands/fork.ts
CHANGED
|
@@ -14,6 +14,7 @@ import { initializeOpencodeForDirectory } from '../opencode.js'
|
|
|
14
14
|
import { resolveTextChannel, getKimakiMetadata, sendThreadMessage } from '../discord-utils.js'
|
|
15
15
|
import { collectLastAssistantParts } from '../message-formatting.js'
|
|
16
16
|
import { createLogger } from '../logger.js'
|
|
17
|
+
import * as errore from 'errore'
|
|
17
18
|
|
|
18
19
|
const sessionLogger = createLogger('SESSION')
|
|
19
20
|
const forkLogger = createLogger('FORK')
|
|
@@ -71,9 +72,15 @@ export async function handleForkCommand(interaction: ChatInputCommandInteraction
|
|
|
71
72
|
|
|
72
73
|
const sessionId = row.session_id
|
|
73
74
|
|
|
74
|
-
|
|
75
|
-
|
|
75
|
+
const getClient = await initializeOpencodeForDirectory(directory)
|
|
76
|
+
if (errore.isError(getClient)) {
|
|
77
|
+
await interaction.editReply({
|
|
78
|
+
content: `Failed to load messages: ${getClient.message}`,
|
|
79
|
+
})
|
|
80
|
+
return
|
|
81
|
+
}
|
|
76
82
|
|
|
83
|
+
try {
|
|
77
84
|
const messagesResponse = await getClient().session.messages({
|
|
78
85
|
path: { id: sessionId },
|
|
79
86
|
})
|
|
@@ -85,7 +92,7 @@ export async function handleForkCommand(interaction: ChatInputCommandInteraction
|
|
|
85
92
|
return
|
|
86
93
|
}
|
|
87
94
|
|
|
88
|
-
const userMessages = messagesResponse.data.filter((m) => m.info.role === 'user')
|
|
95
|
+
const userMessages = messagesResponse.data.filter((m: { info: { role: string } }) => m.info.role === 'user')
|
|
89
96
|
|
|
90
97
|
if (userMessages.length === 0) {
|
|
91
98
|
await interaction.editReply({
|
|
@@ -96,8 +103,8 @@ export async function handleForkCommand(interaction: ChatInputCommandInteraction
|
|
|
96
103
|
|
|
97
104
|
const recentMessages = userMessages.slice(-25)
|
|
98
105
|
|
|
99
|
-
const options = recentMessages.map((m, index) => {
|
|
100
|
-
const textPart = m.parts.find((p) => p.type === 'text') as
|
|
106
|
+
const options = recentMessages.map((m: { parts: Array<{ type: string; text?: string }>; info: { id: string; time: { created: number } } }, index: number) => {
|
|
107
|
+
const textPart = m.parts.find((p: { type: string }) => p.type === 'text') as
|
|
101
108
|
| { type: 'text'; text: string }
|
|
102
109
|
| undefined
|
|
103
110
|
const preview = textPart?.text?.slice(0, 80) || '(no text)'
|
|
@@ -163,9 +170,13 @@ export async function handleForkSelectMenu(
|
|
|
163
170
|
|
|
164
171
|
await interaction.deferReply({ ephemeral: false })
|
|
165
172
|
|
|
166
|
-
|
|
167
|
-
|
|
173
|
+
const getClient = await initializeOpencodeForDirectory(directory)
|
|
174
|
+
if (errore.isError(getClient)) {
|
|
175
|
+
await interaction.editReply(`Failed to fork session: ${getClient.message}`)
|
|
176
|
+
return
|
|
177
|
+
}
|
|
168
178
|
|
|
179
|
+
try {
|
|
169
180
|
const forkResponse = await getClient().session.fork({
|
|
170
181
|
path: { id: sessionId },
|
|
171
182
|
body: { messageID: selectedMessageId },
|
package/src/commands/model.ts
CHANGED
|
@@ -15,6 +15,7 @@ import { initializeOpencodeForDirectory } from '../opencode.js'
|
|
|
15
15
|
import { resolveTextChannel, getKimakiMetadata } from '../discord-utils.js'
|
|
16
16
|
import { abortAndRetrySession } from '../session-handler.js'
|
|
17
17
|
import { createLogger } from '../logger.js'
|
|
18
|
+
import * as errore from 'errore'
|
|
18
19
|
|
|
19
20
|
const modelLogger = createLogger('MODEL')
|
|
20
21
|
|
|
@@ -128,6 +129,10 @@ export async function handleModelCommand({
|
|
|
128
129
|
|
|
129
130
|
try {
|
|
130
131
|
const getClient = await initializeOpencodeForDirectory(projectDirectory)
|
|
132
|
+
if (errore.isError(getClient)) {
|
|
133
|
+
await interaction.editReply({ content: getClient.message })
|
|
134
|
+
return
|
|
135
|
+
}
|
|
131
136
|
|
|
132
137
|
const providersResponse = await getClient().provider.list({
|
|
133
138
|
query: { directory: projectDirectory },
|
|
@@ -232,6 +237,13 @@ export async function handleProviderSelectMenu(
|
|
|
232
237
|
|
|
233
238
|
try {
|
|
234
239
|
const getClient = await initializeOpencodeForDirectory(context.dir)
|
|
240
|
+
if (errore.isError(getClient)) {
|
|
241
|
+
await interaction.editReply({
|
|
242
|
+
content: getClient.message,
|
|
243
|
+
components: [],
|
|
244
|
+
})
|
|
245
|
+
return
|
|
246
|
+
}
|
|
235
247
|
|
|
236
248
|
const providersResponse = await getClient().provider.list({
|
|
237
249
|
query: { directory: context.dir },
|