kimaki 0.4.82 → 0.4.84
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/LICENSE +21 -0
- package/dist/anthropic-auth-plugin.js +7 -0
- package/dist/cli.js +51 -7
- package/dist/commands/abort.js +5 -16
- package/dist/commands/action-buttons.js +3 -3
- package/dist/commands/add-project.js +1 -1
- package/dist/commands/ask-question.js +3 -3
- package/dist/commands/context-usage.js +1 -1
- package/dist/commands/create-new-project.js +1 -1
- package/dist/commands/fork.js +11 -8
- package/dist/commands/merge-worktree.js +1 -1
- package/dist/commands/new-worktree.js +63 -44
- package/dist/commands/remove-project.js +1 -1
- package/dist/commands/resume.js +11 -8
- package/dist/commands/screenshare.js +14 -6
- package/dist/commands/screenshare.test.js +20 -0
- package/dist/commands/session.js +1 -1
- package/dist/commands/undo-redo.js +91 -7
- package/dist/commands/user-command.js +1 -1
- package/dist/config.js +16 -1
- package/dist/database.js +53 -2
- package/dist/db.js +6 -0
- package/dist/discord-bot.js +48 -85
- package/dist/discord-command-registration.js +1 -1
- package/dist/external-opencode-sync.js +515 -0
- package/dist/external-opencode-sync.test.js +151 -0
- package/dist/gateway-proxy.e2e.test.js +8 -5
- package/dist/genai.js +1 -1
- package/dist/generated/enums.js +4 -0
- package/dist/generated/internal/class.js +4 -4
- package/dist/generated/internal/prismaNamespace.js +1 -0
- package/dist/generated/internal/prismaNamespaceBrowser.js +1 -0
- package/dist/generated/models/external_session_pending_prompts.js +1 -0
- package/dist/hrana-server.js +14 -285
- package/dist/hrana-server.test.js +4 -2
- package/dist/kimaki-opencode-plugin-loading.e2e.test.js +7 -0
- package/dist/kimaki-opencode-plugin.js +2 -0
- package/dist/kitty-graphics-parser.js +3 -0
- package/dist/kitty-graphics-parser.test.js +276 -0
- package/dist/kitty-graphics-plugin.js +3 -0
- package/dist/markdown.js +4 -4
- package/dist/markdown.test.js +1 -1
- package/dist/message-formatting.js +54 -15
- package/dist/onboarding-tutorial.js +1 -1
- package/dist/openai-realtime.js +9 -13
- package/dist/opencode.js +28 -5
- package/dist/queue-advanced-e2e-setup.js +89 -0
- package/dist/queue-advanced-permissions-typing.e2e.test.js +5 -5
- package/dist/queue-advanced-typing.e2e.test.js +9 -22
- package/dist/queue-question-select-drain.e2e.test.js +117 -0
- package/dist/session-handler/event-stream-state.js +101 -7
- package/dist/session-handler/event-stream-state.test.js +7 -3
- package/dist/session-handler/thread-session-runtime.js +120 -9
- package/dist/store.js +1 -0
- package/dist/system-message.js +22 -4
- package/dist/system-message.test.js +19 -0
- package/dist/task-runner.js +1 -1
- package/dist/thread-message-queue.e2e.test.js +8 -14
- package/dist/tools.js +1 -1
- package/dist/undo-redo.e2e.test.js +20 -25
- package/package.json +10 -6
- package/schema.prisma +6 -0
- package/skills/errore/SKILL.md +40 -13
- package/skills/goke/SKILL.md +12 -0
- package/skills/lintcn/SKILL.md +868 -0
- package/skills/npm-package/SKILL.md +1 -0
- package/skills/proxyman/SKILL.md +215 -0
- package/skills/spiceflow/SKILL.md +1 -1
- package/skills/usecomputer/SKILL.md +339 -0
- package/src/ai-tool-to-genai.ts +1 -0
- package/src/anthropic-auth-plugin.ts +7 -0
- package/src/cli.ts +59 -6
- package/src/commands/abort.ts +6 -16
- package/src/commands/action-buttons.ts +5 -1
- package/src/commands/add-project.ts +1 -1
- package/src/commands/ask-question.ts +5 -2
- package/src/commands/context-usage.ts +1 -1
- package/src/commands/create-new-project.ts +1 -1
- package/src/commands/fork.ts +12 -11
- package/src/commands/merge-worktree.ts +1 -1
- package/src/commands/new-worktree.ts +74 -55
- package/src/commands/remove-project.ts +1 -1
- package/src/commands/resume.ts +12 -10
- package/src/commands/screenshare.test.ts +30 -0
- package/src/commands/screenshare.ts +18 -6
- package/src/commands/session.ts +1 -1
- package/src/commands/undo-redo.ts +108 -10
- package/src/commands/user-command.ts +1 -1
- package/src/config.ts +19 -1
- package/src/database.ts +72 -3
- package/src/db.ts +8 -0
- package/src/discord-bot.ts +58 -93
- package/src/discord-command-registration.ts +1 -1
- package/src/external-opencode-sync.ts +729 -0
- package/src/gateway-proxy.e2e.test.ts +9 -5
- package/src/genai.ts +3 -3
- package/src/generated/commonInputTypes.ts +34 -0
- package/src/generated/enums.ts +8 -0
- package/src/generated/internal/class.ts +4 -4
- package/src/generated/internal/prismaNamespace.ts +8 -0
- package/src/generated/internal/prismaNamespaceBrowser.ts +1 -0
- package/src/generated/models/thread_sessions.ts +53 -1
- package/src/hrana-server.test.ts +8 -2
- package/src/hrana-server.ts +18 -390
- package/src/kimaki-opencode-plugin-loading.e2e.test.ts +7 -0
- package/src/kimaki-opencode-plugin.ts +2 -0
- package/src/markdown.test.ts +1 -1
- package/src/markdown.ts +4 -4
- package/src/message-formatting.ts +66 -17
- package/src/onboarding-tutorial.ts +1 -1
- package/src/openai-realtime.ts +6 -10
- package/src/opencode.ts +31 -7
- package/src/queue-advanced-e2e-setup.ts +92 -0
- package/src/queue-advanced-permissions-typing.e2e.test.ts +5 -5
- package/src/queue-advanced-typing.e2e.test.ts +9 -22
- package/src/queue-question-select-drain.e2e.test.ts +149 -0
- package/src/schema.sql +1 -0
- package/src/session-handler/event-stream-state.test.ts +7 -2
- package/src/session-handler/event-stream-state.ts +128 -7
- package/src/session-handler/thread-runtime-state.ts +5 -0
- package/src/session-handler/thread-session-runtime.ts +153 -11
- package/src/store.ts +8 -0
- package/src/system-message.ts +27 -4
- package/src/task-runner.ts +1 -1
- package/src/thread-message-queue.e2e.test.ts +8 -14
- package/src/tools.ts +1 -1
- package/src/undo-redo.e2e.test.ts +28 -26
- package/skills/jitter/node_modules/.bin/esbuild +0 -21
- package/skills/jitter/node_modules/.bin/tsc +0 -21
- package/skills/jitter/node_modules/.bin/tsserver +0 -21
- package/skills/jitter/node_modules/typescript/LICENSE.txt +0 -55
- package/skills/jitter/node_modules/typescript/README.md +0 -50
- package/skills/jitter/node_modules/typescript/SECURITY.md +0 -41
- package/skills/jitter/node_modules/typescript/ThirdPartyNoticeText.txt +0 -193
- package/skills/jitter/node_modules/typescript/bin/tsc +0 -2
- package/skills/jitter/node_modules/typescript/bin/tsserver +0 -2
- package/skills/jitter/node_modules/typescript/lib/_tsc.js +0 -133792
- package/skills/jitter/node_modules/typescript/lib/_tsserver.js +0 -659
- package/skills/jitter/node_modules/typescript/lib/_typingsInstaller.js +0 -222
- package/skills/jitter/node_modules/typescript/lib/cs/diagnosticMessages.generated.json +0 -2122
- package/skills/jitter/node_modules/typescript/lib/de/diagnosticMessages.generated.json +0 -2122
- package/skills/jitter/node_modules/typescript/lib/es/diagnosticMessages.generated.json +0 -2122
- package/skills/jitter/node_modules/typescript/lib/fr/diagnosticMessages.generated.json +0 -2122
- package/skills/jitter/node_modules/typescript/lib/it/diagnosticMessages.generated.json +0 -2122
- package/skills/jitter/node_modules/typescript/lib/ja/diagnosticMessages.generated.json +0 -2122
- package/skills/jitter/node_modules/typescript/lib/ko/diagnosticMessages.generated.json +0 -2122
- package/skills/jitter/node_modules/typescript/lib/lib.d.ts +0 -22
- package/skills/jitter/node_modules/typescript/lib/lib.decorators.d.ts +0 -384
- package/skills/jitter/node_modules/typescript/lib/lib.decorators.legacy.d.ts +0 -22
- package/skills/jitter/node_modules/typescript/lib/lib.dom.asynciterable.d.ts +0 -41
- package/skills/jitter/node_modules/typescript/lib/lib.dom.d.ts +0 -39429
- package/skills/jitter/node_modules/typescript/lib/lib.dom.iterable.d.ts +0 -571
- package/skills/jitter/node_modules/typescript/lib/lib.es2015.collection.d.ts +0 -147
- package/skills/jitter/node_modules/typescript/lib/lib.es2015.core.d.ts +0 -597
- package/skills/jitter/node_modules/typescript/lib/lib.es2015.d.ts +0 -28
- package/skills/jitter/node_modules/typescript/lib/lib.es2015.generator.d.ts +0 -77
- package/skills/jitter/node_modules/typescript/lib/lib.es2015.iterable.d.ts +0 -605
- package/skills/jitter/node_modules/typescript/lib/lib.es2015.promise.d.ts +0 -81
- package/skills/jitter/node_modules/typescript/lib/lib.es2015.proxy.d.ts +0 -128
- package/skills/jitter/node_modules/typescript/lib/lib.es2015.reflect.d.ts +0 -144
- package/skills/jitter/node_modules/typescript/lib/lib.es2015.symbol.d.ts +0 -46
- package/skills/jitter/node_modules/typescript/lib/lib.es2015.symbol.wellknown.d.ts +0 -326
- package/skills/jitter/node_modules/typescript/lib/lib.es2016.array.include.d.ts +0 -116
- package/skills/jitter/node_modules/typescript/lib/lib.es2016.d.ts +0 -21
- package/skills/jitter/node_modules/typescript/lib/lib.es2016.full.d.ts +0 -23
- package/skills/jitter/node_modules/typescript/lib/lib.es2016.intl.d.ts +0 -31
- package/skills/jitter/node_modules/typescript/lib/lib.es2017.arraybuffer.d.ts +0 -21
- package/skills/jitter/node_modules/typescript/lib/lib.es2017.d.ts +0 -26
- package/skills/jitter/node_modules/typescript/lib/lib.es2017.date.d.ts +0 -31
- package/skills/jitter/node_modules/typescript/lib/lib.es2017.full.d.ts +0 -23
- package/skills/jitter/node_modules/typescript/lib/lib.es2017.intl.d.ts +0 -44
- package/skills/jitter/node_modules/typescript/lib/lib.es2017.object.d.ts +0 -49
- package/skills/jitter/node_modules/typescript/lib/lib.es2017.sharedmemory.d.ts +0 -135
- package/skills/jitter/node_modules/typescript/lib/lib.es2017.string.d.ts +0 -45
- package/skills/jitter/node_modules/typescript/lib/lib.es2017.typedarrays.d.ts +0 -53
- package/skills/jitter/node_modules/typescript/lib/lib.es2018.asyncgenerator.d.ts +0 -77
- package/skills/jitter/node_modules/typescript/lib/lib.es2018.asynciterable.d.ts +0 -53
- package/skills/jitter/node_modules/typescript/lib/lib.es2018.d.ts +0 -24
- package/skills/jitter/node_modules/typescript/lib/lib.es2018.full.d.ts +0 -24
- package/skills/jitter/node_modules/typescript/lib/lib.es2018.intl.d.ts +0 -83
- package/skills/jitter/node_modules/typescript/lib/lib.es2018.promise.d.ts +0 -30
- package/skills/jitter/node_modules/typescript/lib/lib.es2018.regexp.d.ts +0 -37
- package/skills/jitter/node_modules/typescript/lib/lib.es2019.array.d.ts +0 -79
- package/skills/jitter/node_modules/typescript/lib/lib.es2019.d.ts +0 -24
- package/skills/jitter/node_modules/typescript/lib/lib.es2019.full.d.ts +0 -24
- package/skills/jitter/node_modules/typescript/lib/lib.es2019.intl.d.ts +0 -23
- package/skills/jitter/node_modules/typescript/lib/lib.es2019.object.d.ts +0 -33
- package/skills/jitter/node_modules/typescript/lib/lib.es2019.string.d.ts +0 -37
- package/skills/jitter/node_modules/typescript/lib/lib.es2019.symbol.d.ts +0 -24
- package/skills/jitter/node_modules/typescript/lib/lib.es2020.bigint.d.ts +0 -765
- package/skills/jitter/node_modules/typescript/lib/lib.es2020.d.ts +0 -27
- package/skills/jitter/node_modules/typescript/lib/lib.es2020.date.d.ts +0 -42
- package/skills/jitter/node_modules/typescript/lib/lib.es2020.full.d.ts +0 -24
- package/skills/jitter/node_modules/typescript/lib/lib.es2020.intl.d.ts +0 -474
- package/skills/jitter/node_modules/typescript/lib/lib.es2020.number.d.ts +0 -28
- package/skills/jitter/node_modules/typescript/lib/lib.es2020.promise.d.ts +0 -47
- package/skills/jitter/node_modules/typescript/lib/lib.es2020.sharedmemory.d.ts +0 -99
- package/skills/jitter/node_modules/typescript/lib/lib.es2020.string.d.ts +0 -44
- package/skills/jitter/node_modules/typescript/lib/lib.es2020.symbol.wellknown.d.ts +0 -41
- package/skills/jitter/node_modules/typescript/lib/lib.es2021.d.ts +0 -23
- package/skills/jitter/node_modules/typescript/lib/lib.es2021.full.d.ts +0 -24
- package/skills/jitter/node_modules/typescript/lib/lib.es2021.intl.d.ts +0 -166
- package/skills/jitter/node_modules/typescript/lib/lib.es2021.promise.d.ts +0 -48
- package/skills/jitter/node_modules/typescript/lib/lib.es2021.string.d.ts +0 -33
- package/skills/jitter/node_modules/typescript/lib/lib.es2021.weakref.d.ts +0 -78
- package/skills/jitter/node_modules/typescript/lib/lib.es2022.array.d.ts +0 -121
- package/skills/jitter/node_modules/typescript/lib/lib.es2022.d.ts +0 -25
- package/skills/jitter/node_modules/typescript/lib/lib.es2022.error.d.ts +0 -75
- package/skills/jitter/node_modules/typescript/lib/lib.es2022.full.d.ts +0 -24
- package/skills/jitter/node_modules/typescript/lib/lib.es2022.intl.d.ts +0 -145
- package/skills/jitter/node_modules/typescript/lib/lib.es2022.object.d.ts +0 -26
- package/skills/jitter/node_modules/typescript/lib/lib.es2022.regexp.d.ts +0 -39
- package/skills/jitter/node_modules/typescript/lib/lib.es2022.string.d.ts +0 -25
- package/skills/jitter/node_modules/typescript/lib/lib.es2023.array.d.ts +0 -924
- package/skills/jitter/node_modules/typescript/lib/lib.es2023.collection.d.ts +0 -21
- package/skills/jitter/node_modules/typescript/lib/lib.es2023.d.ts +0 -22
- package/skills/jitter/node_modules/typescript/lib/lib.es2023.full.d.ts +0 -24
- package/skills/jitter/node_modules/typescript/lib/lib.es2023.intl.d.ts +0 -56
- package/skills/jitter/node_modules/typescript/lib/lib.es2024.arraybuffer.d.ts +0 -65
- package/skills/jitter/node_modules/typescript/lib/lib.es2024.collection.d.ts +0 -29
- package/skills/jitter/node_modules/typescript/lib/lib.es2024.d.ts +0 -26
- package/skills/jitter/node_modules/typescript/lib/lib.es2024.full.d.ts +0 -24
- package/skills/jitter/node_modules/typescript/lib/lib.es2024.object.d.ts +0 -29
- package/skills/jitter/node_modules/typescript/lib/lib.es2024.promise.d.ts +0 -35
- package/skills/jitter/node_modules/typescript/lib/lib.es2024.regexp.d.ts +0 -25
- package/skills/jitter/node_modules/typescript/lib/lib.es2024.sharedmemory.d.ts +0 -68
- package/skills/jitter/node_modules/typescript/lib/lib.es2024.string.d.ts +0 -29
- package/skills/jitter/node_modules/typescript/lib/lib.es5.d.ts +0 -4601
- package/skills/jitter/node_modules/typescript/lib/lib.es6.d.ts +0 -23
- package/skills/jitter/node_modules/typescript/lib/lib.esnext.array.d.ts +0 -35
- package/skills/jitter/node_modules/typescript/lib/lib.esnext.collection.d.ts +0 -96
- package/skills/jitter/node_modules/typescript/lib/lib.esnext.d.ts +0 -29
- package/skills/jitter/node_modules/typescript/lib/lib.esnext.decorators.d.ts +0 -28
- package/skills/jitter/node_modules/typescript/lib/lib.esnext.disposable.d.ts +0 -193
- package/skills/jitter/node_modules/typescript/lib/lib.esnext.error.d.ts +0 -24
- package/skills/jitter/node_modules/typescript/lib/lib.esnext.float16.d.ts +0 -443
- package/skills/jitter/node_modules/typescript/lib/lib.esnext.full.d.ts +0 -24
- package/skills/jitter/node_modules/typescript/lib/lib.esnext.intl.d.ts +0 -21
- package/skills/jitter/node_modules/typescript/lib/lib.esnext.iterator.d.ts +0 -148
- package/skills/jitter/node_modules/typescript/lib/lib.esnext.promise.d.ts +0 -34
- package/skills/jitter/node_modules/typescript/lib/lib.esnext.sharedmemory.d.ts +0 -25
- package/skills/jitter/node_modules/typescript/lib/lib.scripthost.d.ts +0 -322
- package/skills/jitter/node_modules/typescript/lib/lib.webworker.asynciterable.d.ts +0 -41
- package/skills/jitter/node_modules/typescript/lib/lib.webworker.d.ts +0 -13150
- package/skills/jitter/node_modules/typescript/lib/lib.webworker.importscripts.d.ts +0 -23
- package/skills/jitter/node_modules/typescript/lib/lib.webworker.iterable.d.ts +0 -340
- package/skills/jitter/node_modules/typescript/lib/pl/diagnosticMessages.generated.json +0 -2122
- package/skills/jitter/node_modules/typescript/lib/pt-br/diagnosticMessages.generated.json +0 -2122
- package/skills/jitter/node_modules/typescript/lib/ru/diagnosticMessages.generated.json +0 -2122
- package/skills/jitter/node_modules/typescript/lib/tr/diagnosticMessages.generated.json +0 -2122
- package/skills/jitter/node_modules/typescript/lib/tsc.js +0 -8
- package/skills/jitter/node_modules/typescript/lib/tsserver.js +0 -8
- package/skills/jitter/node_modules/typescript/lib/tsserverlibrary.d.ts +0 -17
- package/skills/jitter/node_modules/typescript/lib/tsserverlibrary.js +0 -21
- package/skills/jitter/node_modules/typescript/lib/typesMap.json +0 -497
- package/skills/jitter/node_modules/typescript/lib/typescript.d.ts +0 -11438
- package/skills/jitter/node_modules/typescript/lib/typescript.js +0 -200253
- package/skills/jitter/node_modules/typescript/lib/typingsInstaller.js +0 -8
- package/skills/jitter/node_modules/typescript/lib/watchGuard.js +0 -53
- package/skills/jitter/node_modules/typescript/lib/zh-cn/diagnosticMessages.generated.json +0 -2122
- package/skills/jitter/node_modules/typescript/lib/zh-tw/diagnosticMessages.generated.json +0 -2122
- package/skills/jitter/node_modules/typescript/node_modules/.bin/tsc +0 -21
- package/skills/jitter/node_modules/typescript/node_modules/.bin/tsserver +0 -21
- package/skills/jitter/node_modules/typescript/package.json +0 -120
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { describe, expect, test } from 'vitest';
|
|
2
|
+
import { buildNoVncUrl, createScreenshareTunnelId } from './screenshare.js';
|
|
3
|
+
describe('screenshare security defaults', () => {
|
|
4
|
+
test('generates a 128-bit tunnel id', () => {
|
|
5
|
+
const ids = new Set(Array.from({ length: 32 }, () => {
|
|
6
|
+
return createScreenshareTunnelId();
|
|
7
|
+
}));
|
|
8
|
+
expect(ids.size).toBe(32);
|
|
9
|
+
for (const id of ids) {
|
|
10
|
+
expect(id).toMatch(/^[0-9a-f]{32}$/);
|
|
11
|
+
}
|
|
12
|
+
});
|
|
13
|
+
test('builds a secure noVNC URL', () => {
|
|
14
|
+
const url = new URL(buildNoVncUrl({ tunnelHost: '0123456789abcdef-tunnel.kimaki.xyz' }));
|
|
15
|
+
expect(url.origin).toBe('https://novnc.com');
|
|
16
|
+
expect(url.searchParams.get('host')).toBe('0123456789abcdef-tunnel.kimaki.xyz');
|
|
17
|
+
expect(url.searchParams.get('port')).toBe('443');
|
|
18
|
+
expect(url.searchParams.get('encrypt')).toBe('1');
|
|
19
|
+
});
|
|
20
|
+
});
|
package/dist/commands/session.js
CHANGED
|
@@ -10,7 +10,7 @@ import { createLogger, LogPrefix } from '../logger.js';
|
|
|
10
10
|
import * as errore from 'errore';
|
|
11
11
|
const logger = createLogger(LogPrefix.SESSION);
|
|
12
12
|
export async function handleSessionCommand({ command, appId, }) {
|
|
13
|
-
await command.deferReply(
|
|
13
|
+
await command.deferReply();
|
|
14
14
|
const prompt = command.options.getString('prompt', true);
|
|
15
15
|
const filesString = command.options.getString('files') || '';
|
|
16
16
|
const agent = command.options.getString('agent') || undefined;
|
|
@@ -5,6 +5,19 @@ import { initializeOpencodeForDirectory } from '../opencode.js';
|
|
|
5
5
|
import { resolveWorkingDirectory, SILENT_MESSAGE_FLAGS, } from '../discord-utils.js';
|
|
6
6
|
import { createLogger, LogPrefix } from '../logger.js';
|
|
7
7
|
const logger = createLogger(LogPrefix.UNDO_REDO);
|
|
8
|
+
async function waitForSessionIdle({ client, sessionId, directory, timeoutMs = 2_000, }) {
|
|
9
|
+
const deadline = Date.now() + timeoutMs;
|
|
10
|
+
while (Date.now() < deadline) {
|
|
11
|
+
const statusResponse = await client.session.status({ directory });
|
|
12
|
+
const sessionStatus = statusResponse.data?.[sessionId];
|
|
13
|
+
if (!sessionStatus || sessionStatus.type === 'idle') {
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
await new Promise((resolve) => {
|
|
17
|
+
setTimeout(resolve, 50);
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
}
|
|
8
21
|
export async function handleUndoCommand({ command, }) {
|
|
9
22
|
const channel = command.channel;
|
|
10
23
|
if (!channel) {
|
|
@@ -36,7 +49,7 @@ export async function handleUndoCommand({ command, }) {
|
|
|
36
49
|
});
|
|
37
50
|
return;
|
|
38
51
|
}
|
|
39
|
-
const { projectDirectory } = resolved;
|
|
52
|
+
const { projectDirectory, workingDirectory } = resolved;
|
|
40
53
|
const sessionId = await getThreadSession(channel.id);
|
|
41
54
|
if (!sessionId) {
|
|
42
55
|
await command.reply({
|
|
@@ -56,13 +69,36 @@ export async function handleUndoCommand({ command, }) {
|
|
|
56
69
|
// Fetch session to check existing revert state
|
|
57
70
|
const sessionResponse = await client.session.get({
|
|
58
71
|
sessionID: sessionId,
|
|
72
|
+
directory: workingDirectory,
|
|
59
73
|
});
|
|
60
74
|
if (sessionResponse.error) {
|
|
61
75
|
await command.editReply(`Failed to undo: ${JSON.stringify(sessionResponse.error)}`);
|
|
62
76
|
return;
|
|
63
77
|
}
|
|
78
|
+
// Abort if session is busy before reverting, matching TUI behavior
|
|
79
|
+
// (use-session-commands.tsx always aborts non-idle sessions before revert).
|
|
80
|
+
// session.status() returns a sparse map — only non-idle sessions have entries,
|
|
81
|
+
// so a missing key means idle.
|
|
82
|
+
const statusResponse = await client.session.status({
|
|
83
|
+
directory: workingDirectory,
|
|
84
|
+
});
|
|
85
|
+
const sessionStatus = statusResponse.data?.[sessionId];
|
|
86
|
+
if (sessionStatus && sessionStatus.type !== 'idle') {
|
|
87
|
+
await client.session.abort({
|
|
88
|
+
sessionID: sessionId,
|
|
89
|
+
directory: workingDirectory,
|
|
90
|
+
}).catch((error) => {
|
|
91
|
+
logger.warn(`[UNDO] abort failed for ${sessionId}`, error);
|
|
92
|
+
});
|
|
93
|
+
await waitForSessionIdle({
|
|
94
|
+
client,
|
|
95
|
+
sessionId,
|
|
96
|
+
directory: workingDirectory,
|
|
97
|
+
});
|
|
98
|
+
}
|
|
64
99
|
const messagesResponse = await client.session.messages({
|
|
65
100
|
sessionID: sessionId,
|
|
101
|
+
directory: workingDirectory,
|
|
66
102
|
});
|
|
67
103
|
if (messagesResponse.error) {
|
|
68
104
|
await command.editReply(`Failed to undo: ${JSON.stringify(messagesResponse.error)}`);
|
|
@@ -87,24 +123,46 @@ export async function handleUndoCommand({ command, }) {
|
|
|
87
123
|
await command.editReply('No messages to undo');
|
|
88
124
|
return;
|
|
89
125
|
}
|
|
126
|
+
const targetAssistantMessage = [...messagesResponse.data].reverse().find((m) => {
|
|
127
|
+
return m.info.role === 'assistant' && m.info.parentID === targetUserMessage.info.id;
|
|
128
|
+
});
|
|
129
|
+
const revertMessageId = targetAssistantMessage?.info.id || targetUserMessage.info.id;
|
|
90
130
|
// session.revert() reverts filesystem patches (file edits, writes) and
|
|
91
131
|
// marks the session with revert.messageID. Messages are NOT deleted — they
|
|
92
132
|
// get cleaned up automatically on the next promptAsync() call via
|
|
93
133
|
// SessionRevert.cleanup(). The model only sees messages before the revert
|
|
94
134
|
// point when processing the next prompt.
|
|
95
|
-
|
|
135
|
+
logger.log(`[UNDO] session.revert start messageId=${revertMessageId}`);
|
|
136
|
+
let response = await client.session.revert({
|
|
96
137
|
sessionID: sessionId,
|
|
97
|
-
|
|
138
|
+
directory: workingDirectory,
|
|
139
|
+
messageID: revertMessageId,
|
|
98
140
|
});
|
|
141
|
+
logger.log(`[UNDO] session.revert done error=${Boolean(response.error)}`);
|
|
99
142
|
if (response.error) {
|
|
100
|
-
|
|
101
|
-
|
|
143
|
+
logger.log('[UNDO] retry wait idle before revert retry');
|
|
144
|
+
await waitForSessionIdle({
|
|
145
|
+
client,
|
|
146
|
+
sessionId,
|
|
147
|
+
directory: workingDirectory,
|
|
148
|
+
});
|
|
149
|
+
logger.log('[UNDO] retry revert start');
|
|
150
|
+
response = await client.session.revert({
|
|
151
|
+
sessionID: sessionId,
|
|
152
|
+
directory: workingDirectory,
|
|
153
|
+
messageID: revertMessageId,
|
|
154
|
+
});
|
|
155
|
+
logger.log(`[UNDO] retry revert done error=${Boolean(response.error)}`);
|
|
156
|
+
if (response.error) {
|
|
157
|
+
await command.editReply(`Failed to undo: ${JSON.stringify(response.error)}`);
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
102
160
|
}
|
|
103
161
|
const diffInfo = response.data?.revert?.diff
|
|
104
162
|
? `\n\`\`\`diff\n${response.data.revert.diff.slice(0, 1500)}\n\`\`\``
|
|
105
163
|
: '';
|
|
106
164
|
await command.editReply(`Undone - reverted last assistant message${diffInfo}`);
|
|
107
|
-
logger.log(`Session ${sessionId} reverted
|
|
165
|
+
logger.log(`Session ${sessionId} reverted at message ${revertMessageId}`);
|
|
108
166
|
}
|
|
109
167
|
catch (error) {
|
|
110
168
|
logger.error('[UNDO] Error:', error);
|
|
@@ -142,7 +200,7 @@ export async function handleRedoCommand({ command, }) {
|
|
|
142
200
|
});
|
|
143
201
|
return;
|
|
144
202
|
}
|
|
145
|
-
const { projectDirectory } = resolved;
|
|
203
|
+
const { projectDirectory, workingDirectory } = resolved;
|
|
146
204
|
const sessionId = await getThreadSession(channel.id);
|
|
147
205
|
if (!sessionId) {
|
|
148
206
|
await command.reply({
|
|
@@ -162,6 +220,7 @@ export async function handleRedoCommand({ command, }) {
|
|
|
162
220
|
// Fetch session to check existing revert state
|
|
163
221
|
const sessionResponse = await client.session.get({
|
|
164
222
|
sessionID: sessionId,
|
|
223
|
+
directory: workingDirectory,
|
|
165
224
|
});
|
|
166
225
|
if (sessionResponse.error) {
|
|
167
226
|
await command.editReply(`Failed to redo: ${JSON.stringify(sessionResponse.error)}`);
|
|
@@ -172,12 +231,35 @@ export async function handleRedoCommand({ command, }) {
|
|
|
172
231
|
await command.editReply('Nothing to redo - no previous undo found');
|
|
173
232
|
return;
|
|
174
233
|
}
|
|
234
|
+
// Abort if session is busy before reverting/unreverting — both enforce
|
|
235
|
+
// assertNotBusy in OpenCode and would fail with "Session is busy"
|
|
236
|
+
const redoStatusResponse = await client.session.status({
|
|
237
|
+
directory: workingDirectory,
|
|
238
|
+
});
|
|
239
|
+
const redoSessionStatus = redoStatusResponse.data?.[sessionId];
|
|
240
|
+
if (redoSessionStatus && redoSessionStatus.type !== 'idle') {
|
|
241
|
+
await client.session.abort({
|
|
242
|
+
sessionID: sessionId,
|
|
243
|
+
directory: workingDirectory,
|
|
244
|
+
}).catch((error) => {
|
|
245
|
+
logger.warn(`[REDO] abort failed for ${sessionId}`, error);
|
|
246
|
+
});
|
|
247
|
+
await waitForSessionIdle({
|
|
248
|
+
client,
|
|
249
|
+
sessionId,
|
|
250
|
+
directory: workingDirectory,
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
await new Promise((resolve) => {
|
|
254
|
+
setTimeout(resolve, 500);
|
|
255
|
+
});
|
|
175
256
|
// Follow the same approach as the OpenCode TUI (use-session-commands.tsx):
|
|
176
257
|
// find the next user message after the current revert point. If one exists,
|
|
177
258
|
// move the revert cursor forward to it (one step redo). If none exists,
|
|
178
259
|
// fully unrevert — we're at the end of the message history.
|
|
179
260
|
const messagesResponse = await client.session.messages({
|
|
180
261
|
sessionID: sessionId,
|
|
262
|
+
directory: workingDirectory,
|
|
181
263
|
});
|
|
182
264
|
if (messagesResponse.error) {
|
|
183
265
|
await command.editReply(`Failed to redo: ${JSON.stringify(messagesResponse.error)}`);
|
|
@@ -193,6 +275,7 @@ export async function handleRedoCommand({ command, }) {
|
|
|
193
275
|
// No more messages after revert point — fully unrevert
|
|
194
276
|
const response = await client.session.unrevert({
|
|
195
277
|
sessionID: sessionId,
|
|
278
|
+
directory: workingDirectory,
|
|
196
279
|
});
|
|
197
280
|
if (response.error) {
|
|
198
281
|
await command.editReply(`Failed to redo: ${JSON.stringify(response.error)}`);
|
|
@@ -205,6 +288,7 @@ export async function handleRedoCommand({ command, }) {
|
|
|
205
288
|
// Move revert cursor forward one step to the next user message
|
|
206
289
|
const response = await client.session.revert({
|
|
207
290
|
sessionID: sessionId,
|
|
291
|
+
directory: workingDirectory,
|
|
208
292
|
messageID: nextMessage.info.id,
|
|
209
293
|
});
|
|
210
294
|
if (response.error) {
|
|
@@ -75,7 +75,7 @@ export const handleUserCommand = async ({ command, appId, }) => {
|
|
|
75
75
|
});
|
|
76
76
|
return;
|
|
77
77
|
}
|
|
78
|
-
await command.deferReply(
|
|
78
|
+
await command.deferReply();
|
|
79
79
|
try {
|
|
80
80
|
// Use the dedicated session.command API instead of formatting as text prompt
|
|
81
81
|
const commandPayload = { name: commandName, arguments: args };
|
package/dist/config.js
CHANGED
|
@@ -42,11 +42,26 @@ export function setDataDir(dir) {
|
|
|
42
42
|
}
|
|
43
43
|
/**
|
|
44
44
|
* Get the projects directory path (for /create-new-project command).
|
|
45
|
-
* Returns <dataDir>/projects
|
|
45
|
+
* Returns the custom --projects-dir if set, otherwise <dataDir>/projects.
|
|
46
46
|
*/
|
|
47
47
|
export function getProjectsDir() {
|
|
48
|
+
const custom = store.getState().projectsDir;
|
|
49
|
+
if (custom) {
|
|
50
|
+
return custom;
|
|
51
|
+
}
|
|
48
52
|
return path.join(getDataDir(), 'projects');
|
|
49
53
|
}
|
|
54
|
+
/**
|
|
55
|
+
* Set a custom projects directory path (from --projects-dir CLI flag).
|
|
56
|
+
* Creates the directory if it doesn't exist.
|
|
57
|
+
*/
|
|
58
|
+
export function setProjectsDir(dir) {
|
|
59
|
+
const resolvedDir = path.resolve(dir);
|
|
60
|
+
if (!fs.existsSync(resolvedDir)) {
|
|
61
|
+
fs.mkdirSync(resolvedDir, { recursive: true });
|
|
62
|
+
}
|
|
63
|
+
store.setState({ projectsDir: resolvedDir });
|
|
64
|
+
}
|
|
50
65
|
const DEFAULT_LOCK_PORT = 29988;
|
|
51
66
|
/**
|
|
52
67
|
* Derive a lock port from the data directory path.
|
package/dist/database.js
CHANGED
|
@@ -662,12 +662,34 @@ export async function getThreadSession(threadId) {
|
|
|
662
662
|
* Set the session ID for a thread.
|
|
663
663
|
*/
|
|
664
664
|
export async function setThreadSession(threadId, sessionId) {
|
|
665
|
+
await upsertThreadSession({
|
|
666
|
+
threadId,
|
|
667
|
+
sessionId,
|
|
668
|
+
source: 'kimaki',
|
|
669
|
+
});
|
|
670
|
+
}
|
|
671
|
+
export async function upsertThreadSession({ threadId, sessionId, source, }) {
|
|
665
672
|
const prisma = await getPrisma();
|
|
666
673
|
await prisma.thread_sessions.upsert({
|
|
667
674
|
where: { thread_id: threadId },
|
|
668
|
-
create: {
|
|
669
|
-
|
|
675
|
+
create: {
|
|
676
|
+
thread_id: threadId,
|
|
677
|
+
session_id: sessionId,
|
|
678
|
+
source,
|
|
679
|
+
},
|
|
680
|
+
update: {
|
|
681
|
+
session_id: sessionId,
|
|
682
|
+
source,
|
|
683
|
+
},
|
|
684
|
+
});
|
|
685
|
+
}
|
|
686
|
+
export async function getThreadSessionSource(threadId) {
|
|
687
|
+
const prisma = await getPrisma();
|
|
688
|
+
const row = await prisma.thread_sessions.findUnique({
|
|
689
|
+
where: { thread_id: threadId },
|
|
690
|
+
select: { source: true },
|
|
670
691
|
});
|
|
692
|
+
return row?.source;
|
|
671
693
|
}
|
|
672
694
|
/**
|
|
673
695
|
* Get the thread ID for a session.
|
|
@@ -1094,6 +1116,14 @@ export async function getAllTextChannelDirectories() {
|
|
|
1094
1116
|
});
|
|
1095
1117
|
return rows.map((row) => row.directory);
|
|
1096
1118
|
}
|
|
1119
|
+
export async function listTrackedTextChannels() {
|
|
1120
|
+
const prisma = await getPrisma();
|
|
1121
|
+
return prisma.channel_directories.findMany({
|
|
1122
|
+
where: { channel_type: 'text' },
|
|
1123
|
+
orderBy: [{ created_at: 'asc' }, { channel_id: 'asc' }],
|
|
1124
|
+
select: { channel_id: true, directory: true, created_at: true },
|
|
1125
|
+
});
|
|
1126
|
+
}
|
|
1097
1127
|
/**
|
|
1098
1128
|
* Delete all channel directories for a specific directory.
|
|
1099
1129
|
*/
|
|
@@ -1103,6 +1133,27 @@ export async function deleteChannelDirectoriesByDirectory(directory) {
|
|
|
1103
1133
|
where: { directory },
|
|
1104
1134
|
});
|
|
1105
1135
|
}
|
|
1136
|
+
/**
|
|
1137
|
+
* Delete a single channel_directories row and all its child rows
|
|
1138
|
+
* (channel_models, channel_agents, channel_worktrees, channel_verbosity,
|
|
1139
|
+
* channel_mention_mode) in a single transaction. scheduled_tasks has
|
|
1140
|
+
* onDelete:SetNull so Prisma handles it automatically.
|
|
1141
|
+
*/
|
|
1142
|
+
export async function deleteChannelDirectoryById(channelId) {
|
|
1143
|
+
const prisma = await getPrisma();
|
|
1144
|
+
const deletedCount = await prisma.$transaction(async (tx) => {
|
|
1145
|
+
await tx.channel_models.deleteMany({ where: { channel_id: channelId } });
|
|
1146
|
+
await tx.channel_agents.deleteMany({ where: { channel_id: channelId } });
|
|
1147
|
+
await tx.channel_worktrees.deleteMany({ where: { channel_id: channelId } });
|
|
1148
|
+
await tx.channel_verbosity.deleteMany({ where: { channel_id: channelId } });
|
|
1149
|
+
await tx.channel_mention_mode.deleteMany({ where: { channel_id: channelId } });
|
|
1150
|
+
const result = await tx.channel_directories.deleteMany({
|
|
1151
|
+
where: { channel_id: channelId },
|
|
1152
|
+
});
|
|
1153
|
+
return result.count;
|
|
1154
|
+
});
|
|
1155
|
+
return deletedCount > 0;
|
|
1156
|
+
}
|
|
1106
1157
|
/**
|
|
1107
1158
|
* Get the directory for a voice channel.
|
|
1108
1159
|
*/
|
package/dist/db.js
CHANGED
|
@@ -159,6 +159,12 @@ async function migrateSchema(prisma) {
|
|
|
159
159
|
// Column already exists
|
|
160
160
|
}
|
|
161
161
|
}
|
|
162
|
+
try {
|
|
163
|
+
await prisma.$executeRawUnsafe("ALTER TABLE thread_sessions ADD COLUMN source TEXT DEFAULT 'kimaki'");
|
|
164
|
+
}
|
|
165
|
+
catch {
|
|
166
|
+
// Column already exists
|
|
167
|
+
}
|
|
162
168
|
// Migration: move session_thinking data into session_models.variant.
|
|
163
169
|
// session_thinking table is left in place (not dropped) so older kimaki versions
|
|
164
170
|
// that still reference it won't crash on the same database.
|
package/dist/discord-bot.js
CHANGED
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
// Core Discord bot module that handles message events and bot lifecycle.
|
|
2
2
|
// Bridges Discord messages to OpenCode sessions, manages voice connections,
|
|
3
3
|
// and orchestrates the main event loop for the Kimaki bot.
|
|
4
|
-
import { initDatabase, closeDatabase, getThreadWorktree, getThreadSession,
|
|
4
|
+
import { initDatabase, closeDatabase, getThreadWorktree, getThreadSession, getChannelWorktreesEnabled, getChannelMentionMode, getChannelDirectory, getPrisma, cancelAllPendingIpcRequests, deleteChannelDirectoryById, } from './database.js';
|
|
5
5
|
import { stopOpencodeServer, } from './opencode.js';
|
|
6
|
-
import { formatWorktreeName } from './commands/new-worktree.js';
|
|
6
|
+
import { formatWorktreeName, createWorktreeInBackground, worktreeCreatingMessage } from './commands/new-worktree.js';
|
|
7
7
|
import { WORKTREE_PREFIX } from './commands/merge-worktree.js';
|
|
8
|
-
import { createWorktreeWithSubmodules } from './worktrees.js';
|
|
9
8
|
import { escapeBackticksInCodeBlocks, splitMarkdownForDiscord, sendThreadMessage, SILENT_MESSAGE_FLAGS, NOTIFY_MESSAGE_FLAGS, reactToThread, stripMentions, hasKimakiBotPermission, hasNoKimakiRole, } from './discord-utils.js';
|
|
10
|
-
import { getOpencodeSystemMessage, } from './system-message.js';
|
|
9
|
+
import { getOpencodeSystemMessage, isInjectedPromptMarker, } from './system-message.js';
|
|
11
10
|
import yaml from 'js-yaml';
|
|
12
11
|
import { getTextAttachments, resolveMentions, } from './message-formatting.js';
|
|
13
12
|
import { isVoiceAttachment } from './voice-attachment.js';
|
|
@@ -28,6 +27,7 @@ import { markDiscordGatewayReady, stopHranaServer } from './hrana-server.js';
|
|
|
28
27
|
import { notifyError } from './sentry.js';
|
|
29
28
|
import { flushDebouncedProcessCallbacks } from './debounced-process-flush.js';
|
|
30
29
|
import { startRuntimeIdleSweeper } from './runtime-idle-sweeper.js';
|
|
30
|
+
import { startExternalOpencodeSessionSync, stopExternalOpencodeSessionSync, } from './external-opencode-sync.js';
|
|
31
31
|
export { initDatabase, closeDatabase, getChannelDirectory, getPrisma, } from './database.js';
|
|
32
32
|
export { initializeOpencodeForDirectory } from './opencode.js';
|
|
33
33
|
export { escapeBackticksInCodeBlocks, splitMarkdownForDiscord, } from './discord-utils.js';
|
|
@@ -162,6 +162,7 @@ export async function startDiscordBot({ token, appId, discordClient, useWorktree
|
|
|
162
162
|
markDiscordGatewayReady();
|
|
163
163
|
registerInteractionHandler({ discordClient: c, appId: currentAppId });
|
|
164
164
|
registerVoiceStateHandler({ discordClient: c, appId: currentAppId });
|
|
165
|
+
startExternalOpencodeSessionSync({ discordClient: c });
|
|
165
166
|
// Channel logging is informational only; do it in background so startup stays responsive.
|
|
166
167
|
void (async () => {
|
|
167
168
|
for (const guild of c.guilds.cache.values()) {
|
|
@@ -244,7 +245,7 @@ export async function startDiscordBot({ token, appId, discordClient, useWorktree
|
|
|
244
245
|
const promptMarker = parseEmbedFooterMarker({
|
|
245
246
|
footer: message.embeds[0]?.footer?.text,
|
|
246
247
|
});
|
|
247
|
-
const isCliInjectedPrompt = Boolean(isSelfBotMessage && promptMarker
|
|
248
|
+
const isCliInjectedPrompt = Boolean(isSelfBotMessage && isInjectedPromptMarker({ marker: promptMarker }));
|
|
248
249
|
const sessionStartSource = isCliInjectedPrompt
|
|
249
250
|
? parseSessionStartSourceFromMarker(promptMarker)
|
|
250
251
|
: undefined;
|
|
@@ -419,7 +420,6 @@ export async function startDiscordBot({ token, appId, discordClient, useWorktree
|
|
|
419
420
|
discordLogger.log(`Cannot process message: no project directory for thread ${thread.id}`);
|
|
420
421
|
return;
|
|
421
422
|
}
|
|
422
|
-
// Capture narrowed non-undefined value for use in the preprocess closure
|
|
423
423
|
const resolvedProjectDir = projectDirectory;
|
|
424
424
|
const sdkDir = worktreeInfo?.status === 'ready' &&
|
|
425
425
|
worktreeInfo.worktree_directory
|
|
@@ -442,7 +442,9 @@ export async function startDiscordBot({ token, appId, discordClient, useWorktree
|
|
|
442
442
|
cancelHtmlActionsForThread(thread.id);
|
|
443
443
|
const dismissedPermission = await cancelPendingPermission(thread.id);
|
|
444
444
|
if (dismissedPermission) {
|
|
445
|
-
runtime.
|
|
445
|
+
await runtime.abortActiveRunAndWait({
|
|
446
|
+
reason: 'user sent a new message while permission was pending',
|
|
447
|
+
});
|
|
446
448
|
}
|
|
447
449
|
const questionResult = await cancelPendingQuestion(thread.id, message.content);
|
|
448
450
|
void cancelPendingFileUpload(thread.id);
|
|
@@ -460,6 +462,8 @@ export async function startDiscordBot({ token, appId, discordClient, useWorktree
|
|
|
460
462
|
username: cliInjectedUsername ||
|
|
461
463
|
message.member?.displayName ||
|
|
462
464
|
message.author.displayName,
|
|
465
|
+
sourceMessageId: message.id,
|
|
466
|
+
sourceThreadId: thread.id,
|
|
463
467
|
appId: currentAppId,
|
|
464
468
|
agent: cliInjectedAgent,
|
|
465
469
|
model: cliInjectedModel,
|
|
@@ -560,42 +564,21 @@ export async function startDiscordBot({ token, appId, discordClient, useWorktree
|
|
|
560
564
|
if (shouldUseWorktrees) {
|
|
561
565
|
const worktreeName = formatWorktreeName(hasVoice ? `voice-${Date.now()}` : threadName.slice(0, 50));
|
|
562
566
|
discordLogger.log(`[WORKTREE] Creating worktree: ${worktreeName}`);
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
567
|
+
const worktreeStatusMessage = await thread
|
|
568
|
+
.send({
|
|
569
|
+
content: worktreeCreatingMessage(worktreeName),
|
|
570
|
+
flags: SILENT_MESSAGE_FLAGS,
|
|
571
|
+
})
|
|
572
|
+
.catch(() => undefined);
|
|
573
|
+
const result = await createWorktreeInBackground({
|
|
574
|
+
thread,
|
|
575
|
+
starterMessage: worktreeStatusMessage,
|
|
566
576
|
worktreeName,
|
|
567
577
|
projectDirectory,
|
|
578
|
+
rest: discordClient.rest,
|
|
568
579
|
});
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
name: worktreeName,
|
|
572
|
-
});
|
|
573
|
-
if (worktreeResult instanceof Error) {
|
|
574
|
-
const errMsg = worktreeResult.message;
|
|
575
|
-
discordLogger.error(`[WORKTREE] Creation failed: ${errMsg}`);
|
|
576
|
-
await setWorktreeError({
|
|
577
|
-
threadId: thread.id,
|
|
578
|
-
errorMessage: errMsg,
|
|
579
|
-
});
|
|
580
|
-
await thread.send({
|
|
581
|
-
content: `⚠️ Failed to create worktree: ${errMsg}\nUsing main project directory instead.`,
|
|
582
|
-
flags: NOTIFY_MESSAGE_FLAGS,
|
|
583
|
-
});
|
|
584
|
-
}
|
|
585
|
-
else {
|
|
586
|
-
await setWorktreeReady({
|
|
587
|
-
threadId: thread.id,
|
|
588
|
-
worktreeDirectory: worktreeResult.directory,
|
|
589
|
-
});
|
|
590
|
-
sessionDirectory = worktreeResult.directory;
|
|
591
|
-
discordLogger.log(`[WORKTREE] Created: ${worktreeResult.directory} (branch: ${worktreeResult.branch})`);
|
|
592
|
-
// React with tree emoji to mark as worktree thread
|
|
593
|
-
await reactToThread({
|
|
594
|
-
rest: discordClient.rest,
|
|
595
|
-
threadId: thread.id,
|
|
596
|
-
channelId: thread.parentId || undefined,
|
|
597
|
-
emoji: '🌳',
|
|
598
|
-
});
|
|
580
|
+
if (!(result instanceof Error)) {
|
|
581
|
+
sessionDirectory = result;
|
|
599
582
|
}
|
|
600
583
|
}
|
|
601
584
|
const channelRuntime = getOrCreateRuntime({
|
|
@@ -610,6 +593,8 @@ export async function startDiscordBot({ token, appId, discordClient, useWorktree
|
|
|
610
593
|
prompt: '',
|
|
611
594
|
userId: message.author.id,
|
|
612
595
|
username: message.member?.displayName || message.author.displayName,
|
|
596
|
+
sourceMessageId: message.id,
|
|
597
|
+
sourceThreadId: thread.id,
|
|
613
598
|
appId: currentAppId,
|
|
614
599
|
preprocess: () => {
|
|
615
600
|
return preprocessNewThreadMessage({
|
|
@@ -711,58 +696,21 @@ export async function startDiscordBot({ token, appId, discordClient, useWorktree
|
|
|
711
696
|
discordLogger.log(`[BOT_SESSION] Creating worktree: ${marker.worktree}`);
|
|
712
697
|
const worktreeStatusMessage = await thread
|
|
713
698
|
.send({
|
|
714
|
-
content:
|
|
699
|
+
content: worktreeCreatingMessage(marker.worktree),
|
|
715
700
|
flags: SILENT_MESSAGE_FLAGS,
|
|
716
701
|
})
|
|
717
|
-
.catch(() =>
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
threadId: thread.id,
|
|
702
|
+
.catch(() => undefined);
|
|
703
|
+
const result = await createWorktreeInBackground({
|
|
704
|
+
thread,
|
|
705
|
+
starterMessage: worktreeStatusMessage,
|
|
722
706
|
worktreeName: marker.worktree,
|
|
723
707
|
projectDirectory,
|
|
708
|
+
rest: discordClient.rest,
|
|
724
709
|
});
|
|
725
|
-
|
|
726
|
-
directory: projectDirectory,
|
|
727
|
-
name: marker.worktree,
|
|
728
|
-
});
|
|
729
|
-
if (errore.isError(worktreeResult)) {
|
|
730
|
-
discordLogger.error(`[BOT_SESSION] Worktree creation failed: ${worktreeResult.message}`);
|
|
731
|
-
await setWorktreeError({
|
|
732
|
-
threadId: thread.id,
|
|
733
|
-
errorMessage: worktreeResult.message,
|
|
734
|
-
});
|
|
735
|
-
await (worktreeStatusMessage?.edit({
|
|
736
|
-
content: `⚠️ Failed to create worktree: ${worktreeResult.message}\nUsing main project directory instead.`,
|
|
737
|
-
flags: NOTIFY_MESSAGE_FLAGS,
|
|
738
|
-
}) ||
|
|
739
|
-
thread.send({
|
|
740
|
-
content: `⚠️ Failed to create worktree: ${worktreeResult.message}\nUsing main project directory instead.`,
|
|
741
|
-
flags: NOTIFY_MESSAGE_FLAGS,
|
|
742
|
-
}));
|
|
710
|
+
if (result instanceof Error) {
|
|
743
711
|
return projectDirectory;
|
|
744
712
|
}
|
|
745
|
-
|
|
746
|
-
threadId: thread.id,
|
|
747
|
-
worktreeDirectory: worktreeResult.directory,
|
|
748
|
-
});
|
|
749
|
-
discordLogger.log(`[BOT_SESSION] Worktree created: ${worktreeResult.directory}`);
|
|
750
|
-
// React with tree emoji to mark as worktree thread
|
|
751
|
-
await reactToThread({
|
|
752
|
-
rest: discordClient.rest,
|
|
753
|
-
threadId: thread.id,
|
|
754
|
-
channelId: thread.parentId || undefined,
|
|
755
|
-
emoji: '🌳',
|
|
756
|
-
});
|
|
757
|
-
await (worktreeStatusMessage?.edit({
|
|
758
|
-
content: `🌳 **Worktree ready: ${marker.worktree}**\n📁 \`${worktreeResult.directory}\`\n🌿 Branch: \`${worktreeResult.branch}\``,
|
|
759
|
-
flags: SILENT_MESSAGE_FLAGS,
|
|
760
|
-
}) ||
|
|
761
|
-
thread.send({
|
|
762
|
-
content: `🌳 **Worktree ready: ${marker.worktree}**\n📁 \`${worktreeResult.directory}\`\n🌿 Branch: \`${worktreeResult.branch}\``,
|
|
763
|
-
flags: SILENT_MESSAGE_FLAGS,
|
|
764
|
-
}));
|
|
765
|
-
return worktreeResult.directory;
|
|
713
|
+
return result;
|
|
766
714
|
})();
|
|
767
715
|
discordLogger.log(`[BOT_SESSION] Starting session for thread ${thread.id} with prompt: "${prompt.slice(0, 50)}..."`);
|
|
768
716
|
const botThreadStartSource = parseSessionStartSourceFromMarker(marker);
|
|
@@ -811,6 +759,20 @@ export async function startDiscordBot({ token, appId, discordClient, useWorktree
|
|
|
811
759
|
discordClient.on(Events.ThreadDelete, (thread) => {
|
|
812
760
|
disposeRuntime(thread.id);
|
|
813
761
|
});
|
|
762
|
+
// Clean up SQLite when a Discord channel is deleted so project list
|
|
763
|
+
// doesn't show stale ghost entries. Thread runtimes inside the deleted
|
|
764
|
+
// channel are disposed by their own ThreadDelete events from Discord.
|
|
765
|
+
discordClient.on(Events.ChannelDelete, async (channel) => {
|
|
766
|
+
try {
|
|
767
|
+
const deleted = await deleteChannelDirectoryById(channel.id);
|
|
768
|
+
if (deleted) {
|
|
769
|
+
discordLogger.log(`Cleaned up channel_directories for deleted channel ${channel.id}`);
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
catch (error) {
|
|
773
|
+
notifyError(error instanceof Error ? error : new Error(String(error)), `Failed to clean up channel_directories for deleted channel ${channel.id}`);
|
|
774
|
+
}
|
|
775
|
+
});
|
|
814
776
|
// Skip login if the caller already connected the client (e.g. cli.ts logs in
|
|
815
777
|
// before calling startDiscordBot). Calling login() again destroys the existing
|
|
816
778
|
// WebSocket (close code 1000) and triggers a spurious ShardReconnecting event.
|
|
@@ -849,6 +811,7 @@ export async function startDiscordBot({ token, appId, discordClient, useWorktree
|
|
|
849
811
|
discordLogger.log(`All voice connections cleaned up`);
|
|
850
812
|
}
|
|
851
813
|
voiceLogger.log('[SHUTDOWN] Stopping OpenCode server');
|
|
814
|
+
stopExternalOpencodeSessionSync();
|
|
852
815
|
await stopOpencodeServer();
|
|
853
816
|
discordLogger.log('Closing database...');
|
|
854
817
|
await closeDatabase();
|
|
@@ -360,7 +360,7 @@ export async function registerCommands({ token, appId, guildIds, userCommands =
|
|
|
360
360
|
.toJSON(),
|
|
361
361
|
new SlashCommandBuilder()
|
|
362
362
|
.setName('screenshare')
|
|
363
|
-
.setDescription(truncateCommandDescription('Start screen sharing via VNC tunnel (auto-stops after
|
|
363
|
+
.setDescription(truncateCommandDescription('Start screen sharing via VNC tunnel (auto-stops after 30 minutes)'))
|
|
364
364
|
.setDMPermission(false)
|
|
365
365
|
.toJSON(),
|
|
366
366
|
new SlashCommandBuilder()
|