kimaki 0.4.39 → 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 +19 -21
- package/dist/commands/abort.js +1 -1
- package/dist/commands/add-project.js +2 -2
- package/dist/commands/agent.js +2 -2
- package/dist/commands/fork.js +2 -2
- package/dist/commands/model.js +2 -2
- package/dist/commands/remove-project.js +2 -2
- package/dist/commands/resume.js +2 -2
- package/dist/commands/session.js +4 -4
- package/dist/commands/share.js +1 -1
- package/dist/commands/undo-redo.js +2 -2
- package/dist/commands/worktree.js +180 -0
- package/dist/database.js +49 -1
- package/dist/discord-bot.js +29 -4
- package/dist/discord-utils.js +36 -0
- package/dist/errors.js +86 -87
- package/dist/genai-worker.js +1 -1
- package/dist/interaction-handler.js +6 -2
- package/dist/markdown.js +5 -1
- package/dist/message-formatting.js +2 -2
- package/dist/opencode.js +4 -4
- package/dist/session-handler.js +2 -2
- package/dist/tools.js +3 -3
- package/dist/voice-handler.js +3 -3
- package/dist/voice.js +4 -4
- package/package.json +4 -3
- package/src/cli.ts +20 -30
- package/src/commands/abort.ts +1 -1
- package/src/commands/add-project.ts +2 -2
- package/src/commands/agent.ts +2 -2
- package/src/commands/fork.ts +2 -2
- package/src/commands/model.ts +2 -2
- package/src/commands/remove-project.ts +2 -2
- package/src/commands/resume.ts +2 -2
- package/src/commands/session.ts +4 -4
- package/src/commands/share.ts +1 -1
- package/src/commands/undo-redo.ts +2 -2
- package/src/commands/worktree.ts +243 -0
- package/src/database.ts +96 -1
- package/src/discord-bot.ts +30 -4
- package/src/discord-utils.ts +50 -0
- package/src/errors.ts +90 -160
- package/src/genai-worker.ts +1 -1
- package/src/interaction-handler.ts +7 -2
- package/src/markdown.ts +5 -4
- package/src/message-formatting.ts +2 -2
- package/src/opencode.ts +4 -4
- package/src/session-handler.ts +2 -2
- package/src/tools.ts +3 -3
- package/src/voice-handler.ts +3 -3
- package/src/voice.ts +4 -4
package/dist/errors.js
CHANGED
|
@@ -1,110 +1,109 @@
|
|
|
1
1
|
// TaggedError definitions for type-safe error handling with errore.
|
|
2
2
|
// Errors are grouped by category: infrastructure, domain, and validation.
|
|
3
3
|
// Use errore.matchError() for exhaustive error handling in command handlers.
|
|
4
|
-
import
|
|
4
|
+
import { createTaggedError } from 'errore';
|
|
5
5
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
6
6
|
// INFRASTRUCTURE ERRORS - Server, filesystem, external services
|
|
7
7
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
8
|
-
export class DirectoryNotAccessibleError extends
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
}
|
|
13
|
-
export class ServerStartError extends
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
}
|
|
18
|
-
export class ServerNotFoundError extends
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
}
|
|
23
|
-
export class ServerNotReadyError extends
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
constructor(args) {
|
|
33
|
-
super({ ...args, message: `${args.service} API key is required` });
|
|
34
|
-
}
|
|
8
|
+
export class DirectoryNotAccessibleError extends createTaggedError({
|
|
9
|
+
name: 'DirectoryNotAccessibleError',
|
|
10
|
+
message: 'Directory does not exist or is not accessible: $directory',
|
|
11
|
+
}) {
|
|
12
|
+
}
|
|
13
|
+
export class ServerStartError extends createTaggedError({
|
|
14
|
+
name: 'ServerStartError',
|
|
15
|
+
message: 'Server failed to start on port $port: $reason',
|
|
16
|
+
}) {
|
|
17
|
+
}
|
|
18
|
+
export class ServerNotFoundError extends createTaggedError({
|
|
19
|
+
name: 'ServerNotFoundError',
|
|
20
|
+
message: 'OpenCode server not found for directory: $directory',
|
|
21
|
+
}) {
|
|
22
|
+
}
|
|
23
|
+
export class ServerNotReadyError extends createTaggedError({
|
|
24
|
+
name: 'ServerNotReadyError',
|
|
25
|
+
message: 'OpenCode server for directory "$directory" is in an error state (no client available)',
|
|
26
|
+
}) {
|
|
27
|
+
}
|
|
28
|
+
export class ApiKeyMissingError extends createTaggedError({
|
|
29
|
+
name: 'ApiKeyMissingError',
|
|
30
|
+
message: '$service API key is required',
|
|
31
|
+
}) {
|
|
35
32
|
}
|
|
36
33
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
37
34
|
// DOMAIN ERRORS - Sessions, messages, transcription
|
|
38
35
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
39
|
-
export class SessionNotFoundError extends
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
}
|
|
44
|
-
export class SessionCreateError extends
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
36
|
+
export class SessionNotFoundError extends createTaggedError({
|
|
37
|
+
name: 'SessionNotFoundError',
|
|
38
|
+
message: 'Session $sessionId not found',
|
|
39
|
+
}) {
|
|
40
|
+
}
|
|
41
|
+
export class SessionCreateError extends createTaggedError({
|
|
42
|
+
name: 'SessionCreateError',
|
|
43
|
+
message: '$message',
|
|
44
|
+
}) {
|
|
45
|
+
}
|
|
46
|
+
export class MessagesNotFoundError extends createTaggedError({
|
|
47
|
+
name: 'MessagesNotFoundError',
|
|
48
|
+
message: 'No messages found for session $sessionId',
|
|
49
|
+
}) {
|
|
50
|
+
}
|
|
51
|
+
export class TranscriptionError extends createTaggedError({
|
|
52
|
+
name: 'TranscriptionError',
|
|
53
|
+
message: 'Transcription failed: $reason',
|
|
54
|
+
}) {
|
|
55
|
+
}
|
|
56
|
+
export class GrepSearchError extends createTaggedError({
|
|
57
|
+
name: 'GrepSearchError',
|
|
58
|
+
message: 'Grep search failed for pattern: $pattern',
|
|
59
|
+
}) {
|
|
60
|
+
}
|
|
61
|
+
export class GlobSearchError extends createTaggedError({
|
|
62
|
+
name: 'GlobSearchError',
|
|
63
|
+
message: 'Glob search failed for pattern: $pattern',
|
|
64
|
+
}) {
|
|
65
65
|
}
|
|
66
66
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
67
67
|
// VALIDATION ERRORS - Input validation, format checks
|
|
68
68
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
69
|
-
export class InvalidAudioFormatError extends
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
}
|
|
74
|
-
export class EmptyTranscriptionError extends
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
}
|
|
79
|
-
export class NoResponseContentError extends
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
}
|
|
84
|
-
export class NoToolResponseError extends
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
69
|
+
export class InvalidAudioFormatError extends createTaggedError({
|
|
70
|
+
name: 'InvalidAudioFormatError',
|
|
71
|
+
message: 'Invalid audio format',
|
|
72
|
+
}) {
|
|
73
|
+
}
|
|
74
|
+
export class EmptyTranscriptionError extends createTaggedError({
|
|
75
|
+
name: 'EmptyTranscriptionError',
|
|
76
|
+
message: 'Model returned empty transcription',
|
|
77
|
+
}) {
|
|
78
|
+
}
|
|
79
|
+
export class NoResponseContentError extends createTaggedError({
|
|
80
|
+
name: 'NoResponseContentError',
|
|
81
|
+
message: 'No response content from model',
|
|
82
|
+
}) {
|
|
83
|
+
}
|
|
84
|
+
export class NoToolResponseError extends createTaggedError({
|
|
85
|
+
name: 'NoToolResponseError',
|
|
86
|
+
message: 'No valid tool responses',
|
|
87
|
+
}) {
|
|
88
88
|
}
|
|
89
89
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
90
90
|
// NETWORK ERRORS - Fetch and HTTP
|
|
91
91
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
92
|
-
export class FetchError extends
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
}
|
|
92
|
+
export class FetchError extends createTaggedError({
|
|
93
|
+
name: 'FetchError',
|
|
94
|
+
message: 'Fetch failed for $url',
|
|
95
|
+
}) {
|
|
97
96
|
}
|
|
98
97
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
99
98
|
// API ERRORS - External service responses
|
|
100
99
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
101
|
-
export class DiscordApiError extends
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
}
|
|
106
|
-
export class OpenCodeApiError extends
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
100
|
+
export class DiscordApiError extends createTaggedError({
|
|
101
|
+
name: 'DiscordApiError',
|
|
102
|
+
message: 'Discord API error: $status $body',
|
|
103
|
+
}) {
|
|
104
|
+
}
|
|
105
|
+
export class OpenCodeApiError extends createTaggedError({
|
|
106
|
+
name: 'OpenCodeApiError',
|
|
107
|
+
message: 'OpenCode API error ($status): $body',
|
|
108
|
+
}) {
|
|
110
109
|
}
|
package/dist/genai-worker.js
CHANGED
|
@@ -103,7 +103,7 @@ async function createAssistantAudioLogStream(guildId, channelId) {
|
|
|
103
103
|
try: () => mkdir(audioDir, { recursive: true }),
|
|
104
104
|
catch: (e) => e,
|
|
105
105
|
});
|
|
106
|
-
if (
|
|
106
|
+
if (mkdirError instanceof Error) {
|
|
107
107
|
workerLogger.error(`Failed to create audio log directory:`, mkdirError.message);
|
|
108
108
|
return null;
|
|
109
109
|
}
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
// and manages autocomplete, select menu interactions for the bot.
|
|
4
4
|
import { Events } from 'discord.js';
|
|
5
5
|
import { handleSessionCommand, handleSessionAutocomplete } from './commands/session.js';
|
|
6
|
+
import { handleNewWorktreeCommand } from './commands/worktree.js';
|
|
6
7
|
import { handleResumeCommand, handleResumeAutocomplete } from './commands/resume.js';
|
|
7
8
|
import { handleAddProjectCommand, handleAddProjectAutocomplete } from './commands/add-project.js';
|
|
8
9
|
import { handleRemoveProjectCommand, handleRemoveProjectAutocomplete, } from './commands/remove-project.js';
|
|
@@ -30,7 +31,7 @@ export function registerInteractionHandler({ discordClient, appId, }) {
|
|
|
30
31
|
: 'other'}`);
|
|
31
32
|
if (interaction.isAutocomplete()) {
|
|
32
33
|
switch (interaction.commandName) {
|
|
33
|
-
case 'session':
|
|
34
|
+
case 'new-session':
|
|
34
35
|
await handleSessionAutocomplete({ interaction, appId });
|
|
35
36
|
return;
|
|
36
37
|
case 'resume':
|
|
@@ -50,9 +51,12 @@ export function registerInteractionHandler({ discordClient, appId, }) {
|
|
|
50
51
|
if (interaction.isChatInputCommand()) {
|
|
51
52
|
interactionLogger.log(`[COMMAND] Processing: ${interaction.commandName}`);
|
|
52
53
|
switch (interaction.commandName) {
|
|
53
|
-
case 'session':
|
|
54
|
+
case 'new-session':
|
|
54
55
|
await handleSessionCommand({ command: interaction, appId });
|
|
55
56
|
return;
|
|
57
|
+
case 'new-worktree':
|
|
58
|
+
await handleNewWorktreeCommand({ command: interaction, appId });
|
|
59
|
+
return;
|
|
56
60
|
case 'resume':
|
|
57
61
|
await handleResumeCommand({ command: interaction, appId });
|
|
58
62
|
return;
|
package/dist/markdown.js
CHANGED
|
@@ -3,13 +3,17 @@
|
|
|
3
3
|
// user messages, assistant responses, tool calls, and reasoning blocks.
|
|
4
4
|
// Uses errore for type-safe error handling.
|
|
5
5
|
import * as errore from 'errore';
|
|
6
|
+
import { createTaggedError } from 'errore';
|
|
6
7
|
import * as yaml from 'js-yaml';
|
|
7
8
|
import { formatDateTime } from './utils.js';
|
|
8
9
|
import { extractNonXmlContent } from './xml.js';
|
|
9
10
|
import { createLogger } from './logger.js';
|
|
10
11
|
import { SessionNotFoundError, MessagesNotFoundError } from './errors.js';
|
|
11
12
|
// Generic error for unexpected exceptions in async operations
|
|
12
|
-
class UnexpectedError extends
|
|
13
|
+
class UnexpectedError extends createTaggedError({
|
|
14
|
+
name: 'UnexpectedError',
|
|
15
|
+
message: '$message',
|
|
16
|
+
}) {
|
|
13
17
|
}
|
|
14
18
|
const markdownLogger = createLogger('MARKDOWN');
|
|
15
19
|
export class ShareMarkdown {
|
|
@@ -62,7 +62,7 @@ export async function getTextAttachments(message) {
|
|
|
62
62
|
try: () => fetch(attachment.url),
|
|
63
63
|
catch: (e) => new FetchError({ url: attachment.url, cause: e }),
|
|
64
64
|
});
|
|
65
|
-
if (
|
|
65
|
+
if (response instanceof Error) {
|
|
66
66
|
return `<attachment filename="${attachment.name}" error="${response.message}" />`;
|
|
67
67
|
}
|
|
68
68
|
if (!response.ok) {
|
|
@@ -90,7 +90,7 @@ export async function getFileAttachments(message) {
|
|
|
90
90
|
try: () => fetch(attachment.url),
|
|
91
91
|
catch: (e) => new FetchError({ url: attachment.url, cause: e }),
|
|
92
92
|
});
|
|
93
|
-
if (
|
|
93
|
+
if (response instanceof Error) {
|
|
94
94
|
logger.error(`Error downloading attachment ${attachment.name}:`, response.message);
|
|
95
95
|
return null;
|
|
96
96
|
}
|
package/dist/opencode.js
CHANGED
|
@@ -43,7 +43,7 @@ async function waitForServer(port, maxAttempts = 30) {
|
|
|
43
43
|
try: () => fetch(endpoint),
|
|
44
44
|
catch: (e) => new FetchError({ url: endpoint, cause: e }),
|
|
45
45
|
});
|
|
46
|
-
if (
|
|
46
|
+
if (response instanceof Error) {
|
|
47
47
|
// Connection refused or other transient errors - continue polling
|
|
48
48
|
opencodeLogger.debug(`Server polling attempt failed: ${response.message}`);
|
|
49
49
|
continue;
|
|
@@ -80,7 +80,7 @@ export async function initializeOpencodeForDirectory(directory) {
|
|
|
80
80
|
},
|
|
81
81
|
catch: () => new DirectoryNotAccessibleError({ directory }),
|
|
82
82
|
});
|
|
83
|
-
if (
|
|
83
|
+
if (accessCheck instanceof Error) {
|
|
84
84
|
return accessCheck;
|
|
85
85
|
}
|
|
86
86
|
const port = await getOpenPort();
|
|
@@ -125,7 +125,7 @@ export async function initializeOpencodeForDirectory(directory) {
|
|
|
125
125
|
serverRetryCount.set(directory, retryCount + 1);
|
|
126
126
|
opencodeLogger.log(`Restarting server for directory: ${directory} (attempt ${retryCount + 1}/5)`);
|
|
127
127
|
initializeOpencodeForDirectory(directory).then((result) => {
|
|
128
|
-
if (
|
|
128
|
+
if (result instanceof Error) {
|
|
129
129
|
opencodeLogger.error(`Failed to restart opencode server:`, result);
|
|
130
130
|
}
|
|
131
131
|
});
|
|
@@ -139,7 +139,7 @@ export async function initializeOpencodeForDirectory(directory) {
|
|
|
139
139
|
}
|
|
140
140
|
});
|
|
141
141
|
const waitResult = await waitForServer(port);
|
|
142
|
-
if (
|
|
142
|
+
if (waitResult instanceof Error) {
|
|
143
143
|
// Dump buffered logs on failure
|
|
144
144
|
opencodeLogger.error(`Server failed to start for ${directory}:`);
|
|
145
145
|
for (const line of logBuffer) {
|
package/dist/session-handler.js
CHANGED
|
@@ -49,7 +49,7 @@ export async function abortAndRetrySession({ sessionId, thread, projectDirectory
|
|
|
49
49
|
controller.abort('model-change');
|
|
50
50
|
// Also call the API abort endpoint
|
|
51
51
|
const getClient = await initializeOpencodeForDirectory(projectDirectory);
|
|
52
|
-
if (
|
|
52
|
+
if (getClient instanceof Error) {
|
|
53
53
|
sessionLogger.error(`[ABORT+RETRY] Failed to initialize OpenCode client:`, getClient.message);
|
|
54
54
|
return false;
|
|
55
55
|
}
|
|
@@ -98,7 +98,7 @@ export async function handleOpencodeSession({ prompt, thread, projectDirectory,
|
|
|
98
98
|
const directory = projectDirectory || process.cwd();
|
|
99
99
|
sessionLogger.log(`Using directory: ${directory}`);
|
|
100
100
|
const getClient = await initializeOpencodeForDirectory(directory);
|
|
101
|
-
if (
|
|
101
|
+
if (getClient instanceof Error) {
|
|
102
102
|
await sendThreadMessage(thread, `✗ ${getClient.message}`);
|
|
103
103
|
return;
|
|
104
104
|
}
|
package/dist/tools.js
CHANGED
|
@@ -15,7 +15,7 @@ import pc from 'picocolors';
|
|
|
15
15
|
import { initializeOpencodeForDirectory, getOpencodeSystemMessage } from './discord-bot.js';
|
|
16
16
|
export async function getTools({ onMessageCompleted, directory, }) {
|
|
17
17
|
const getClient = await initializeOpencodeForDirectory(directory);
|
|
18
|
-
if (
|
|
18
|
+
if (getClient instanceof Error) {
|
|
19
19
|
throw new Error(getClient.message);
|
|
20
20
|
}
|
|
21
21
|
const client = getClient();
|
|
@@ -248,7 +248,7 @@ export async function getTools({ onMessageCompleted, directory, }) {
|
|
|
248
248
|
sessionID: sessionId,
|
|
249
249
|
lastAssistantOnly: true,
|
|
250
250
|
});
|
|
251
|
-
if (
|
|
251
|
+
if (markdownResult instanceof Error) {
|
|
252
252
|
throw new Error(markdownResult.message);
|
|
253
253
|
}
|
|
254
254
|
return {
|
|
@@ -261,7 +261,7 @@ export async function getTools({ onMessageCompleted, directory, }) {
|
|
|
261
261
|
const markdownResult = await markdownRenderer.generate({
|
|
262
262
|
sessionID: sessionId,
|
|
263
263
|
});
|
|
264
|
-
if (
|
|
264
|
+
if (markdownResult instanceof Error) {
|
|
265
265
|
throw new Error(markdownResult.message);
|
|
266
266
|
}
|
|
267
267
|
const messages = await getClient().session.messages({
|
package/dist/voice-handler.js
CHANGED
|
@@ -326,7 +326,7 @@ export async function processVoiceAttachment({ message, thread, projectDirectory
|
|
|
326
326
|
try: () => fetch(audioAttachment.url),
|
|
327
327
|
catch: (e) => new FetchError({ url: audioAttachment.url, cause: e }),
|
|
328
328
|
});
|
|
329
|
-
if (
|
|
329
|
+
if (audioResponse instanceof Error) {
|
|
330
330
|
voiceLogger.error(`Failed to download audio attachment:`, audioResponse.message);
|
|
331
331
|
await sendThreadMessage(thread, `⚠️ Failed to download audio: ${audioResponse.message}`);
|
|
332
332
|
return null;
|
|
@@ -367,7 +367,7 @@ export async function processVoiceAttachment({ message, thread, projectDirectory
|
|
|
367
367
|
currentSessionContext,
|
|
368
368
|
lastSessionContext,
|
|
369
369
|
});
|
|
370
|
-
if (
|
|
370
|
+
if (transcription instanceof Error) {
|
|
371
371
|
const errMsg = errore.matchError(transcription, {
|
|
372
372
|
ApiKeyMissingError: (e) => e.message,
|
|
373
373
|
InvalidAudioFormatError: (e) => e.message,
|
|
@@ -398,7 +398,7 @@ export async function processVoiceAttachment({ message, thread, projectDirectory
|
|
|
398
398
|
if (renamed === null) {
|
|
399
399
|
voiceLogger.log(`Thread name update timed out`);
|
|
400
400
|
}
|
|
401
|
-
else if (
|
|
401
|
+
else if (renamed instanceof Error) {
|
|
402
402
|
voiceLogger.log(`Could not update thread name:`, renamed.message);
|
|
403
403
|
}
|
|
404
404
|
else {
|
package/dist/voice.js
CHANGED
|
@@ -103,7 +103,7 @@ function createToolRunner({ directory }) {
|
|
|
103
103
|
voiceLogger.log(`Grep search: "${pattern}"`);
|
|
104
104
|
const result = await runGrep({ pattern, directory });
|
|
105
105
|
const output = (() => {
|
|
106
|
-
if (
|
|
106
|
+
if (result instanceof Error) {
|
|
107
107
|
voiceLogger.error('grep search failed:', result);
|
|
108
108
|
return 'grep search failed';
|
|
109
109
|
}
|
|
@@ -117,7 +117,7 @@ function createToolRunner({ directory }) {
|
|
|
117
117
|
voiceLogger.log(`Glob search: "${pattern}"`);
|
|
118
118
|
const result = await runGlob({ pattern, directory });
|
|
119
119
|
const output = (() => {
|
|
120
|
-
if (
|
|
120
|
+
if (result instanceof Error) {
|
|
121
121
|
voiceLogger.error('glob search failed:', result);
|
|
122
122
|
return 'glob search failed';
|
|
123
123
|
}
|
|
@@ -145,7 +145,7 @@ export async function runTranscriptionLoop({ genAI, model, initialContents, tool
|
|
|
145
145
|
}),
|
|
146
146
|
catch: (e) => new TranscriptionError({ reason: `API call failed: ${String(e)}`, cause: e }),
|
|
147
147
|
});
|
|
148
|
-
if (
|
|
148
|
+
if (initialResponse instanceof Error) {
|
|
149
149
|
return initialResponse;
|
|
150
150
|
}
|
|
151
151
|
let response = initialResponse;
|
|
@@ -233,7 +233,7 @@ export async function runTranscriptionLoop({ genAI, model, initialContents, tool
|
|
|
233
233
|
}),
|
|
234
234
|
catch: (e) => new TranscriptionError({ reason: `API call failed: ${String(e)}`, cause: e }),
|
|
235
235
|
});
|
|
236
|
-
if (
|
|
236
|
+
if (nextResponse instanceof Error) {
|
|
237
237
|
return nextResponse;
|
|
238
238
|
}
|
|
239
239
|
response = nextResponse;
|
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.40",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"dev": "tsx --env-file .env src/cli.ts",
|
|
8
8
|
"prepublishOnly": "pnpm tsc",
|
|
@@ -34,18 +34,19 @@
|
|
|
34
34
|
"@clack/prompts": "^0.11.0",
|
|
35
35
|
"@discordjs/voice": "^0.19.0",
|
|
36
36
|
"@google/genai": "^1.34.0",
|
|
37
|
-
"@opencode-ai/sdk": "^1.1.
|
|
37
|
+
"@opencode-ai/sdk": "^1.1.31",
|
|
38
38
|
"@purinton/resampler": "^1.0.4",
|
|
39
39
|
"ai": "^5.0.114",
|
|
40
40
|
"better-sqlite3": "^12.3.0",
|
|
41
41
|
"cac": "^6.7.14",
|
|
42
42
|
"discord.js": "^14.16.3",
|
|
43
43
|
"domhandler": "^5.0.3",
|
|
44
|
-
"errore": "
|
|
44
|
+
"errore": "workspace:^",
|
|
45
45
|
"glob": "^13.0.0",
|
|
46
46
|
"htmlparser2": "^10.0.0",
|
|
47
47
|
"js-yaml": "^4.1.0",
|
|
48
48
|
"marked": "^16.3.0",
|
|
49
|
+
"mime": "^4.1.0",
|
|
49
50
|
"picocolors": "^1.1.1",
|
|
50
51
|
"pretty-ms": "^9.3.0",
|
|
51
52
|
"ripgrep-js": "^3.0.0",
|
package/src/cli.ts
CHANGED
|
@@ -43,6 +43,7 @@ import fs from 'node:fs'
|
|
|
43
43
|
import * as errore from 'errore'
|
|
44
44
|
|
|
45
45
|
import { createLogger } from './logger.js'
|
|
46
|
+
import { uploadFilesToDiscord } from './discord-utils.js'
|
|
46
47
|
import { spawn, spawnSync, execSync, type ExecSyncOptions } from 'node:child_process'
|
|
47
48
|
import http from 'node:http'
|
|
48
49
|
import { setDataDir, getDataDir, getLockPort } from './config.js'
|
|
@@ -211,7 +212,7 @@ async function registerCommands({
|
|
|
211
212
|
})
|
|
212
213
|
.toJSON(),
|
|
213
214
|
new SlashCommandBuilder()
|
|
214
|
-
.setName('session')
|
|
215
|
+
.setName('new-session')
|
|
215
216
|
.setDescription('Start a new OpenCode session')
|
|
216
217
|
.addStringOption((option) => {
|
|
217
218
|
option.setName('prompt').setDescription('Prompt content for the session').setRequired(true)
|
|
@@ -236,6 +237,18 @@ async function registerCommands({
|
|
|
236
237
|
return option
|
|
237
238
|
})
|
|
238
239
|
.toJSON(),
|
|
240
|
+
new SlashCommandBuilder()
|
|
241
|
+
.setName('new-worktree')
|
|
242
|
+
.setDescription('Create a new git worktree and start a session thread')
|
|
243
|
+
.addStringOption((option) => {
|
|
244
|
+
option
|
|
245
|
+
.setName('name')
|
|
246
|
+
.setDescription('Name for the worktree (will be formatted: lowercase, spaces to dashes)')
|
|
247
|
+
.setRequired(true)
|
|
248
|
+
|
|
249
|
+
return option
|
|
250
|
+
})
|
|
251
|
+
.toJSON(),
|
|
239
252
|
new SlashCommandBuilder()
|
|
240
253
|
.setName('add-project')
|
|
241
254
|
.setDescription('Create Discord channels for a new OpenCode project')
|
|
@@ -582,7 +595,7 @@ async function run({ restart, addChannels }: CliOptions) {
|
|
|
582
595
|
const currentDir = process.cwd()
|
|
583
596
|
s.start('Starting OpenCode server...')
|
|
584
597
|
const opencodePromise = initializeOpencodeForDirectory(currentDir).then((result) => {
|
|
585
|
-
if (
|
|
598
|
+
if (result instanceof Error) {
|
|
586
599
|
throw new Error(result.message)
|
|
587
600
|
}
|
|
588
601
|
return result
|
|
@@ -998,34 +1011,11 @@ cli
|
|
|
998
1011
|
const s = spinner()
|
|
999
1012
|
s.start(`Uploading ${resolvedFiles.length} file(s)...`)
|
|
1000
1013
|
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
'payload_json',
|
|
1007
|
-
JSON.stringify({
|
|
1008
|
-
attachments: [{ id: 0, filename: path.basename(file) }],
|
|
1009
|
-
}),
|
|
1010
|
-
)
|
|
1011
|
-
formData.append('files[0]', new Blob([buffer]), path.basename(file))
|
|
1012
|
-
|
|
1013
|
-
const response = await fetch(
|
|
1014
|
-
`https://discord.com/api/v10/channels/${threadRow.thread_id}/messages`,
|
|
1015
|
-
{
|
|
1016
|
-
method: 'POST',
|
|
1017
|
-
headers: {
|
|
1018
|
-
Authorization: `Bot ${botRow.token}`,
|
|
1019
|
-
},
|
|
1020
|
-
body: formData,
|
|
1021
|
-
},
|
|
1022
|
-
)
|
|
1023
|
-
|
|
1024
|
-
if (!response.ok) {
|
|
1025
|
-
const error = await response.text()
|
|
1026
|
-
throw new Error(`Discord API error: ${response.status} - ${error}`)
|
|
1027
|
-
}
|
|
1028
|
-
}
|
|
1014
|
+
await uploadFilesToDiscord({
|
|
1015
|
+
threadId: threadRow.thread_id,
|
|
1016
|
+
botToken: botRow.token,
|
|
1017
|
+
files: resolvedFiles,
|
|
1018
|
+
})
|
|
1029
1019
|
|
|
1030
1020
|
s.stop(`Uploaded ${resolvedFiles.length} file(s)!`)
|
|
1031
1021
|
|
package/src/commands/abort.ts
CHANGED
|
@@ -72,7 +72,7 @@ export async function handleAbortCommand({ command }: CommandContext): Promise<v
|
|
|
72
72
|
}
|
|
73
73
|
|
|
74
74
|
const getClient = await initializeOpencodeForDirectory(directory)
|
|
75
|
-
if (
|
|
75
|
+
if (getClient instanceof Error) {
|
|
76
76
|
await command.reply({
|
|
77
77
|
content: `Failed to abort: ${getClient.message}`,
|
|
78
78
|
ephemeral: true,
|
|
@@ -26,7 +26,7 @@ export async function handleAddProjectCommand({ command, appId }: CommandContext
|
|
|
26
26
|
try {
|
|
27
27
|
const currentDir = process.cwd()
|
|
28
28
|
const getClient = await initializeOpencodeForDirectory(currentDir)
|
|
29
|
-
if (
|
|
29
|
+
if (getClient instanceof Error) {
|
|
30
30
|
await command.editReply(getClient.message)
|
|
31
31
|
return
|
|
32
32
|
}
|
|
@@ -94,7 +94,7 @@ export async function handleAddProjectAutocomplete({
|
|
|
94
94
|
try {
|
|
95
95
|
const currentDir = process.cwd()
|
|
96
96
|
const getClient = await initializeOpencodeForDirectory(currentDir)
|
|
97
|
-
if (
|
|
97
|
+
if (getClient instanceof Error) {
|
|
98
98
|
await interaction.respond([])
|
|
99
99
|
return
|
|
100
100
|
}
|
package/src/commands/agent.ts
CHANGED
|
@@ -162,7 +162,7 @@ export async function handleAgentCommand({
|
|
|
162
162
|
|
|
163
163
|
try {
|
|
164
164
|
const getClient = await initializeOpencodeForDirectory(context.dir)
|
|
165
|
-
if (
|
|
165
|
+
if (getClient instanceof Error) {
|
|
166
166
|
await interaction.editReply({ content: getClient.message })
|
|
167
167
|
return
|
|
168
168
|
}
|
|
@@ -297,7 +297,7 @@ export async function handleQuickAgentCommand({
|
|
|
297
297
|
|
|
298
298
|
try {
|
|
299
299
|
const getClient = await initializeOpencodeForDirectory(context.dir)
|
|
300
|
-
if (
|
|
300
|
+
if (getClient instanceof Error) {
|
|
301
301
|
await command.editReply({ content: getClient.message })
|
|
302
302
|
return
|
|
303
303
|
}
|
package/src/commands/fork.ts
CHANGED
|
@@ -73,7 +73,7 @@ export async function handleForkCommand(interaction: ChatInputCommandInteraction
|
|
|
73
73
|
const sessionId = row.session_id
|
|
74
74
|
|
|
75
75
|
const getClient = await initializeOpencodeForDirectory(directory)
|
|
76
|
-
if (
|
|
76
|
+
if (getClient instanceof Error) {
|
|
77
77
|
await interaction.editReply({
|
|
78
78
|
content: `Failed to load messages: ${getClient.message}`,
|
|
79
79
|
})
|
|
@@ -171,7 +171,7 @@ export async function handleForkSelectMenu(
|
|
|
171
171
|
await interaction.deferReply({ ephemeral: false })
|
|
172
172
|
|
|
173
173
|
const getClient = await initializeOpencodeForDirectory(directory)
|
|
174
|
-
if (
|
|
174
|
+
if (getClient instanceof Error) {
|
|
175
175
|
await interaction.editReply(`Failed to fork session: ${getClient.message}`)
|
|
176
176
|
return
|
|
177
177
|
}
|
package/src/commands/model.ts
CHANGED
|
@@ -129,7 +129,7 @@ export async function handleModelCommand({
|
|
|
129
129
|
|
|
130
130
|
try {
|
|
131
131
|
const getClient = await initializeOpencodeForDirectory(projectDirectory)
|
|
132
|
-
if (
|
|
132
|
+
if (getClient instanceof Error) {
|
|
133
133
|
await interaction.editReply({ content: getClient.message })
|
|
134
134
|
return
|
|
135
135
|
}
|
|
@@ -237,7 +237,7 @@ export async function handleProviderSelectMenu(
|
|
|
237
237
|
|
|
238
238
|
try {
|
|
239
239
|
const getClient = await initializeOpencodeForDirectory(context.dir)
|
|
240
|
-
if (
|
|
240
|
+
if (getClient instanceof Error) {
|
|
241
241
|
await interaction.editReply({
|
|
242
242
|
content: getClient.message,
|
|
243
243
|
components: [],
|
|
@@ -42,7 +42,7 @@ export async function handleRemoveProjectCommand({ command, appId }: CommandCont
|
|
|
42
42
|
catch: (e) => e as Error,
|
|
43
43
|
})
|
|
44
44
|
|
|
45
|
-
if (
|
|
45
|
+
if (channel instanceof Error) {
|
|
46
46
|
logger.error(`Failed to fetch channel ${channel_id}:`, channel)
|
|
47
47
|
failedChannels.push(`${channel_type}: ${channel_id}`)
|
|
48
48
|
continue
|
|
@@ -116,7 +116,7 @@ export async function handleRemoveProjectAutocomplete({
|
|
|
116
116
|
try: () => guild.channels.fetch(channel_id),
|
|
117
117
|
catch: (e) => e as Error,
|
|
118
118
|
})
|
|
119
|
-
if (
|
|
119
|
+
if (channel instanceof Error) {
|
|
120
120
|
// Channel not in this guild, skip
|
|
121
121
|
continue
|
|
122
122
|
}
|