kimaki 0.4.63 → 0.4.65
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/bin.js +2 -1
- package/dist/cli-parsing.test.js +7 -4
- package/dist/cli.js +92 -39
- package/dist/commands/abort.js +10 -14
- package/dist/commands/agent.js +9 -5
- package/dist/commands/ask-question.js +3 -3
- package/dist/commands/compact.js +10 -14
- package/dist/commands/context-usage.js +10 -15
- package/dist/commands/diff.js +7 -8
- package/dist/commands/file-upload.js +5 -7
- package/dist/commands/fork.js +27 -17
- package/dist/commands/gemini-apikey.js +3 -3
- package/dist/commands/login.js +3 -3
- package/dist/commands/mention-mode.js +5 -5
- package/dist/commands/merge-worktree.js +2 -2
- package/dist/commands/model.js +169 -74
- package/dist/commands/queue.js +16 -30
- package/dist/commands/remove-project.js +3 -3
- package/dist/commands/restart-opencode-server.js +61 -13
- package/dist/commands/resume.js +1 -1
- package/dist/commands/run-command.js +9 -12
- package/dist/commands/share.js +11 -16
- package/dist/commands/thinking.js +128 -0
- package/dist/commands/undo-redo.js +15 -19
- package/dist/commands/unset-model.js +7 -7
- package/dist/commands/user-command.js +7 -7
- package/dist/commands/verbosity.js +4 -4
- package/dist/commands/worktree-settings.js +5 -5
- package/dist/commands/worktree.js +16 -5
- package/dist/config.js +9 -0
- package/dist/database.js +55 -18
- package/dist/db.js +34 -1
- package/dist/db.test.js +11 -11
- package/dist/discord-bot.js +35 -16
- package/dist/discord-utils.js +8 -5
- package/dist/format-tables.test.js +1 -1
- package/dist/generated/internal/class.js +2 -2
- package/dist/generated/internal/prismaNamespace.js +3 -0
- package/dist/generated/internal/prismaNamespaceBrowser.js +3 -0
- package/dist/generated/models/session_thinking.js +1 -0
- package/dist/interaction-handler.js +13 -8
- package/dist/logger.js +1 -0
- package/dist/opencode-plugin.js +3 -7
- package/dist/opencode.js +1 -1
- package/dist/session-handler.js +121 -60
- package/dist/system-message.js +59 -33
- package/dist/thinking-utils.js +35 -0
- package/dist/thinking-utils.test.js +48 -0
- package/dist/unnest-code-blocks.js +36 -20
- package/dist/unnest-code-blocks.test.js +184 -1
- package/dist/wait-session.js +10 -6
- package/dist/worktree-utils.js +45 -21
- package/package.json +1 -1
- package/schema.prisma +3 -0
- package/src/__snapshots__/compact-session-context-no-system.md +2 -0
- package/src/__snapshots__/compact-session-context.md +2 -0
- package/src/__snapshots__/first-session-no-info.md +500 -498
- package/src/__snapshots__/first-session-with-info.md +500 -498
- package/src/__snapshots__/session-1.md +500 -498
- package/src/__snapshots__/session-2.md +1 -3
- package/src/__snapshots__/session-3.md +583 -574
- package/src/__snapshots__/session-with-tools.md +500 -498
- package/src/ai-tool-to-genai.ts +1 -4
- package/src/ai-tool.ts +1 -4
- package/src/bin.ts +4 -1
- package/src/cli-parsing.test.ts +7 -4
- package/src/cli.ts +524 -417
- package/src/commands/abort.ts +16 -16
- package/src/commands/agent.ts +19 -5
- package/src/commands/ask-question.ts +7 -3
- package/src/commands/compact.ts +14 -17
- package/src/commands/context-usage.ts +21 -24
- package/src/commands/diff.ts +13 -9
- package/src/commands/file-upload.ts +5 -6
- package/src/commands/fork.ts +54 -32
- package/src/commands/gemini-apikey.ts +4 -5
- package/src/commands/login.ts +4 -5
- package/src/commands/mention-mode.ts +10 -5
- package/src/commands/merge-worktree.ts +18 -4
- package/src/commands/model.ts +202 -72
- package/src/commands/queue.ts +21 -31
- package/src/commands/remove-project.ts +19 -6
- package/src/commands/restart-opencode-server.ts +70 -13
- package/src/commands/resume.ts +6 -1
- package/src/commands/run-command.ts +22 -13
- package/src/commands/share.ts +11 -16
- package/src/commands/undo-redo.ts +15 -19
- package/src/commands/unset-model.ts +7 -6
- package/src/commands/user-command.ts +8 -11
- package/src/commands/verbosity.ts +10 -4
- package/src/commands/worktree-settings.ts +10 -5
- package/src/commands/worktree.ts +27 -12
- package/src/config.ts +12 -0
- package/src/database.ts +470 -379
- package/src/db.test.ts +21 -21
- package/src/db.ts +41 -7
- package/src/discord-bot.ts +70 -33
- package/src/discord-utils.ts +27 -19
- package/src/errors.ts +1 -4
- package/src/format-tables.test.ts +11 -2
- package/src/format-tables.ts +1 -3
- package/src/generated/internal/class.ts +2 -2
- package/src/generated/internal/prismaNamespace.ts +3 -0
- package/src/generated/internal/prismaNamespaceBrowser.ts +3 -0
- package/src/generated/models/channel_models.ts +33 -1
- package/src/generated/models/global_models.ts +33 -1
- package/src/generated/models/session_models.ts +29 -1
- package/src/image-utils.ts +3 -1
- package/src/interaction-handler.ts +22 -9
- package/src/logger.ts +1 -0
- package/src/message-formatting.ts +3 -1
- package/src/opencode-plugin.ts +4 -9
- package/src/opencode.ts +8 -2
- package/src/schema.sql +3 -0
- package/src/session-handler.ts +301 -215
- package/src/system-message.ts +65 -37
- package/src/thinking-utils.ts +61 -0
- package/src/tools.ts +3 -1
- package/src/unnest-code-blocks.test.ts +196 -1
- package/src/unnest-code-blocks.ts +48 -18
- package/src/voice.ts +0 -1
- package/src/wait-session.ts +15 -7
- package/src/worktree-utils.ts +94 -43
package/dist/bin.js
CHANGED
|
@@ -38,7 +38,8 @@ else {
|
|
|
38
38
|
}
|
|
39
39
|
const now = Date.now();
|
|
40
40
|
restartTimestamps.push(now);
|
|
41
|
-
while (restartTimestamps.length > 0 &&
|
|
41
|
+
while (restartTimestamps.length > 0 &&
|
|
42
|
+
restartTimestamps[0] < now - RAPID_RESTART_WINDOW_MS) {
|
|
42
43
|
restartTimestamps.shift();
|
|
43
44
|
}
|
|
44
45
|
if (restartTimestamps.length > MAX_RAPID_RESTARTS) {
|
package/dist/cli-parsing.test.js
CHANGED
|
@@ -8,8 +8,7 @@ function createCliForIdParsing() {
|
|
|
8
8
|
.option('-c, --channel <channelId>', 'Discord channel ID')
|
|
9
9
|
.option('--thread <threadId>', 'Thread ID')
|
|
10
10
|
.option('--session <sessionId>', 'Session ID');
|
|
11
|
-
cli
|
|
12
|
-
.command('session archive <threadId>', 'Archive a thread');
|
|
11
|
+
cli.command('session archive <threadId>', 'Archive a thread');
|
|
13
12
|
cli
|
|
14
13
|
.command('add-project', 'Add a project')
|
|
15
14
|
.option('-g, --guild <guildId>', 'Discord guild/server ID');
|
|
@@ -21,13 +20,17 @@ describe('goke CLI ID parsing', () => {
|
|
|
21
20
|
const channelId = '1234567890123456789';
|
|
22
21
|
const threadId = '9876543210987654321';
|
|
23
22
|
const sessionId = '1111222233334444555';
|
|
24
|
-
const channelResult = cli.parse(['node', 'kimaki', 'send', '--channel', channelId], {
|
|
23
|
+
const channelResult = cli.parse(['node', 'kimaki', 'send', '--channel', channelId], {
|
|
24
|
+
run: false,
|
|
25
|
+
});
|
|
25
26
|
expect(channelResult.options.channel).toBe(channelId);
|
|
26
27
|
expect(typeof channelResult.options.channel).toBe('string');
|
|
27
28
|
const threadResult = cli.parse(['node', 'kimaki', 'send', '--thread', threadId], { run: false });
|
|
28
29
|
expect(threadResult.options.thread).toBe(threadId);
|
|
29
30
|
expect(typeof threadResult.options.thread).toBe('string');
|
|
30
|
-
const sessionResult = cli.parse(['node', 'kimaki', 'send', '--session', sessionId], {
|
|
31
|
+
const sessionResult = cli.parse(['node', 'kimaki', 'send', '--session', sessionId], {
|
|
32
|
+
run: false,
|
|
33
|
+
});
|
|
31
34
|
expect(sessionResult.options.session).toBe(sessionId);
|
|
32
35
|
expect(typeof sessionResult.options.session).toBe('string');
|
|
33
36
|
});
|
package/dist/cli.js
CHANGED
|
@@ -19,9 +19,9 @@ import { createLogger, formatErrorWithStack, LogPrefix } from './logger.js';
|
|
|
19
19
|
import { archiveThread, uploadFilesToDiscord, stripMentions } from './discord-utils.js';
|
|
20
20
|
import { spawn, spawnSync, execSync } from 'node:child_process';
|
|
21
21
|
import http from 'node:http';
|
|
22
|
-
import { setDataDir, getDataDir, getLockPort, setDefaultVerbosity, setDefaultMentionMode, getProjectsDir } from './config.js';
|
|
22
|
+
import { setDataDir, getDataDir, getLockPort, setDefaultVerbosity, setDefaultMentionMode, setCritiqueEnabled, getProjectsDir, } from './config.js';
|
|
23
23
|
import { sanitizeAgentName } from './commands/agent.js';
|
|
24
|
-
import { showFileUploadButton
|
|
24
|
+
import { showFileUploadButton } from './commands/file-upload.js';
|
|
25
25
|
import { execAsync } from './worktree-utils.js';
|
|
26
26
|
import { backgroundUpgradeKimaki, upgrade, getCurrentVersion } from './upgrade.js';
|
|
27
27
|
const cliLogger = createLogger(LogPrefix.CLI);
|
|
@@ -32,7 +32,10 @@ function stripBracketedPaste(value) {
|
|
|
32
32
|
if (!value) {
|
|
33
33
|
return '';
|
|
34
34
|
}
|
|
35
|
-
return value
|
|
35
|
+
return value
|
|
36
|
+
.replace(/\x1b\[200~/g, '')
|
|
37
|
+
.replace(/\x1b\[201~/g, '')
|
|
38
|
+
.trim();
|
|
36
39
|
}
|
|
37
40
|
function isThreadChannelType(type) {
|
|
38
41
|
return [
|
|
@@ -93,7 +96,11 @@ async function ensureCommandAvailable({ name, envPathKey, installUnix, installWi
|
|
|
93
96
|
}
|
|
94
97
|
const isWindows = process.platform === 'win32';
|
|
95
98
|
const whichCmd = isWindows ? 'where' : 'which';
|
|
96
|
-
const isInstalled = await execAsync(`${whichCmd} ${name}`, { env: process.env }).then(() => {
|
|
99
|
+
const isInstalled = await execAsync(`${whichCmd} ${name}`, { env: process.env }).then(() => {
|
|
100
|
+
return true;
|
|
101
|
+
}, () => {
|
|
102
|
+
return false;
|
|
103
|
+
});
|
|
97
104
|
if (isInstalled) {
|
|
98
105
|
return;
|
|
99
106
|
}
|
|
@@ -134,7 +141,11 @@ async function ensureCommandAvailable({ name, envPathKey, installUnix, installWi
|
|
|
134
141
|
process.exit(EXIT_NO_RESTART);
|
|
135
142
|
}
|
|
136
143
|
// After install, re-check PATH first (install script may have added it)
|
|
137
|
-
const foundInPath = await execAsync(`${whichCmd} ${name}`, { env: process.env }).then((result) => {
|
|
144
|
+
const foundInPath = await execAsync(`${whichCmd} ${name}`, { env: process.env }).then((result) => {
|
|
145
|
+
return result.stdout.trim();
|
|
146
|
+
}, () => {
|
|
147
|
+
return '';
|
|
148
|
+
});
|
|
138
149
|
if (foundInPath) {
|
|
139
150
|
process.env[envPathKey] = foundInPath;
|
|
140
151
|
return;
|
|
@@ -143,8 +154,12 @@ async function ensureCommandAvailable({ name, envPathKey, installUnix, installWi
|
|
|
143
154
|
const home = process.env.HOME || process.env.USERPROFILE || '';
|
|
144
155
|
const accessFlag = isWindows ? fs.constants.F_OK : fs.constants.X_OK;
|
|
145
156
|
const possiblePaths = (isWindows ? possiblePathsWindows : possiblePathsUnix)
|
|
146
|
-
.filter((p) => {
|
|
147
|
-
|
|
157
|
+
.filter((p) => {
|
|
158
|
+
return !p.startsWith('~') || home;
|
|
159
|
+
})
|
|
160
|
+
.map((p) => {
|
|
161
|
+
return p.replace('~', home);
|
|
162
|
+
});
|
|
148
163
|
const installedPath = possiblePaths.find((p) => {
|
|
149
164
|
try {
|
|
150
165
|
fs.accessSync(p, accessFlag);
|
|
@@ -306,7 +321,9 @@ async function startLockServer() {
|
|
|
306
321
|
};
|
|
307
322
|
if (!request.sessionId || !request.threadId || !request.directory) {
|
|
308
323
|
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
309
|
-
res.end(JSON.stringify({
|
|
324
|
+
res.end(JSON.stringify({
|
|
325
|
+
error: 'Missing required fields: sessionId, threadId, directory',
|
|
326
|
+
}));
|
|
310
327
|
return;
|
|
311
328
|
}
|
|
312
329
|
const thread = await discordClientRef.channels.fetch(request.threadId);
|
|
@@ -584,10 +601,7 @@ async function registerCommands({ token, appId, userCommands = [], agents = [],
|
|
|
584
601
|
.setName('run-shell-command')
|
|
585
602
|
.setDescription('Run a shell command in the project directory. Tip: prefix messages with ! as shortcut')
|
|
586
603
|
.addStringOption((option) => {
|
|
587
|
-
option
|
|
588
|
-
.setName('command')
|
|
589
|
-
.setDescription('Command to run')
|
|
590
|
-
.setRequired(true);
|
|
604
|
+
option.setName('command').setDescription('Command to run').setRequired(true);
|
|
591
605
|
return option;
|
|
592
606
|
})
|
|
593
607
|
.setDMPermission(false)
|
|
@@ -772,13 +786,8 @@ async function run({ restart, addChannels, useWorktrees, enableVoiceChannels })
|
|
|
772
786
|
envPathKey: 'BUN_PATH',
|
|
773
787
|
installUnix: 'curl -fsSL https://bun.sh/install | bash',
|
|
774
788
|
installWindows: 'irm bun.sh/install.ps1 | iex',
|
|
775
|
-
possiblePathsUnix: [
|
|
776
|
-
|
|
777
|
-
'/usr/local/bin/bun',
|
|
778
|
-
],
|
|
779
|
-
possiblePathsWindows: [
|
|
780
|
-
'~\\.bun\\bin\\bun.exe',
|
|
781
|
-
],
|
|
789
|
+
possiblePathsUnix: ['~/.bun/bin/bun', '/usr/local/bin/bun'],
|
|
790
|
+
possiblePathsWindows: ['~\\.bun\\bin\\bun.exe'],
|
|
782
791
|
});
|
|
783
792
|
backgroundUpgradeOpencode();
|
|
784
793
|
backgroundUpgradeKimaki();
|
|
@@ -1105,6 +1114,7 @@ cli
|
|
|
1105
1114
|
.option('--enable-voice-channels', 'Create voice channels for projects (disabled by default)')
|
|
1106
1115
|
.option('--verbosity <level>', 'Default verbosity for all channels (tools-and-text, text-and-essential-tools, or text-only)')
|
|
1107
1116
|
.option('--mention-mode', 'Bot only responds when @mentioned (default for all channels)')
|
|
1117
|
+
.option('--no-critique', 'Disable automatic diff upload to critique.work in system prompts')
|
|
1108
1118
|
.option('--auto-restart', 'Automatically restart the bot on crash or OOM kill')
|
|
1109
1119
|
.action(async (options) => {
|
|
1110
1120
|
try {
|
|
@@ -1126,6 +1136,10 @@ cli
|
|
|
1126
1136
|
setDefaultMentionMode(true);
|
|
1127
1137
|
cliLogger.log('Default mention mode: enabled (bot only responds when @mentioned)');
|
|
1128
1138
|
}
|
|
1139
|
+
if (options.noCritique) {
|
|
1140
|
+
setCritiqueEnabled(false);
|
|
1141
|
+
cliLogger.log('Critique disabled: diffs will not be auto-uploaded to critique.work');
|
|
1142
|
+
}
|
|
1129
1143
|
if (options.installUrl) {
|
|
1130
1144
|
await initDatabase();
|
|
1131
1145
|
const existingBot = await getBotToken();
|
|
@@ -1318,11 +1332,16 @@ cli
|
|
|
1318
1332
|
try {
|
|
1319
1333
|
// Helper to find channel for a path (prefers current bot's channel)
|
|
1320
1334
|
const findChannelForPath = async (dirPath) => {
|
|
1321
|
-
const withAppId = appId
|
|
1335
|
+
const withAppId = appId
|
|
1336
|
+
? await findChannelsByDirectory({ directory: dirPath, channelType: 'text', appId })
|
|
1337
|
+
: [];
|
|
1322
1338
|
if (withAppId.length > 0) {
|
|
1323
1339
|
return withAppId[0];
|
|
1324
1340
|
}
|
|
1325
|
-
const withoutAppId = await findChannelsByDirectory({
|
|
1341
|
+
const withoutAppId = await findChannelsByDirectory({
|
|
1342
|
+
directory: dirPath,
|
|
1343
|
+
channelType: 'text',
|
|
1344
|
+
});
|
|
1326
1345
|
return withoutAppId[0];
|
|
1327
1346
|
};
|
|
1328
1347
|
// Try exact match first, then walk up parent directories
|
|
@@ -1506,9 +1525,7 @@ cli
|
|
|
1506
1525
|
const worktreeName = options.worktree
|
|
1507
1526
|
? formatWorktreeName(typeof options.worktree === 'string' ? options.worktree : baseThreadName)
|
|
1508
1527
|
: undefined;
|
|
1509
|
-
const threadName = worktreeName
|
|
1510
|
-
? `${WORKTREE_PREFIX}${baseThreadName}`
|
|
1511
|
-
: baseThreadName;
|
|
1528
|
+
const threadName = worktreeName ? `${WORKTREE_PREFIX}${baseThreadName}` : baseThreadName;
|
|
1512
1529
|
// Embed marker for auto-start sessions (unless --notify-only)
|
|
1513
1530
|
// Bot parses this YAML to know it should start a session, optionally create a worktree, and set initial user
|
|
1514
1531
|
const embedMarker = notifyOnly
|
|
@@ -1544,7 +1561,9 @@ cli
|
|
|
1544
1561
|
await rest.put(Routes.threadMembers(threadData.id, resolvedUser.id));
|
|
1545
1562
|
}
|
|
1546
1563
|
const threadUrl = `https://discord.com/channels/${channelData.guild_id}/${threadData.id}`;
|
|
1547
|
-
const worktreeNote = worktreeName
|
|
1564
|
+
const worktreeNote = worktreeName
|
|
1565
|
+
? `\nWorktree: ${worktreeName} (will be created by bot)`
|
|
1566
|
+
: '';
|
|
1548
1567
|
const successMessage = notifyOnly
|
|
1549
1568
|
? `Thread: ${threadData.name}\nDirectory: ${projectDirectory}\n\nNotification created. Reply to start a session.\n\nURL: ${threadUrl}`
|
|
1550
1569
|
: `Thread: ${threadData.name}\nDirectory: ${projectDirectory}${worktreeNote}\n\nThe running bot will pick this up and start the session.\n\nURL: ${threadUrl}`;
|
|
@@ -1774,11 +1793,16 @@ cli
|
|
|
1774
1793
|
const absolutePath = path.resolve('.');
|
|
1775
1794
|
// Walk up parent directories to find a matching channel
|
|
1776
1795
|
const findChannelForPath = async (dirPath) => {
|
|
1777
|
-
const withAppId = appId
|
|
1796
|
+
const withAppId = appId
|
|
1797
|
+
? await findChannelsByDirectory({ directory: dirPath, channelType: 'text', appId })
|
|
1798
|
+
: [];
|
|
1778
1799
|
if (withAppId.length > 0) {
|
|
1779
1800
|
return withAppId[0];
|
|
1780
1801
|
}
|
|
1781
|
-
const withoutAppId = await findChannelsByDirectory({
|
|
1802
|
+
const withoutAppId = await findChannelsByDirectory({
|
|
1803
|
+
directory: dirPath,
|
|
1804
|
+
channelType: 'text',
|
|
1805
|
+
});
|
|
1782
1806
|
return withoutAppId[0];
|
|
1783
1807
|
};
|
|
1784
1808
|
let existingChannel;
|
|
@@ -1918,9 +1942,7 @@ cli
|
|
|
1918
1942
|
command: command.length > 0 ? command : undefined,
|
|
1919
1943
|
});
|
|
1920
1944
|
});
|
|
1921
|
-
cli
|
|
1922
|
-
.command('sqlitedb', 'Show the location of the SQLite database file')
|
|
1923
|
-
.action(() => {
|
|
1945
|
+
cli.command('sqlitedb', 'Show the location of the SQLite database file').action(() => {
|
|
1924
1946
|
const dataDir = getDataDir();
|
|
1925
1947
|
const dbPath = path.join(dataDir, 'discord-sessions.db');
|
|
1926
1948
|
cliLogger.log(dbPath);
|
|
@@ -1992,15 +2014,48 @@ cli
|
|
|
1992
2014
|
cliLogger.error('Failed to connect to OpenCode:', getClient.message);
|
|
1993
2015
|
process.exit(EXIT_NO_RESTART);
|
|
1994
2016
|
}
|
|
2017
|
+
// Try current project first (fast path)
|
|
1995
2018
|
const markdown = new ShareMarkdown(getClient());
|
|
1996
2019
|
const result = await markdown.generate({ sessionID: sessionId });
|
|
1997
|
-
if (result instanceof Error) {
|
|
1998
|
-
|
|
1999
|
-
process.exit(
|
|
2020
|
+
if (!(result instanceof Error)) {
|
|
2021
|
+
process.stdout.write(result);
|
|
2022
|
+
process.exit(0);
|
|
2000
2023
|
}
|
|
2001
|
-
//
|
|
2002
|
-
|
|
2003
|
-
|
|
2024
|
+
// Session not found in current project, search across all projects.
|
|
2025
|
+
// project.list() returns all known projects globally from any OpenCode server,
|
|
2026
|
+
// but session.list/get are scoped to the server's own project. So we try each.
|
|
2027
|
+
cliLogger.log('Session not in current project, searching all projects...');
|
|
2028
|
+
const projectsResponse = await getClient().project.list({});
|
|
2029
|
+
const projects = projectsResponse.data || [];
|
|
2030
|
+
const otherProjects = projects
|
|
2031
|
+
.filter((p) => path.resolve(p.worktree) !== projectDirectory)
|
|
2032
|
+
.filter((p) => {
|
|
2033
|
+
try {
|
|
2034
|
+
fs.accessSync(p.worktree, fs.constants.R_OK);
|
|
2035
|
+
return true;
|
|
2036
|
+
}
|
|
2037
|
+
catch {
|
|
2038
|
+
return false;
|
|
2039
|
+
}
|
|
2040
|
+
})
|
|
2041
|
+
// Sort by most recently created first to find sessions faster
|
|
2042
|
+
.sort((a, b) => b.time.created - a.time.created);
|
|
2043
|
+
for (const project of otherProjects) {
|
|
2044
|
+
const dir = project.worktree;
|
|
2045
|
+
cliLogger.log(`Trying project: ${dir}`);
|
|
2046
|
+
const otherClient = await initializeOpencodeForDirectory(dir);
|
|
2047
|
+
if (otherClient instanceof Error) {
|
|
2048
|
+
continue;
|
|
2049
|
+
}
|
|
2050
|
+
const otherMarkdown = new ShareMarkdown(otherClient());
|
|
2051
|
+
const otherResult = await otherMarkdown.generate({ sessionID: sessionId });
|
|
2052
|
+
if (!(otherResult instanceof Error)) {
|
|
2053
|
+
process.stdout.write(otherResult);
|
|
2054
|
+
process.exit(0);
|
|
2055
|
+
}
|
|
2056
|
+
}
|
|
2057
|
+
cliLogger.error(`Session ${sessionId} not found in any project`);
|
|
2058
|
+
process.exit(EXIT_NO_RESTART);
|
|
2004
2059
|
}
|
|
2005
2060
|
catch (error) {
|
|
2006
2061
|
cliLogger.error('Error:', error instanceof Error ? error.message : String(error));
|
|
@@ -2060,9 +2115,7 @@ cli
|
|
|
2060
2115
|
process.exit(EXIT_NO_RESTART);
|
|
2061
2116
|
}
|
|
2062
2117
|
});
|
|
2063
|
-
cli
|
|
2064
|
-
.command('upgrade', 'Upgrade kimaki to the latest version')
|
|
2065
|
-
.action(async () => {
|
|
2118
|
+
cli.command('upgrade', 'Upgrade kimaki to the latest version').action(async () => {
|
|
2066
2119
|
try {
|
|
2067
2120
|
const current = getCurrentVersion();
|
|
2068
2121
|
cliLogger.log(`Current version: v${current}`);
|
package/dist/commands/abort.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// /abort command - Abort the current OpenCode request in this thread.
|
|
2
|
-
import { ChannelType } from 'discord.js';
|
|
2
|
+
import { ChannelType, MessageFlags } from 'discord.js';
|
|
3
3
|
import { getThreadSession } from '../database.js';
|
|
4
4
|
import { initializeOpencodeForDirectory } from '../opencode.js';
|
|
5
5
|
import { resolveWorkingDirectory, SILENT_MESSAGE_FLAGS } from '../discord-utils.js';
|
|
@@ -11,8 +11,7 @@ export async function handleAbortCommand({ command }) {
|
|
|
11
11
|
if (!channel) {
|
|
12
12
|
await command.reply({
|
|
13
13
|
content: 'This command can only be used in a channel',
|
|
14
|
-
|
|
15
|
-
flags: SILENT_MESSAGE_FLAGS,
|
|
14
|
+
flags: MessageFlags.Ephemeral | SILENT_MESSAGE_FLAGS,
|
|
16
15
|
});
|
|
17
16
|
return;
|
|
18
17
|
}
|
|
@@ -24,17 +23,17 @@ export async function handleAbortCommand({ command }) {
|
|
|
24
23
|
if (!isThread) {
|
|
25
24
|
await command.reply({
|
|
26
25
|
content: 'This command can only be used in a thread with an active session',
|
|
27
|
-
|
|
28
|
-
flags: SILENT_MESSAGE_FLAGS,
|
|
26
|
+
flags: MessageFlags.Ephemeral | SILENT_MESSAGE_FLAGS,
|
|
29
27
|
});
|
|
30
28
|
return;
|
|
31
29
|
}
|
|
32
|
-
const resolved = await resolveWorkingDirectory({
|
|
30
|
+
const resolved = await resolveWorkingDirectory({
|
|
31
|
+
channel: channel,
|
|
32
|
+
});
|
|
33
33
|
if (!resolved) {
|
|
34
34
|
await command.reply({
|
|
35
35
|
content: 'Could not determine project directory for this channel',
|
|
36
|
-
|
|
37
|
-
flags: SILENT_MESSAGE_FLAGS,
|
|
36
|
+
flags: MessageFlags.Ephemeral | SILENT_MESSAGE_FLAGS,
|
|
38
37
|
});
|
|
39
38
|
return;
|
|
40
39
|
}
|
|
@@ -43,8 +42,7 @@ export async function handleAbortCommand({ command }) {
|
|
|
43
42
|
if (!sessionId) {
|
|
44
43
|
await command.reply({
|
|
45
44
|
content: 'No active session in this thread',
|
|
46
|
-
|
|
47
|
-
flags: SILENT_MESSAGE_FLAGS,
|
|
45
|
+
flags: MessageFlags.Ephemeral | SILENT_MESSAGE_FLAGS,
|
|
48
46
|
});
|
|
49
47
|
return;
|
|
50
48
|
}
|
|
@@ -58,8 +56,7 @@ export async function handleAbortCommand({ command }) {
|
|
|
58
56
|
if (getClient instanceof Error) {
|
|
59
57
|
await command.reply({
|
|
60
58
|
content: `Failed to abort: ${getClient.message}`,
|
|
61
|
-
|
|
62
|
-
flags: SILENT_MESSAGE_FLAGS,
|
|
59
|
+
flags: MessageFlags.Ephemeral | SILENT_MESSAGE_FLAGS,
|
|
63
60
|
});
|
|
64
61
|
return;
|
|
65
62
|
}
|
|
@@ -78,8 +75,7 @@ export async function handleAbortCommand({ command }) {
|
|
|
78
75
|
logger.error('[ABORT] Error:', error);
|
|
79
76
|
await command.reply({
|
|
80
77
|
content: `Failed to abort: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
81
|
-
|
|
82
|
-
flags: SILENT_MESSAGE_FLAGS,
|
|
78
|
+
flags: MessageFlags.Ephemeral | SILENT_MESSAGE_FLAGS,
|
|
83
79
|
});
|
|
84
80
|
}
|
|
85
81
|
}
|
package/dist/commands/agent.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
// /agent command - Set the preferred agent for this channel or session.
|
|
2
2
|
// Also provides quick agent commands like /plan-agent, /build-agent that switch instantly.
|
|
3
|
-
import { ChatInputCommandInteraction, StringSelectMenuInteraction, StringSelectMenuBuilder, ActionRowBuilder, ChannelType, } from 'discord.js';
|
|
3
|
+
import { ChatInputCommandInteraction, StringSelectMenuInteraction, StringSelectMenuBuilder, ActionRowBuilder, ChannelType, MessageFlags, } from 'discord.js';
|
|
4
4
|
import crypto from 'node:crypto';
|
|
5
|
-
import { setChannelAgent, setSessionAgent, clearSessionModel, getThreadSession, getSessionAgent, getChannelAgent } from '../database.js';
|
|
5
|
+
import { setChannelAgent, setSessionAgent, clearSessionModel, getThreadSession, getSessionAgent, getChannelAgent, } from '../database.js';
|
|
6
6
|
import { initializeOpencodeForDirectory } from '../opencode.js';
|
|
7
7
|
import { resolveTextChannel, getKimakiMetadata } from '../discord-utils.js';
|
|
8
8
|
import { createLogger, LogPrefix } from '../logger.js';
|
|
@@ -32,7 +32,11 @@ export async function getCurrentAgentInfo({ sessionId, channelId, }) {
|
|
|
32
32
|
* Lowercase, alphanumeric and hyphens only.
|
|
33
33
|
*/
|
|
34
34
|
export function sanitizeAgentName(name) {
|
|
35
|
-
return name
|
|
35
|
+
return name
|
|
36
|
+
.toLowerCase()
|
|
37
|
+
.replace(/[^a-z0-9-]/g, '-')
|
|
38
|
+
.replace(/-+/g, '-')
|
|
39
|
+
.replace(/^-|-$/g, '');
|
|
36
40
|
}
|
|
37
41
|
/**
|
|
38
42
|
* Resolve the context for an agent command (directory, channel, session).
|
|
@@ -110,7 +114,7 @@ export async function setAgentForContext({ context, agentName, }) {
|
|
|
110
114
|
}
|
|
111
115
|
}
|
|
112
116
|
export async function handleAgentCommand({ interaction, appId, }) {
|
|
113
|
-
await interaction.deferReply({
|
|
117
|
+
await interaction.deferReply({ flags: MessageFlags.Ephemeral });
|
|
114
118
|
const context = await resolveAgentCommandContext({ interaction, appId });
|
|
115
119
|
if (!context) {
|
|
116
120
|
return;
|
|
@@ -236,7 +240,7 @@ export async function handleAgentSelectMenu(interaction) {
|
|
|
236
240
|
export async function handleQuickAgentCommand({ command, appId, }) {
|
|
237
241
|
// Extract agent name from command: "plan-agent" → "plan"
|
|
238
242
|
const sanitizedAgentName = command.commandName.replace(/-agent$/, '');
|
|
239
|
-
await command.deferReply({
|
|
243
|
+
await command.deferReply({ flags: MessageFlags.Ephemeral });
|
|
240
244
|
const context = await resolveAgentCommandContext({ interaction: command, appId });
|
|
241
245
|
if (!context) {
|
|
242
246
|
return;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// AskUserQuestion tool handler - Shows Discord dropdowns for AI questions.
|
|
2
2
|
// When the AI uses the AskUserQuestion tool, this module renders dropdowns
|
|
3
3
|
// for each question and collects user responses.
|
|
4
|
-
import { StringSelectMenuBuilder, StringSelectMenuInteraction, ActionRowBuilder, } from 'discord.js';
|
|
4
|
+
import { StringSelectMenuBuilder, StringSelectMenuInteraction, ActionRowBuilder, MessageFlags, } from 'discord.js';
|
|
5
5
|
import crypto from 'node:crypto';
|
|
6
6
|
import { sendThreadMessage, NOTIFY_MESSAGE_FLAGS } from '../discord-utils.js';
|
|
7
7
|
import { getOpencodeClientV2 } from '../opencode.js';
|
|
@@ -77,7 +77,7 @@ export async function handleAskQuestionSelectMenu(interaction) {
|
|
|
77
77
|
if (!contextHash) {
|
|
78
78
|
await interaction.reply({
|
|
79
79
|
content: 'Invalid selection.',
|
|
80
|
-
|
|
80
|
+
flags: MessageFlags.Ephemeral,
|
|
81
81
|
});
|
|
82
82
|
return;
|
|
83
83
|
}
|
|
@@ -85,7 +85,7 @@ export async function handleAskQuestionSelectMenu(interaction) {
|
|
|
85
85
|
if (!context) {
|
|
86
86
|
await interaction.reply({
|
|
87
87
|
content: 'This question has expired. Please ask the AI again.',
|
|
88
|
-
|
|
88
|
+
flags: MessageFlags.Ephemeral,
|
|
89
89
|
});
|
|
90
90
|
return;
|
|
91
91
|
}
|
package/dist/commands/compact.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// /compact command - Trigger context compaction (summarization) for the current session.
|
|
2
|
-
import { ChannelType } from 'discord.js';
|
|
2
|
+
import { ChannelType, MessageFlags } from 'discord.js';
|
|
3
3
|
import { getThreadSession } from '../database.js';
|
|
4
4
|
import { initializeOpencodeForDirectory, getOpencodeClientV2 } from '../opencode.js';
|
|
5
5
|
import { resolveWorkingDirectory, SILENT_MESSAGE_FLAGS } from '../discord-utils.js';
|
|
@@ -10,8 +10,7 @@ export async function handleCompactCommand({ command }) {
|
|
|
10
10
|
if (!channel) {
|
|
11
11
|
await command.reply({
|
|
12
12
|
content: 'This command can only be used in a channel',
|
|
13
|
-
|
|
14
|
-
flags: SILENT_MESSAGE_FLAGS,
|
|
13
|
+
flags: MessageFlags.Ephemeral | SILENT_MESSAGE_FLAGS,
|
|
15
14
|
});
|
|
16
15
|
return;
|
|
17
16
|
}
|
|
@@ -23,17 +22,17 @@ export async function handleCompactCommand({ command }) {
|
|
|
23
22
|
if (!isThread) {
|
|
24
23
|
await command.reply({
|
|
25
24
|
content: 'This command can only be used in a thread with an active session',
|
|
26
|
-
|
|
27
|
-
flags: SILENT_MESSAGE_FLAGS,
|
|
25
|
+
flags: MessageFlags.Ephemeral | SILENT_MESSAGE_FLAGS,
|
|
28
26
|
});
|
|
29
27
|
return;
|
|
30
28
|
}
|
|
31
|
-
const resolved = await resolveWorkingDirectory({
|
|
29
|
+
const resolved = await resolveWorkingDirectory({
|
|
30
|
+
channel: channel,
|
|
31
|
+
});
|
|
32
32
|
if (!resolved) {
|
|
33
33
|
await command.reply({
|
|
34
34
|
content: 'Could not determine project directory for this channel',
|
|
35
|
-
|
|
36
|
-
flags: SILENT_MESSAGE_FLAGS,
|
|
35
|
+
flags: MessageFlags.Ephemeral | SILENT_MESSAGE_FLAGS,
|
|
37
36
|
});
|
|
38
37
|
return;
|
|
39
38
|
}
|
|
@@ -42,8 +41,7 @@ export async function handleCompactCommand({ command }) {
|
|
|
42
41
|
if (!sessionId) {
|
|
43
42
|
await command.reply({
|
|
44
43
|
content: 'No active session in this thread',
|
|
45
|
-
|
|
46
|
-
flags: SILENT_MESSAGE_FLAGS,
|
|
44
|
+
flags: MessageFlags.Ephemeral | SILENT_MESSAGE_FLAGS,
|
|
47
45
|
});
|
|
48
46
|
return;
|
|
49
47
|
}
|
|
@@ -52,8 +50,7 @@ export async function handleCompactCommand({ command }) {
|
|
|
52
50
|
if (getClient instanceof Error) {
|
|
53
51
|
await command.reply({
|
|
54
52
|
content: `Failed to compact: ${getClient.message}`,
|
|
55
|
-
|
|
56
|
-
flags: SILENT_MESSAGE_FLAGS,
|
|
53
|
+
flags: MessageFlags.Ephemeral | SILENT_MESSAGE_FLAGS,
|
|
57
54
|
});
|
|
58
55
|
return;
|
|
59
56
|
}
|
|
@@ -61,8 +58,7 @@ export async function handleCompactCommand({ command }) {
|
|
|
61
58
|
if (!clientV2) {
|
|
62
59
|
await command.reply({
|
|
63
60
|
content: 'Failed to get OpenCode client',
|
|
64
|
-
|
|
65
|
-
flags: SILENT_MESSAGE_FLAGS,
|
|
61
|
+
flags: MessageFlags.Ephemeral | SILENT_MESSAGE_FLAGS,
|
|
66
62
|
});
|
|
67
63
|
return;
|
|
68
64
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// /context-usage command - Show token usage and context window percentage for the current session.
|
|
2
|
-
import { ChannelType } from 'discord.js';
|
|
2
|
+
import { ChannelType, MessageFlags } from 'discord.js';
|
|
3
3
|
import { getThreadSession } from '../database.js';
|
|
4
4
|
import { initializeOpencodeForDirectory } from '../opencode.js';
|
|
5
5
|
import { resolveWorkingDirectory, SILENT_MESSAGE_FLAGS } from '../discord-utils.js';
|
|
@@ -14,8 +14,7 @@ export async function handleContextUsageCommand({ command }) {
|
|
|
14
14
|
if (!channel) {
|
|
15
15
|
await command.reply({
|
|
16
16
|
content: 'This command can only be used in a channel',
|
|
17
|
-
|
|
18
|
-
flags: SILENT_MESSAGE_FLAGS,
|
|
17
|
+
flags: MessageFlags.Ephemeral | SILENT_MESSAGE_FLAGS,
|
|
19
18
|
});
|
|
20
19
|
return;
|
|
21
20
|
}
|
|
@@ -27,17 +26,17 @@ export async function handleContextUsageCommand({ command }) {
|
|
|
27
26
|
if (!isThread) {
|
|
28
27
|
await command.reply({
|
|
29
28
|
content: 'This command can only be used in a thread with an active session',
|
|
30
|
-
|
|
31
|
-
flags: SILENT_MESSAGE_FLAGS,
|
|
29
|
+
flags: MessageFlags.Ephemeral | SILENT_MESSAGE_FLAGS,
|
|
32
30
|
});
|
|
33
31
|
return;
|
|
34
32
|
}
|
|
35
|
-
const resolved = await resolveWorkingDirectory({
|
|
33
|
+
const resolved = await resolveWorkingDirectory({
|
|
34
|
+
channel: channel,
|
|
35
|
+
});
|
|
36
36
|
if (!resolved) {
|
|
37
37
|
await command.reply({
|
|
38
38
|
content: 'Could not determine project directory for this channel',
|
|
39
|
-
|
|
40
|
-
flags: SILENT_MESSAGE_FLAGS,
|
|
39
|
+
flags: MessageFlags.Ephemeral | SILENT_MESSAGE_FLAGS,
|
|
41
40
|
});
|
|
42
41
|
return;
|
|
43
42
|
}
|
|
@@ -46,8 +45,7 @@ export async function handleContextUsageCommand({ command }) {
|
|
|
46
45
|
if (!sessionId) {
|
|
47
46
|
await command.reply({
|
|
48
47
|
content: 'No active session in this thread',
|
|
49
|
-
|
|
50
|
-
flags: SILENT_MESSAGE_FLAGS,
|
|
48
|
+
flags: MessageFlags.Ephemeral | SILENT_MESSAGE_FLAGS,
|
|
51
49
|
});
|
|
52
50
|
return;
|
|
53
51
|
}
|
|
@@ -55,8 +53,7 @@ export async function handleContextUsageCommand({ command }) {
|
|
|
55
53
|
if (getClient instanceof Error) {
|
|
56
54
|
await command.reply({
|
|
57
55
|
content: `Failed to get context usage: ${getClient.message}`,
|
|
58
|
-
|
|
59
|
-
flags: SILENT_MESSAGE_FLAGS,
|
|
56
|
+
flags: MessageFlags.Ephemeral | SILENT_MESSAGE_FLAGS,
|
|
60
57
|
});
|
|
61
58
|
return;
|
|
62
59
|
}
|
|
@@ -74,9 +71,7 @@ export async function handleContextUsageCommand({ command }) {
|
|
|
74
71
|
});
|
|
75
72
|
return;
|
|
76
73
|
}
|
|
77
|
-
const lastAssistant = [...assistantMessages]
|
|
78
|
-
.reverse()
|
|
79
|
-
.find((m) => {
|
|
74
|
+
const lastAssistant = [...assistantMessages].reverse().find((m) => {
|
|
80
75
|
if (m.info.role !== 'assistant') {
|
|
81
76
|
return false;
|
|
82
77
|
}
|
package/dist/commands/diff.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// /diff command - Show git diff as a shareable URL.
|
|
2
|
-
import { ChannelType, EmbedBuilder } from 'discord.js';
|
|
2
|
+
import { ChannelType, EmbedBuilder, MessageFlags, } from 'discord.js';
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import { resolveWorkingDirectory, SILENT_MESSAGE_FLAGS } from '../discord-utils.js';
|
|
5
5
|
import { createLogger, LogPrefix } from '../logger.js';
|
|
@@ -10,8 +10,7 @@ export async function handleDiffCommand({ command }) {
|
|
|
10
10
|
if (!channel) {
|
|
11
11
|
await command.reply({
|
|
12
12
|
content: 'This command can only be used in a channel',
|
|
13
|
-
|
|
14
|
-
flags: SILENT_MESSAGE_FLAGS,
|
|
13
|
+
flags: MessageFlags.Ephemeral | SILENT_MESSAGE_FLAGS,
|
|
15
14
|
});
|
|
16
15
|
return;
|
|
17
16
|
}
|
|
@@ -24,17 +23,17 @@ export async function handleDiffCommand({ command }) {
|
|
|
24
23
|
if (!isThread && !isTextChannel) {
|
|
25
24
|
await command.reply({
|
|
26
25
|
content: 'This command can only be used in a text channel or thread',
|
|
27
|
-
|
|
28
|
-
flags: SILENT_MESSAGE_FLAGS,
|
|
26
|
+
flags: MessageFlags.Ephemeral | SILENT_MESSAGE_FLAGS,
|
|
29
27
|
});
|
|
30
28
|
return;
|
|
31
29
|
}
|
|
32
|
-
const resolved = await resolveWorkingDirectory({
|
|
30
|
+
const resolved = await resolveWorkingDirectory({
|
|
31
|
+
channel: channel,
|
|
32
|
+
});
|
|
33
33
|
if (!resolved) {
|
|
34
34
|
await command.reply({
|
|
35
35
|
content: 'Could not determine project directory for this channel',
|
|
36
|
-
|
|
37
|
-
flags: SILENT_MESSAGE_FLAGS,
|
|
36
|
+
flags: MessageFlags.Ephemeral | SILENT_MESSAGE_FLAGS,
|
|
38
37
|
});
|
|
39
38
|
return;
|
|
40
39
|
}
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
// with the Discord bot via HTTP. The bot holds the HTTP response open until
|
|
9
9
|
// the user completes the upload or the request is cancelled. This bridges the
|
|
10
10
|
// gap between the plugin process and Discord's interaction-based UI.
|
|
11
|
-
import { ButtonBuilder, ButtonStyle, ActionRowBuilder, ModalBuilder, FileUploadBuilder, LabelBuilder, ComponentType, } from 'discord.js';
|
|
11
|
+
import { ButtonBuilder, ButtonStyle, ActionRowBuilder, ModalBuilder, FileUploadBuilder, LabelBuilder, ComponentType, MessageFlags, } from 'discord.js';
|
|
12
12
|
import crypto from 'node:crypto';
|
|
13
13
|
import fs from 'node:fs';
|
|
14
14
|
import path from 'node:path';
|
|
@@ -126,7 +126,7 @@ export async function handleFileUploadButton(interaction) {
|
|
|
126
126
|
if (!context || context.resolved) {
|
|
127
127
|
await interaction.reply({
|
|
128
128
|
content: 'This file upload request has expired.',
|
|
129
|
-
|
|
129
|
+
flags: MessageFlags.Ephemeral,
|
|
130
130
|
});
|
|
131
131
|
return;
|
|
132
132
|
}
|
|
@@ -157,12 +157,12 @@ export async function handleFileUploadModalSubmit(interaction) {
|
|
|
157
157
|
if (!context || context.resolved) {
|
|
158
158
|
await interaction.reply({
|
|
159
159
|
content: 'This file upload request has expired.',
|
|
160
|
-
|
|
160
|
+
flags: MessageFlags.Ephemeral,
|
|
161
161
|
});
|
|
162
162
|
return;
|
|
163
163
|
}
|
|
164
164
|
try {
|
|
165
|
-
await interaction.deferReply({
|
|
165
|
+
await interaction.deferReply({ flags: MessageFlags.Ephemeral });
|
|
166
166
|
// File upload data is nested in the LabelModalData -> FileUploadModalData
|
|
167
167
|
const fileField = interaction.fields.getField('uploaded_files', ComponentType.FileUpload);
|
|
168
168
|
const attachments = fileField.attachments;
|
|
@@ -208,9 +208,7 @@ export async function handleFileUploadModalSubmit(interaction) {
|
|
|
208
208
|
const fileNames = downloadedPaths.map((p) => {
|
|
209
209
|
return path.basename(p);
|
|
210
210
|
});
|
|
211
|
-
updateButtonMessage(context, downloadedPaths.length > 0
|
|
212
|
-
? `Uploaded: ${fileNames.join(', ')}`
|
|
213
|
-
: '_Upload failed_');
|
|
211
|
+
updateButtonMessage(context, downloadedPaths.length > 0 ? `Uploaded: ${fileNames.join(', ')}` : '_Upload failed_');
|
|
214
212
|
const summary = (() => {
|
|
215
213
|
if (downloadedPaths.length > 0 && errors.length === 0) {
|
|
216
214
|
return `Uploaded ${downloadedPaths.length} file(s) successfully.`;
|