kimaki 0.4.46 → 0.4.48
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 +69 -21
- package/dist/commands/abort.js +4 -2
- package/dist/commands/add-project.js +2 -2
- package/dist/commands/agent.js +4 -4
- package/dist/commands/ask-question.js +9 -8
- package/dist/commands/compact.js +126 -0
- package/dist/commands/create-new-project.js +60 -30
- package/dist/commands/fork.js +3 -3
- package/dist/commands/merge-worktree.js +23 -10
- package/dist/commands/model.js +5 -5
- package/dist/commands/permissions.js +5 -3
- package/dist/commands/queue.js +2 -2
- package/dist/commands/remove-project.js +2 -2
- package/dist/commands/resume.js +2 -2
- package/dist/commands/session.js +6 -3
- package/dist/commands/share.js +2 -2
- package/dist/commands/undo-redo.js +2 -2
- package/dist/commands/user-command.js +2 -2
- package/dist/commands/verbosity.js +5 -5
- package/dist/commands/worktree-settings.js +2 -2
- package/dist/commands/worktree.js +18 -8
- package/dist/config.js +7 -0
- package/dist/database.js +10 -7
- package/dist/discord-bot.js +30 -12
- package/dist/discord-utils.js +2 -2
- package/dist/genai-worker-wrapper.js +3 -3
- package/dist/genai-worker.js +2 -2
- package/dist/genai.js +2 -2
- package/dist/interaction-handler.js +6 -2
- package/dist/logger.js +57 -9
- package/dist/markdown.js +2 -2
- package/dist/message-formatting.js +91 -6
- package/dist/openai-realtime.js +2 -2
- package/dist/opencode.js +19 -25
- package/dist/session-handler.js +89 -29
- package/dist/system-message.js +11 -9
- package/dist/tools.js +3 -2
- package/dist/utils.js +1 -0
- package/dist/voice-handler.js +2 -2
- package/dist/voice.js +2 -2
- package/dist/worktree-utils.js +91 -7
- package/dist/xml.js +2 -2
- package/package.json +3 -3
- package/src/cli.ts +108 -21
- package/src/commands/abort.ts +4 -2
- package/src/commands/add-project.ts +2 -2
- package/src/commands/agent.ts +4 -4
- package/src/commands/ask-question.ts +9 -8
- package/src/commands/compact.ts +148 -0
- package/src/commands/create-new-project.ts +87 -36
- package/src/commands/fork.ts +3 -3
- package/src/commands/merge-worktree.ts +47 -10
- package/src/commands/model.ts +5 -5
- package/src/commands/permissions.ts +6 -2
- package/src/commands/queue.ts +2 -2
- package/src/commands/remove-project.ts +2 -2
- package/src/commands/resume.ts +2 -2
- package/src/commands/session.ts +6 -3
- package/src/commands/share.ts +2 -2
- package/src/commands/undo-redo.ts +2 -2
- package/src/commands/user-command.ts +2 -2
- package/src/commands/verbosity.ts +5 -5
- package/src/commands/worktree-settings.ts +2 -2
- package/src/commands/worktree.ts +20 -7
- package/src/config.ts +14 -0
- package/src/database.ts +13 -7
- package/src/discord-bot.ts +45 -12
- package/src/discord-utils.ts +2 -2
- package/src/genai-worker-wrapper.ts +3 -3
- package/src/genai-worker.ts +2 -2
- package/src/genai.ts +2 -2
- package/src/interaction-handler.ts +7 -2
- package/src/logger.ts +64 -10
- package/src/markdown.ts +2 -2
- package/src/message-formatting.ts +100 -6
- package/src/openai-realtime.ts +2 -2
- package/src/opencode.ts +19 -26
- package/src/session-handler.ts +102 -29
- package/src/system-message.ts +11 -9
- package/src/tools.ts +3 -2
- package/src/utils.ts +1 -0
- package/src/voice-handler.ts +2 -2
- package/src/voice.ts +2 -2
- package/src/worktree-utils.ts +111 -7
- package/src/xml.ts +2 -2
package/dist/commands/model.js
CHANGED
|
@@ -5,9 +5,9 @@ import { getDatabase, setChannelModel, setSessionModel, runModelMigrations } fro
|
|
|
5
5
|
import { initializeOpencodeForDirectory } from '../opencode.js';
|
|
6
6
|
import { resolveTextChannel, getKimakiMetadata } from '../discord-utils.js';
|
|
7
7
|
import { abortAndRetrySession } from '../session-handler.js';
|
|
8
|
-
import { createLogger } from '../logger.js';
|
|
8
|
+
import { createLogger, LogPrefix } from '../logger.js';
|
|
9
9
|
import * as errore from 'errore';
|
|
10
|
-
const modelLogger = createLogger(
|
|
10
|
+
const modelLogger = createLogger(LogPrefix.MODEL);
|
|
11
11
|
// Store context by hash to avoid customId length limits (Discord max: 100 chars)
|
|
12
12
|
const pendingModelContexts = new Map();
|
|
13
13
|
/**
|
|
@@ -292,13 +292,13 @@ export async function handleModelSelectMenu(interaction) {
|
|
|
292
292
|
}
|
|
293
293
|
if (retried) {
|
|
294
294
|
await interaction.editReply({
|
|
295
|
-
content: `Model changed for this session:\n**${context.providerName}** / **${selectedModelId}**\n
|
|
295
|
+
content: `Model changed for this session:\n**${context.providerName}** / **${selectedModelId}**\n\`${fullModelId}\`\n_Retrying current request with new model..._`,
|
|
296
296
|
components: [],
|
|
297
297
|
});
|
|
298
298
|
}
|
|
299
299
|
else {
|
|
300
300
|
await interaction.editReply({
|
|
301
|
-
content: `Model preference set for this session:\n**${context.providerName}** / **${selectedModelId}**\n
|
|
301
|
+
content: `Model preference set for this session:\n**${context.providerName}** / **${selectedModelId}**\n\`${fullModelId}\``,
|
|
302
302
|
components: [],
|
|
303
303
|
});
|
|
304
304
|
}
|
|
@@ -308,7 +308,7 @@ export async function handleModelSelectMenu(interaction) {
|
|
|
308
308
|
setChannelModel(context.channelId, fullModelId);
|
|
309
309
|
modelLogger.log(`Set model ${fullModelId} for channel ${context.channelId}`);
|
|
310
310
|
await interaction.editReply({
|
|
311
|
-
content: `Model preference set for this channel:\n**${context.providerName}** / **${selectedModelId}**\n
|
|
311
|
+
content: `Model preference set for this channel:\n**${context.providerName}** / **${selectedModelId}**\n\`${fullModelId}\`\nAll new sessions in this channel will use this model.`,
|
|
312
312
|
components: [],
|
|
313
313
|
});
|
|
314
314
|
}
|
|
@@ -5,15 +5,15 @@ import { StringSelectMenuBuilder, StringSelectMenuInteraction, ActionRowBuilder,
|
|
|
5
5
|
import crypto from 'node:crypto';
|
|
6
6
|
import { getOpencodeClientV2 } from '../opencode.js';
|
|
7
7
|
import { NOTIFY_MESSAGE_FLAGS } from '../discord-utils.js';
|
|
8
|
-
import { createLogger } from '../logger.js';
|
|
9
|
-
const logger = createLogger(
|
|
8
|
+
import { createLogger, LogPrefix } from '../logger.js';
|
|
9
|
+
const logger = createLogger(LogPrefix.PERMISSIONS);
|
|
10
10
|
// Store pending permission contexts by hash
|
|
11
11
|
export const pendingPermissionContexts = new Map();
|
|
12
12
|
/**
|
|
13
13
|
* Show permission dropdown for a permission request.
|
|
14
14
|
* Returns the message ID and context hash for tracking.
|
|
15
15
|
*/
|
|
16
|
-
export async function showPermissionDropdown({ thread, permission, directory, }) {
|
|
16
|
+
export async function showPermissionDropdown({ thread, permission, directory, subtaskLabel, }) {
|
|
17
17
|
const contextHash = crypto.randomBytes(8).toString('hex');
|
|
18
18
|
const context = {
|
|
19
19
|
permission,
|
|
@@ -47,8 +47,10 @@ export async function showPermissionDropdown({ thread, permission, directory, })
|
|
|
47
47
|
.setPlaceholder('Choose an action')
|
|
48
48
|
.addOptions(options);
|
|
49
49
|
const actionRow = new ActionRowBuilder().addComponents(selectMenu);
|
|
50
|
+
const subtaskLine = subtaskLabel ? `**From:** \`${subtaskLabel}\`\n` : '';
|
|
50
51
|
const permissionMessage = await thread.send({
|
|
51
52
|
content: `⚠️ **Permission Required**\n\n` +
|
|
53
|
+
subtaskLine +
|
|
52
54
|
`**Type:** \`${permission.permission}\`\n` +
|
|
53
55
|
(patternStr ? `**Pattern:** \`${patternStr}\`` : ''),
|
|
54
56
|
components: [actionRow],
|
package/dist/commands/queue.js
CHANGED
|
@@ -3,8 +3,8 @@ import { ChannelType } from 'discord.js';
|
|
|
3
3
|
import { getDatabase } from '../database.js';
|
|
4
4
|
import { resolveTextChannel, getKimakiMetadata, sendThreadMessage, SILENT_MESSAGE_FLAGS, } from '../discord-utils.js';
|
|
5
5
|
import { handleOpencodeSession, abortControllers, addToQueue, getQueueLength, clearQueue, } from '../session-handler.js';
|
|
6
|
-
import { createLogger } from '../logger.js';
|
|
7
|
-
const logger = createLogger(
|
|
6
|
+
import { createLogger, LogPrefix } from '../logger.js';
|
|
7
|
+
const logger = createLogger(LogPrefix.QUEUE);
|
|
8
8
|
export async function handleQueueCommand({ command }) {
|
|
9
9
|
const message = command.options.getString('message', true);
|
|
10
10
|
const channel = command.channel;
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import * as errore from 'errore';
|
|
4
4
|
import { getDatabase } from '../database.js';
|
|
5
|
-
import { createLogger } from '../logger.js';
|
|
5
|
+
import { createLogger, LogPrefix } from '../logger.js';
|
|
6
6
|
import { abbreviatePath } from '../utils.js';
|
|
7
|
-
const logger = createLogger(
|
|
7
|
+
const logger = createLogger(LogPrefix.REMOVE_PROJECT);
|
|
8
8
|
export async function handleRemoveProjectCommand({ command, appId }) {
|
|
9
9
|
await command.deferReply({ ephemeral: false });
|
|
10
10
|
const directory = command.options.getString('project', true);
|
package/dist/commands/resume.js
CHANGED
|
@@ -5,9 +5,9 @@ import { getDatabase, getChannelDirectory } from '../database.js';
|
|
|
5
5
|
import { initializeOpencodeForDirectory } from '../opencode.js';
|
|
6
6
|
import { sendThreadMessage, resolveTextChannel } from '../discord-utils.js';
|
|
7
7
|
import { collectLastAssistantParts } from '../message-formatting.js';
|
|
8
|
-
import { createLogger } from '../logger.js';
|
|
8
|
+
import { createLogger, LogPrefix } from '../logger.js';
|
|
9
9
|
import * as errore from 'errore';
|
|
10
|
-
const logger = createLogger(
|
|
10
|
+
const logger = createLogger(LogPrefix.RESUME);
|
|
11
11
|
export async function handleResumeCommand({ command, appId }) {
|
|
12
12
|
await command.deferReply({ ephemeral: false });
|
|
13
13
|
const sessionId = command.options.getString('session', true);
|
package/dist/commands/session.js
CHANGED
|
@@ -6,9 +6,9 @@ import { getDatabase, getChannelDirectory } from '../database.js';
|
|
|
6
6
|
import { initializeOpencodeForDirectory } from '../opencode.js';
|
|
7
7
|
import { SILENT_MESSAGE_FLAGS } from '../discord-utils.js';
|
|
8
8
|
import { handleOpencodeSession } from '../session-handler.js';
|
|
9
|
-
import { createLogger } from '../logger.js';
|
|
9
|
+
import { createLogger, LogPrefix } from '../logger.js';
|
|
10
10
|
import * as errore from 'errore';
|
|
11
|
-
const logger = createLogger(
|
|
11
|
+
const logger = createLogger(LogPrefix.SESSION);
|
|
12
12
|
export async function handleSessionCommand({ command, appId }) {
|
|
13
13
|
await command.deferReply({ ephemeral: false });
|
|
14
14
|
const prompt = command.options.getString('prompt', true);
|
|
@@ -105,7 +105,10 @@ async function handleAgentAutocomplete({ interaction, appId }) {
|
|
|
105
105
|
return;
|
|
106
106
|
}
|
|
107
107
|
const agents = agentsResponse.data
|
|
108
|
-
.filter((a) =>
|
|
108
|
+
.filter((a) => {
|
|
109
|
+
const hidden = a.hidden;
|
|
110
|
+
return (a.mode === 'primary' || a.mode === 'all') && !hidden;
|
|
111
|
+
})
|
|
109
112
|
.filter((a) => a.name.toLowerCase().includes(focusedValue.toLowerCase()))
|
|
110
113
|
.slice(0, 25);
|
|
111
114
|
const choices = agents.map((agent) => ({
|
package/dist/commands/share.js
CHANGED
|
@@ -3,9 +3,9 @@ import { ChannelType } from 'discord.js';
|
|
|
3
3
|
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
|
-
import { createLogger } from '../logger.js';
|
|
6
|
+
import { createLogger, LogPrefix } from '../logger.js';
|
|
7
7
|
import * as errore from 'errore';
|
|
8
|
-
const logger = createLogger(
|
|
8
|
+
const logger = createLogger(LogPrefix.SHARE);
|
|
9
9
|
export async function handleShareCommand({ command }) {
|
|
10
10
|
const channel = command.channel;
|
|
11
11
|
if (!channel) {
|
|
@@ -3,9 +3,9 @@ import { ChannelType } from 'discord.js';
|
|
|
3
3
|
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
|
-
import { createLogger } from '../logger.js';
|
|
6
|
+
import { createLogger, LogPrefix } from '../logger.js';
|
|
7
7
|
import * as errore from 'errore';
|
|
8
|
-
const logger = createLogger(
|
|
8
|
+
const logger = createLogger(LogPrefix.UNDO_REDO);
|
|
9
9
|
export async function handleUndoCommand({ command }) {
|
|
10
10
|
const channel = command.channel;
|
|
11
11
|
if (!channel) {
|
|
@@ -3,10 +3,10 @@
|
|
|
3
3
|
import { ChannelType } from 'discord.js';
|
|
4
4
|
import { handleOpencodeSession } from '../session-handler.js';
|
|
5
5
|
import { SILENT_MESSAGE_FLAGS } from '../discord-utils.js';
|
|
6
|
-
import { createLogger } from '../logger.js';
|
|
6
|
+
import { createLogger, LogPrefix } from '../logger.js';
|
|
7
7
|
import { getDatabase, getChannelDirectory } from '../database.js';
|
|
8
8
|
import fs from 'node:fs';
|
|
9
|
-
const userCommandLogger = createLogger(
|
|
9
|
+
const userCommandLogger = createLogger(LogPrefix.USER_CMD);
|
|
10
10
|
export const handleUserCommand = async ({ command, appId }) => {
|
|
11
11
|
const discordCommandName = command.commandName;
|
|
12
12
|
// Strip the -cmd suffix to get the actual OpenCode command name
|
|
@@ -4,11 +4,11 @@
|
|
|
4
4
|
// 'text-only': only shows text responses (⬥ diamond parts)
|
|
5
5
|
import { ChatInputCommandInteraction, ChannelType } from 'discord.js';
|
|
6
6
|
import { getChannelVerbosity, setChannelVerbosity } from '../database.js';
|
|
7
|
-
import { createLogger } from '../logger.js';
|
|
8
|
-
const verbosityLogger = createLogger(
|
|
7
|
+
import { createLogger, LogPrefix } from '../logger.js';
|
|
8
|
+
const verbosityLogger = createLogger(LogPrefix.VERBOSITY);
|
|
9
9
|
/**
|
|
10
10
|
* Handle the /verbosity slash command.
|
|
11
|
-
* Sets output verbosity for the channel (applies
|
|
11
|
+
* Sets output verbosity for the channel (applies immediately, even mid-session).
|
|
12
12
|
*/
|
|
13
13
|
export async function handleVerbosityCommand({ command, appId, }) {
|
|
14
14
|
verbosityLogger.log('[VERBOSITY] Command called');
|
|
@@ -36,7 +36,7 @@ export async function handleVerbosityCommand({ command, appId, }) {
|
|
|
36
36
|
const currentLevel = getChannelVerbosity(channelId);
|
|
37
37
|
if (currentLevel === level) {
|
|
38
38
|
await command.reply({
|
|
39
|
-
content: `Verbosity is already set to **${level}
|
|
39
|
+
content: `Verbosity is already set to **${level}** for this channel.`,
|
|
40
40
|
ephemeral: true,
|
|
41
41
|
});
|
|
42
42
|
return;
|
|
@@ -47,7 +47,7 @@ export async function handleVerbosityCommand({ command, appId, }) {
|
|
|
47
47
|
? 'Only text responses will be shown. Tool executions, status messages, and thinking will be hidden.'
|
|
48
48
|
: 'All output will be shown, including tool executions and status messages.';
|
|
49
49
|
await command.reply({
|
|
50
|
-
content: `Verbosity set to **${level}
|
|
50
|
+
content: `Verbosity set to **${level}** for this channel.\n${description}\nThis is a per-channel setting and applies immediately, including any active sessions.`,
|
|
51
51
|
ephemeral: true,
|
|
52
52
|
});
|
|
53
53
|
}
|
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
import { ChatInputCommandInteraction, ChannelType } from 'discord.js';
|
|
5
5
|
import { getChannelWorktreesEnabled, setChannelWorktreesEnabled } from '../database.js';
|
|
6
6
|
import { getKimakiMetadata } from '../discord-utils.js';
|
|
7
|
-
import { createLogger } from '../logger.js';
|
|
8
|
-
const worktreeSettingsLogger = createLogger(
|
|
7
|
+
import { createLogger, LogPrefix } from '../logger.js';
|
|
8
|
+
const worktreeSettingsLogger = createLogger(LogPrefix.WORKTREE);
|
|
9
9
|
/**
|
|
10
10
|
* Handle the /enable-worktrees slash command.
|
|
11
11
|
* Enables automatic worktree creation for new sessions in this channel.
|
|
@@ -6,11 +6,11 @@ import fs from 'node:fs';
|
|
|
6
6
|
import { createPendingWorktree, setWorktreeReady, setWorktreeError, getChannelDirectory, getThreadWorktree, } from '../database.js';
|
|
7
7
|
import { initializeOpencodeForDirectory, getOpencodeClientV2 } from '../opencode.js';
|
|
8
8
|
import { SILENT_MESSAGE_FLAGS } from '../discord-utils.js';
|
|
9
|
-
import { createLogger } from '../logger.js';
|
|
10
|
-
import { createWorktreeWithSubmodules } from '../worktree-utils.js';
|
|
9
|
+
import { createLogger, LogPrefix } from '../logger.js';
|
|
10
|
+
import { createWorktreeWithSubmodules, captureGitDiff } from '../worktree-utils.js';
|
|
11
11
|
import { WORKTREE_PREFIX } from './merge-worktree.js';
|
|
12
12
|
import * as errore from 'errore';
|
|
13
|
-
const logger = createLogger(
|
|
13
|
+
const logger = createLogger(LogPrefix.WORKTREE);
|
|
14
14
|
class WorktreeError extends Error {
|
|
15
15
|
constructor(message, options) {
|
|
16
16
|
super(message, options);
|
|
@@ -69,14 +69,16 @@ function getProjectDirectoryFromChannel(channel, appId) {
|
|
|
69
69
|
}
|
|
70
70
|
/**
|
|
71
71
|
* Create worktree in background and update starter message when done.
|
|
72
|
+
* If diff is provided, it's applied during worktree creation (before submodule init).
|
|
72
73
|
*/
|
|
73
|
-
async function createWorktreeInBackground({ thread, starterMessage, worktreeName, projectDirectory, clientV2, }) {
|
|
74
|
-
// Create worktree using SDK v2
|
|
74
|
+
async function createWorktreeInBackground({ thread, starterMessage, worktreeName, projectDirectory, clientV2, diff, }) {
|
|
75
|
+
// Create worktree using SDK v2, apply diff, then init submodules
|
|
75
76
|
logger.log(`Creating worktree "${worktreeName}" for project ${projectDirectory}`);
|
|
76
77
|
const worktreeResult = await createWorktreeWithSubmodules({
|
|
77
78
|
clientV2,
|
|
78
79
|
directory: projectDirectory,
|
|
79
80
|
name: worktreeName,
|
|
81
|
+
diff,
|
|
80
82
|
});
|
|
81
83
|
if (worktreeResult instanceof Error) {
|
|
82
84
|
const errorMsg = worktreeResult.message;
|
|
@@ -87,9 +89,11 @@ async function createWorktreeInBackground({ thread, starterMessage, worktreeName
|
|
|
87
89
|
}
|
|
88
90
|
// Success - update database and edit starter message
|
|
89
91
|
setWorktreeReady({ threadId: thread.id, worktreeDirectory: worktreeResult.directory });
|
|
92
|
+
const diffStatus = diff ? (worktreeResult.diffApplied ? '\n✅ Changes applied' : '\n⚠️ Failed to apply changes') : '';
|
|
90
93
|
await starterMessage.edit(`🌳 **Worktree: ${worktreeName}**\n` +
|
|
91
94
|
`📁 \`${worktreeResult.directory}\`\n` +
|
|
92
|
-
`🌿 Branch: \`${worktreeResult.branch}\``
|
|
95
|
+
`🌿 Branch: \`${worktreeResult.branch}\`` +
|
|
96
|
+
diffStatus);
|
|
93
97
|
}
|
|
94
98
|
export async function handleNewWorktreeCommand({ command, appId, }) {
|
|
95
99
|
await command.deferReply({ ephemeral: false });
|
|
@@ -253,6 +257,10 @@ async function handleWorktreeInThread({ command, appId, thread, }) {
|
|
|
253
257
|
await command.editReply(`Worktree \`${worktreeName}\` already exists at \`${existingWorktreePath}\``);
|
|
254
258
|
return;
|
|
255
259
|
}
|
|
260
|
+
// Capture git diff from project directory before creating worktree.
|
|
261
|
+
// This allows transferring uncommitted changes to the new worktree.
|
|
262
|
+
const diff = await captureGitDiff(projectDirectory);
|
|
263
|
+
const hasDiff = diff && (diff.staged || diff.unstaged);
|
|
256
264
|
// Store pending worktree in database for this existing thread
|
|
257
265
|
createPendingWorktree({
|
|
258
266
|
threadId: thread.id,
|
|
@@ -260,18 +268,20 @@ async function handleWorktreeInThread({ command, appId, thread, }) {
|
|
|
260
268
|
projectDirectory,
|
|
261
269
|
});
|
|
262
270
|
// Send status message in thread
|
|
271
|
+
const diffNote = hasDiff ? '\n📋 Will transfer uncommitted changes' : '';
|
|
263
272
|
const statusMessage = await thread.send({
|
|
264
|
-
content: `🌳 **Creating worktree: ${worktreeName}**\n⏳ Setting up
|
|
273
|
+
content: `🌳 **Creating worktree: ${worktreeName}**\n⏳ Setting up...${diffNote}`,
|
|
265
274
|
flags: SILENT_MESSAGE_FLAGS,
|
|
266
275
|
});
|
|
267
276
|
await command.editReply(`Creating worktree \`${worktreeName}\` for this thread...`);
|
|
268
|
-
// Create worktree in background
|
|
277
|
+
// Create worktree in background, passing diff to apply after creation
|
|
269
278
|
createWorktreeInBackground({
|
|
270
279
|
thread,
|
|
271
280
|
starterMessage: statusMessage,
|
|
272
281
|
worktreeName,
|
|
273
282
|
projectDirectory,
|
|
274
283
|
clientV2,
|
|
284
|
+
diff,
|
|
275
285
|
}).catch((e) => {
|
|
276
286
|
logger.error('[NEW-WORKTREE] Background error:', e);
|
|
277
287
|
});
|
package/dist/config.js
CHANGED
|
@@ -35,6 +35,13 @@ export function setDataDir(dir) {
|
|
|
35
35
|
export function getProjectsDir() {
|
|
36
36
|
return path.join(getDataDir(), 'projects');
|
|
37
37
|
}
|
|
38
|
+
let defaultVerbosity = 'tools-and-text';
|
|
39
|
+
export function getDefaultVerbosity() {
|
|
40
|
+
return defaultVerbosity;
|
|
41
|
+
}
|
|
42
|
+
export function setDefaultVerbosity(level) {
|
|
43
|
+
defaultVerbosity = level;
|
|
44
|
+
}
|
|
38
45
|
const DEFAULT_LOCK_PORT = 29988;
|
|
39
46
|
/**
|
|
40
47
|
* Derive a lock port from the data directory path.
|
package/dist/database.js
CHANGED
|
@@ -5,9 +5,9 @@ import Database from 'better-sqlite3';
|
|
|
5
5
|
import fs from 'node:fs';
|
|
6
6
|
import path from 'node:path';
|
|
7
7
|
import * as errore from 'errore';
|
|
8
|
-
import { createLogger } from './logger.js';
|
|
9
|
-
import { getDataDir } from './config.js';
|
|
10
|
-
const dbLogger = createLogger(
|
|
8
|
+
import { createLogger, LogPrefix } from './logger.js';
|
|
9
|
+
import { getDataDir, getDefaultVerbosity } from './config.js';
|
|
10
|
+
const dbLogger = createLogger(LogPrefix.DB);
|
|
11
11
|
let db = null;
|
|
12
12
|
export function getDatabase() {
|
|
13
13
|
if (!db) {
|
|
@@ -58,8 +58,8 @@ export function getDatabase() {
|
|
|
58
58
|
try {
|
|
59
59
|
db.exec(`ALTER TABLE channel_directories ADD COLUMN app_id TEXT`);
|
|
60
60
|
}
|
|
61
|
-
catch {
|
|
62
|
-
|
|
61
|
+
catch (error) {
|
|
62
|
+
dbLogger.debug('Failed to add app_id column to channel_directories (likely exists):', error instanceof Error ? error.message : String(error));
|
|
63
63
|
}
|
|
64
64
|
// Table for threads that should auto-start a session (created by CLI without --notify-only)
|
|
65
65
|
db.exec(`
|
|
@@ -281,14 +281,17 @@ export function runVerbosityMigrations(database) {
|
|
|
281
281
|
}
|
|
282
282
|
/**
|
|
283
283
|
* Get the verbosity setting for a channel.
|
|
284
|
-
*
|
|
284
|
+
* Falls back to the global default set via --verbosity CLI flag if no per-channel override exists.
|
|
285
285
|
*/
|
|
286
286
|
export function getChannelVerbosity(channelId) {
|
|
287
287
|
const db = getDatabase();
|
|
288
288
|
const row = db
|
|
289
289
|
.prepare('SELECT verbosity FROM channel_verbosity WHERE channel_id = ?')
|
|
290
290
|
.get(channelId);
|
|
291
|
-
|
|
291
|
+
if (row?.verbosity) {
|
|
292
|
+
return row.verbosity;
|
|
293
|
+
}
|
|
294
|
+
return getDefaultVerbosity();
|
|
292
295
|
}
|
|
293
296
|
/**
|
|
294
297
|
* Set the verbosity setting for a channel.
|
package/dist/discord-bot.js
CHANGED
|
@@ -22,14 +22,17 @@ export { ensureKimakiCategory, ensureKimakiAudioCategory, createProjectChannels,
|
|
|
22
22
|
import { ChannelType, Client, Events, GatewayIntentBits, Partials, PermissionsBitField, ThreadAutoArchiveDuration, } from 'discord.js';
|
|
23
23
|
import fs from 'node:fs';
|
|
24
24
|
import * as errore from 'errore';
|
|
25
|
-
import { createLogger } from './logger.js';
|
|
25
|
+
import { createLogger, LogPrefix } from './logger.js';
|
|
26
26
|
import { setGlobalDispatcher, Agent } from 'undici';
|
|
27
27
|
// Increase connection pool to prevent deadlock when multiple sessions have open SSE streams.
|
|
28
28
|
// Each session's event.subscribe() holds a connection; without enough connections,
|
|
29
29
|
// regular HTTP requests (question.reply, session.prompt) get blocked → deadlock.
|
|
30
30
|
setGlobalDispatcher(new Agent({ headersTimeout: 0, bodyTimeout: 0, connections: 500 }));
|
|
31
|
-
const discordLogger = createLogger(
|
|
32
|
-
const voiceLogger = createLogger(
|
|
31
|
+
const discordLogger = createLogger(LogPrefix.DISCORD);
|
|
32
|
+
const voiceLogger = createLogger(LogPrefix.VOICE);
|
|
33
|
+
function prefixWithDiscordUser({ username, prompt }) {
|
|
34
|
+
return `<discord-user name="${username}" />\n${prompt}`;
|
|
35
|
+
}
|
|
33
36
|
export async function createDiscordClient() {
|
|
34
37
|
return new Client({
|
|
35
38
|
intents: [
|
|
@@ -93,6 +96,15 @@ export async function startDiscordBot({ token, appId, discordClient, useWorktree
|
|
|
93
96
|
if (message.author?.bot) {
|
|
94
97
|
return;
|
|
95
98
|
}
|
|
99
|
+
// Ignore messages that start with a mention of another user (not the bot).
|
|
100
|
+
// These are likely users talking to each other, not the bot.
|
|
101
|
+
const leadingMentionMatch = message.content?.match(/^<@!?(\d+)>/);
|
|
102
|
+
if (leadingMentionMatch) {
|
|
103
|
+
const mentionedUserId = leadingMentionMatch[1];
|
|
104
|
+
if (mentionedUserId !== discordClient.user?.id) {
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
96
108
|
if (message.partial) {
|
|
97
109
|
discordLogger.log(`Fetching partial message ${message.id}`);
|
|
98
110
|
const fetched = await errore.tryAsync({
|
|
@@ -184,12 +196,15 @@ export async function startDiscordBot({ token, appId, discordClient, useWorktree
|
|
|
184
196
|
}
|
|
185
197
|
// Include starter message as context for the session
|
|
186
198
|
let prompt = message.content;
|
|
187
|
-
const starterMessage = await thread.fetchStarterMessage().catch(() =>
|
|
199
|
+
const starterMessage = await thread.fetchStarterMessage().catch((error) => {
|
|
200
|
+
discordLogger.warn(`[SESSION] Failed to fetch starter message for thread ${thread.id}:`, error instanceof Error ? error.message : String(error));
|
|
201
|
+
return null;
|
|
202
|
+
});
|
|
188
203
|
if (starterMessage?.content && starterMessage.content !== message.content) {
|
|
189
204
|
prompt = `Context from thread:\n${starterMessage.content}\n\nUser request:\n${message.content}`;
|
|
190
205
|
}
|
|
191
206
|
await handleOpencodeSession({
|
|
192
|
-
prompt,
|
|
207
|
+
prompt: prefixWithDiscordUser({ username: message.member?.displayName || message.author.displayName, prompt }),
|
|
193
208
|
thread,
|
|
194
209
|
projectDirectory,
|
|
195
210
|
channelId: parent?.id || '',
|
|
@@ -259,7 +274,7 @@ export async function startDiscordBot({ token, appId, discordClient, useWorktree
|
|
|
259
274
|
? `${messageContent}\n\n${textAttachmentsContent}`
|
|
260
275
|
: messageContent;
|
|
261
276
|
await handleOpencodeSession({
|
|
262
|
-
prompt: promptWithAttachments,
|
|
277
|
+
prompt: prefixWithDiscordUser({ username: message.member?.displayName || message.author.displayName, prompt: promptWithAttachments }),
|
|
263
278
|
thread,
|
|
264
279
|
projectDirectory,
|
|
265
280
|
originalMessage: message,
|
|
@@ -379,7 +394,7 @@ export async function startDiscordBot({ token, appId, discordClient, useWorktree
|
|
|
379
394
|
? `${messageContent}\n\n${textAttachmentsContent}`
|
|
380
395
|
: messageContent;
|
|
381
396
|
await handleOpencodeSession({
|
|
382
|
-
prompt: promptWithAttachments,
|
|
397
|
+
prompt: prefixWithDiscordUser({ username: message.member?.displayName || message.author.displayName, prompt: promptWithAttachments }),
|
|
383
398
|
thread,
|
|
384
399
|
projectDirectory: sessionDirectory,
|
|
385
400
|
originalMessage: message,
|
|
@@ -397,8 +412,8 @@ export async function startDiscordBot({ token, appId, discordClient, useWorktree
|
|
|
397
412
|
const errMsg = error instanceof Error ? error.message : String(error);
|
|
398
413
|
await message.reply({ content: `Error: ${errMsg}`, flags: SILENT_MESSAGE_FLAGS });
|
|
399
414
|
}
|
|
400
|
-
catch {
|
|
401
|
-
voiceLogger.error('Discord handler error (fallback):',
|
|
415
|
+
catch (sendError) {
|
|
416
|
+
voiceLogger.error('Discord handler error (fallback):', sendError instanceof Error ? sendError.message : String(sendError));
|
|
402
417
|
}
|
|
403
418
|
}
|
|
404
419
|
});
|
|
@@ -416,7 +431,10 @@ export async function startDiscordBot({ token, appId, discordClient, useWorktree
|
|
|
416
431
|
return;
|
|
417
432
|
}
|
|
418
433
|
// Get the starter message to check for auto-start marker
|
|
419
|
-
const starterMessage = await thread.fetchStarterMessage().catch(() =>
|
|
434
|
+
const starterMessage = await thread.fetchStarterMessage().catch((error) => {
|
|
435
|
+
discordLogger.warn(`[THREAD_CREATE] Failed to fetch starter message for thread ${thread.id}:`, error instanceof Error ? error.message : String(error));
|
|
436
|
+
return null;
|
|
437
|
+
});
|
|
420
438
|
if (!starterMessage) {
|
|
421
439
|
discordLogger.log(`[THREAD_CREATE] Could not fetch starter message for thread ${thread.id}`);
|
|
422
440
|
return;
|
|
@@ -466,8 +484,8 @@ export async function startDiscordBot({ token, appId, discordClient, useWorktree
|
|
|
466
484
|
const errMsg = error instanceof Error ? error.message : String(error);
|
|
467
485
|
await thread.send({ content: `Error: ${errMsg}`, flags: SILENT_MESSAGE_FLAGS });
|
|
468
486
|
}
|
|
469
|
-
catch {
|
|
470
|
-
|
|
487
|
+
catch (sendError) {
|
|
488
|
+
voiceLogger.error('[BOT_SESSION] Failed to send error message:', sendError instanceof Error ? sendError.message : String(sendError));
|
|
471
489
|
}
|
|
472
490
|
}
|
|
473
491
|
});
|
package/dist/discord-utils.js
CHANGED
|
@@ -7,11 +7,11 @@ import { formatMarkdownTables } from './format-tables.js';
|
|
|
7
7
|
import { getChannelDirectory } from './database.js';
|
|
8
8
|
import { limitHeadingDepth } from './limit-heading-depth.js';
|
|
9
9
|
import { unnestCodeBlocksFromLists } from './unnest-code-blocks.js';
|
|
10
|
-
import { createLogger } from './logger.js';
|
|
10
|
+
import { createLogger, LogPrefix } from './logger.js';
|
|
11
11
|
import mime from 'mime';
|
|
12
12
|
import fs from 'node:fs';
|
|
13
13
|
import path from 'node:path';
|
|
14
|
-
const discordLogger = createLogger(
|
|
14
|
+
const discordLogger = createLogger(LogPrefix.DISCORD);
|
|
15
15
|
export const SILENT_MESSAGE_FLAGS = 4 | 4096;
|
|
16
16
|
// Same as SILENT but without SuppressNotifications - triggers badge/notification
|
|
17
17
|
export const NOTIFY_MESSAGE_FLAGS = 4;
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
// Spawns and manages the worker thread, handling message passing for
|
|
3
3
|
// audio input/output, tool call completions, and graceful shutdown.
|
|
4
4
|
import { Worker } from 'node:worker_threads';
|
|
5
|
-
import { createLogger } from './logger.js';
|
|
6
|
-
const genaiWorkerLogger = createLogger(
|
|
7
|
-
const genaiWrapperLogger = createLogger(
|
|
5
|
+
import { createLogger, LogPrefix } from './logger.js';
|
|
6
|
+
const genaiWorkerLogger = createLogger(LogPrefix.GENAI_WORKER);
|
|
7
|
+
const genaiWrapperLogger = createLogger(LogPrefix.GENAI_WORKER);
|
|
8
8
|
export function createGenAIWorker(options) {
|
|
9
9
|
return new Promise((resolve, reject) => {
|
|
10
10
|
const worker = new Worker(new URL('../dist/genai-worker.js', import.meta.url));
|
package/dist/genai-worker.js
CHANGED
|
@@ -10,11 +10,11 @@ import * as prism from 'prism-media';
|
|
|
10
10
|
import { startGenAiSession } from './genai.js';
|
|
11
11
|
import { getTools } from './tools.js';
|
|
12
12
|
import { mkdir } from 'node:fs/promises';
|
|
13
|
-
import { createLogger } from './logger.js';
|
|
13
|
+
import { createLogger, LogPrefix } from './logger.js';
|
|
14
14
|
if (!parentPort) {
|
|
15
15
|
throw new Error('This module must be run as a worker thread');
|
|
16
16
|
}
|
|
17
|
-
const workerLogger = createLogger(
|
|
17
|
+
const workerLogger = createLogger(`${LogPrefix.WORKER}_${threadId}`);
|
|
18
18
|
workerLogger.log('GenAI worker started');
|
|
19
19
|
// Define sendError early so it can be used by global handlers
|
|
20
20
|
function sendError(error) {
|
package/dist/genai.js
CHANGED
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
// and manages the assistant's audio output for Discord voice channels.
|
|
4
4
|
import { GoogleGenAI, LiveServerMessage, MediaResolution, Modality, Session } from '@google/genai';
|
|
5
5
|
import { writeFile } from 'fs';
|
|
6
|
-
import { createLogger } from './logger.js';
|
|
6
|
+
import { createLogger, LogPrefix } from './logger.js';
|
|
7
7
|
import { aiToolToCallableTool } from './ai-tool-to-genai.js';
|
|
8
|
-
const genaiLogger = createLogger(
|
|
8
|
+
const genaiLogger = createLogger(LogPrefix.GENAI);
|
|
9
9
|
const audioParts = [];
|
|
10
10
|
function saveBinaryFile(fileName, content) {
|
|
11
11
|
writeFile(fileName, content, 'utf8', (err) => {
|
|
@@ -12,6 +12,7 @@ import { handleRemoveProjectCommand, handleRemoveProjectAutocomplete, } from './
|
|
|
12
12
|
import { handleCreateNewProjectCommand } from './commands/create-new-project.js';
|
|
13
13
|
import { handlePermissionSelectMenu } from './commands/permissions.js';
|
|
14
14
|
import { handleAbortCommand } from './commands/abort.js';
|
|
15
|
+
import { handleCompactCommand } from './commands/compact.js';
|
|
15
16
|
import { handleShareCommand } from './commands/share.js';
|
|
16
17
|
import { handleForkCommand, handleForkSelectMenu } from './commands/fork.js';
|
|
17
18
|
import { handleModelCommand, handleProviderSelectMenu, handleModelSelectMenu, } from './commands/model.js';
|
|
@@ -21,8 +22,8 @@ import { handleQueueCommand, handleClearQueueCommand } from './commands/queue.js
|
|
|
21
22
|
import { handleUndoCommand, handleRedoCommand } from './commands/undo-redo.js';
|
|
22
23
|
import { handleUserCommand } from './commands/user-command.js';
|
|
23
24
|
import { handleVerbosityCommand } from './commands/verbosity.js';
|
|
24
|
-
import { createLogger } from './logger.js';
|
|
25
|
-
const interactionLogger = createLogger(
|
|
25
|
+
import { createLogger, LogPrefix } from './logger.js';
|
|
26
|
+
const interactionLogger = createLogger(LogPrefix.INTERACTION);
|
|
26
27
|
export function registerInteractionHandler({ discordClient, appId, }) {
|
|
27
28
|
interactionLogger.log('[REGISTER] Interaction handler registered');
|
|
28
29
|
discordClient.on(Events.InteractionCreate, async (interaction) => {
|
|
@@ -85,6 +86,9 @@ export function registerInteractionHandler({ discordClient, appId, }) {
|
|
|
85
86
|
case 'stop':
|
|
86
87
|
await handleAbortCommand({ command: interaction, appId });
|
|
87
88
|
return;
|
|
89
|
+
case 'compact':
|
|
90
|
+
await handleCompactCommand({ command: interaction, appId });
|
|
91
|
+
return;
|
|
88
92
|
case 'share':
|
|
89
93
|
await handleShareCommand({ command: interaction, appId });
|
|
90
94
|
return;
|