kimaki 0.9.1 → 0.10.0
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/agent-model.e2e.test.js +13 -23
- package/dist/channel-reference-permissions.e2e.test.js +85 -0
- package/dist/cli-commands/bot.js +1 -1
- package/dist/cli-commands/maintenance.js +1 -1
- package/dist/cli-commands/misc.js +1 -1
- package/dist/cli-commands/project.js +5 -5
- package/dist/cli-commands/send.js +11 -8
- package/dist/cli-commands/session.js +55 -7
- package/dist/cli-commands/task.js +1 -1
- package/dist/cli-commands/user.js +1 -1
- package/dist/cli-runner.js +11 -10
- package/dist/cli-send-thread.e2e.test.js +2 -2
- package/dist/commands/last-sessions.js +9 -8
- package/dist/commands/unset-model.js +5 -5
- package/dist/commands/verbosity.js +3 -3
- package/dist/commands/worktrees.js +3 -3
- package/dist/database.js +434 -1110
- package/dist/db.js +66 -151
- package/dist/db.test.js +78 -26
- package/dist/discord-bot.js +16 -13
- package/dist/hrana-server.js +1 -1
- package/dist/hrana-server.test.js +37 -308
- package/dist/ipc-polling.js +2 -2
- package/dist/ipc-tools-plugin.js +9 -17
- package/dist/message-preprocessing.js +25 -2
- package/dist/opencode.js +28 -5
- package/dist/queue-advanced-e2e-setup.js +64 -1
- package/dist/schema.js +270 -0
- package/dist/session-handler/event-stream-state.js +18 -0
- package/dist/session-handler/event-stream-state.test.js +46 -1
- package/dist/session-handler/thread-session-runtime.js +82 -17
- package/dist/startup-time.e2e.test.js +1 -1
- package/dist/system-message.js +24 -11
- package/dist/system-message.test.js +24 -11
- package/dist/test-utils.js +25 -0
- package/dist/wait-session.js +103 -26
- package/dist/worktrees.js +34 -0
- package/dist/worktrees.test.js +119 -1
- package/package.json +11 -14
- package/skills/egaki/SKILL.md +10 -1
- package/skills/errore/SKILL.md +20 -8
- package/skills/goke/SKILL.md +302 -1
- package/skills/sigillo/SKILL.md +214 -36
- package/skills/spiceflow/SKILL.md +100 -0
- package/skills/tuistory/SKILL.md +1 -1
- package/src/agent-model.e2e.test.ts +13 -23
- package/src/channel-reference-permissions.e2e.test.ts +102 -0
- package/src/cli-commands/bot.ts +1 -1
- package/src/cli-commands/maintenance.ts +1 -1
- package/src/cli-commands/misc.ts +1 -1
- package/src/cli-commands/project.ts +5 -5
- package/src/cli-commands/send.ts +12 -8
- package/src/cli-commands/session.ts +79 -7
- package/src/cli-commands/task.ts +1 -1
- package/src/cli-commands/user.ts +1 -1
- package/src/cli-runner.ts +11 -10
- package/src/cli-send-thread.e2e.test.ts +2 -2
- package/src/commands/fork.ts +1 -1
- package/src/commands/last-sessions.ts +9 -8
- package/src/commands/unset-model.ts +7 -5
- package/src/commands/verbosity.ts +3 -3
- package/src/commands/worktrees.ts +3 -3
- package/src/database.ts +528 -1618
- package/src/db.test.ts +80 -26
- package/src/db.ts +78 -158
- package/src/discord-bot.ts +14 -13
- package/src/hrana-server.test.ts +39 -343
- package/src/hrana-server.ts +1 -1
- package/src/ipc-polling.ts +2 -2
- package/src/ipc-tools-plugin.ts +9 -17
- package/src/message-preprocessing.ts +37 -3
- package/src/opencode.ts +31 -5
- package/src/queue-advanced-e2e-setup.ts +72 -0
- package/src/schema.sql +191 -173
- package/src/schema.ts +303 -0
- package/src/session-handler/event-stream-state.test.ts +49 -0
- package/src/session-handler/event-stream-state.ts +29 -0
- package/src/session-handler/thread-runtime-state.ts +4 -0
- package/src/session-handler/thread-session-runtime.ts +109 -19
- package/src/startup-time.e2e.test.ts +1 -1
- package/src/store.ts +1 -1
- package/src/system-message.test.ts +24 -11
- package/src/system-message.ts +25 -12
- package/src/test-utils.ts +27 -0
- package/src/wait-session.ts +154 -33
- package/src/worktrees.test.ts +136 -0
- package/src/worktrees.ts +64 -0
- package/dist/acp-client.test.js +0 -149
- package/dist/adapter-rest-boundary.test.js +0 -34
- package/dist/add-directory.e2e.test.js +0 -101
- package/dist/bash-tool.js +0 -194
- package/dist/bash-tool.test.js +0 -82
- package/dist/bundled-skills.js +0 -37
- package/dist/cli-commands/core.js +0 -1909
- package/dist/commands/add-directory.js +0 -67
- package/dist/commands/channel-ref.js +0 -16
- package/dist/commands/discord-install-url.js +0 -36
- package/dist/commands/sqlitedb.js +0 -16
- package/dist/commands/stop-opencode-server.js +0 -80
- package/dist/commands/thinking.js +0 -128
- package/dist/commands/vscode.test.js +0 -44
- package/dist/commands/worktree.js +0 -279
- package/dist/config-lock-port.test.js +0 -17
- package/dist/diff-patch-plugin.js +0 -314
- package/dist/directVoiceStreaming.js +0 -102
- package/dist/directory-permissions.js +0 -38
- package/dist/directory-permissions.test.js +0 -37
- package/dist/discord-js-import-boundary.test.js +0 -62
- package/dist/discord-ws-proxy.js +0 -350
- package/dist/discord-ws-proxy.test.js +0 -500
- package/dist/discordBot.js +0 -2814
- package/dist/external-opencode-sync.test.js +0 -151
- package/dist/fork.js +0 -163
- package/dist/forum-sync.js +0 -953
- package/dist/gateway-session.js +0 -163
- package/dist/generated/browser.js +0 -17
- package/dist/generated/client.js +0 -37
- package/dist/generated/cloudflare/browser.js +0 -17
- package/dist/generated/cloudflare/client.js +0 -34
- package/dist/generated/cloudflare/commonInputTypes.js +0 -10
- package/dist/generated/cloudflare/enums.js +0 -48
- package/dist/generated/cloudflare/internal/class.js +0 -47
- package/dist/generated/cloudflare/internal/prismaNamespace.js +0 -252
- package/dist/generated/cloudflare/internal/prismaNamespaceBrowser.js +0 -222
- package/dist/generated/cloudflare/internal/query_compiler_fast_bg.js +0 -135
- package/dist/generated/cloudflare/models/bot_api_keys.js +0 -1
- package/dist/generated/cloudflare/models/bot_tokens.js +0 -1
- package/dist/generated/cloudflare/models/channel_agents.js +0 -1
- package/dist/generated/cloudflare/models/channel_directories.js +0 -1
- package/dist/generated/cloudflare/models/channel_mention_mode.js +0 -1
- package/dist/generated/cloudflare/models/channel_models.js +0 -1
- package/dist/generated/cloudflare/models/channel_verbosity.js +0 -1
- package/dist/generated/cloudflare/models/channel_worktrees.js +0 -1
- package/dist/generated/cloudflare/models/forum_sync_configs.js +0 -1
- package/dist/generated/cloudflare/models/global_models.js +0 -1
- package/dist/generated/cloudflare/models/ipc_requests.js +0 -1
- package/dist/generated/cloudflare/models/part_messages.js +0 -1
- package/dist/generated/cloudflare/models/scheduled_tasks.js +0 -1
- package/dist/generated/cloudflare/models/session_agents.js +0 -1
- package/dist/generated/cloudflare/models/session_events.js +0 -1
- package/dist/generated/cloudflare/models/session_models.js +0 -1
- package/dist/generated/cloudflare/models/session_start_sources.js +0 -1
- package/dist/generated/cloudflare/models/thread_sessions.js +0 -1
- package/dist/generated/cloudflare/models/thread_worktrees.js +0 -1
- package/dist/generated/cloudflare/models.js +0 -1
- package/dist/generated/commonInputTypes.js +0 -10
- package/dist/generated/enums.js +0 -52
- package/dist/generated/internal/class.js +0 -49
- package/dist/generated/internal/prismaNamespace.js +0 -254
- package/dist/generated/internal/prismaNamespaceBrowser.js +0 -224
- package/dist/generated/models/bot_api_keys.js +0 -1
- package/dist/generated/models/bot_tokens.js +0 -1
- package/dist/generated/models/channel_agents.js +0 -1
- package/dist/generated/models/channel_directories.js +0 -1
- package/dist/generated/models/channel_mention_mode.js +0 -1
- package/dist/generated/models/channel_models.js +0 -1
- package/dist/generated/models/channel_verbosity.js +0 -1
- package/dist/generated/models/channel_worktrees.js +0 -1
- package/dist/generated/models/external_session_pending_prompts.js +0 -1
- package/dist/generated/models/forum_sync_configs.js +0 -1
- package/dist/generated/models/global_models.js +0 -1
- package/dist/generated/models/ipc_requests.js +0 -1
- package/dist/generated/models/part_messages.js +0 -1
- package/dist/generated/models/pending_auto_start.js +0 -1
- package/dist/generated/models/scheduled_tasks.js +0 -1
- package/dist/generated/models/session_agents.js +0 -1
- package/dist/generated/models/session_events.js +0 -1
- package/dist/generated/models/session_models.js +0 -1
- package/dist/generated/models/session_start_sources.js +0 -1
- package/dist/generated/models/session_thinking.js +0 -1
- package/dist/generated/models/thread_allowed_directories.js +0 -1
- package/dist/generated/models/thread_sessions.js +0 -1
- package/dist/generated/models/thread_worktrees.js +0 -1
- package/dist/generated/models.js +0 -1
- package/dist/generated/node/browser.js +0 -17
- package/dist/generated/node/client.js +0 -37
- package/dist/generated/node/commonInputTypes.js +0 -10
- package/dist/generated/node/enums.js +0 -48
- package/dist/generated/node/internal/class.js +0 -49
- package/dist/generated/node/internal/prismaNamespace.js +0 -252
- package/dist/generated/node/internal/prismaNamespaceBrowser.js +0 -222
- package/dist/generated/node/models/bot_api_keys.js +0 -1
- package/dist/generated/node/models/bot_tokens.js +0 -1
- package/dist/generated/node/models/channel_agents.js +0 -1
- package/dist/generated/node/models/channel_directories.js +0 -1
- package/dist/generated/node/models/channel_mention_mode.js +0 -1
- package/dist/generated/node/models/channel_models.js +0 -1
- package/dist/generated/node/models/channel_verbosity.js +0 -1
- package/dist/generated/node/models/channel_worktrees.js +0 -1
- package/dist/generated/node/models/forum_sync_configs.js +0 -1
- package/dist/generated/node/models/global_models.js +0 -1
- package/dist/generated/node/models/ipc_requests.js +0 -1
- package/dist/generated/node/models/part_messages.js +0 -1
- package/dist/generated/node/models/scheduled_tasks.js +0 -1
- package/dist/generated/node/models/session_agents.js +0 -1
- package/dist/generated/node/models/session_events.js +0 -1
- package/dist/generated/node/models/session_models.js +0 -1
- package/dist/generated/node/models/session_start_sources.js +0 -1
- package/dist/generated/node/models/thread_sessions.js +0 -1
- package/dist/generated/node/models/thread_worktrees.js +0 -1
- package/dist/generated/node/models.js +0 -1
- package/dist/install-url.js +0 -27
- package/dist/kimaki-opencode-plugin-specs.js +0 -13
- package/dist/kimaki-real-discord.e2e.test.js +0 -294
- package/dist/kitty-graphics-parser.js +0 -3
- package/dist/kitty-graphics-parser.test.js +0 -276
- package/dist/kitty-graphics-plugin.js +0 -3
- package/dist/memory-overview-plugin.test.js +0 -262
- package/dist/message-flags-boundary.test.js +0 -54
- package/dist/message-preprocessing.test.js +0 -35
- package/dist/model-command.js +0 -293
- package/dist/onboarding-tutorial-plugin.js +0 -73
- package/dist/opencode-plugin-interrupt.test.js +0 -138
- package/dist/opencode-plugin-loading.e2e.test.js +0 -80
- package/dist/opencode-plugin.js +0 -13
- package/dist/opencode-plugin.test.js +0 -98
- package/dist/opencode.test.js +0 -85
- package/dist/orphan-opencode-sweep.test.js +0 -80
- package/dist/pkce.js +0 -23
- package/dist/platform/components-v2.js +0 -20
- package/dist/platform/discord-adapter.js +0 -1440
- package/dist/platform/discord-routes.js +0 -31
- package/dist/platform/message-flags.js +0 -8
- package/dist/platform/platform-value.js +0 -41
- package/dist/platform/slack-adapter.js +0 -872
- package/dist/platform/slack-markdown.js +0 -169
- package/dist/platform/types.js +0 -4
- package/dist/plugin.js +0 -1414
- package/dist/proxy-ws-preload.cjs +0 -85
- package/dist/session-handler/runtime-types.js +0 -3
- package/dist/session-handler/state.js +0 -53
- package/dist/session-handler/state.test.js +0 -52
- package/dist/session-handler/thread-runtime-state.test.js +0 -287
- package/dist/subagent-rate-limit-plugin.test.js +0 -120
- package/dist/system-prompt-drift-plugin.js +0 -248
- package/dist/system-prompt-drift-plugin.test.js +0 -158
- package/dist/thinking-utils.test.js +0 -48
- package/dist/thread-queue-advanced.e2e.test.js +0 -884
- package/dist/token-usage.js +0 -11
- package/dist/xai-realtime.js +0 -95
- package/schema.prisma +0 -296
- package/skills/event-sourcing-state/SKILL.md +0 -252
- package/skills/jitter/EDITOR.md +0 -219
- package/skills/jitter/EXPORT-INTERNALS.md +0 -309
- package/skills/jitter/SKILL.md +0 -158
- package/skills/jitter/jitter-clipboard.json +0 -1042
- package/skills/jitter/package.json +0 -14
- package/skills/jitter/tsconfig.json +0 -15
- package/skills/jitter/utils/actions.ts +0 -212
- package/skills/jitter/utils/export.ts +0 -114
- package/skills/jitter/utils/index.ts +0 -141
- package/skills/jitter/utils/snapshot.ts +0 -154
- package/skills/jitter/utils/traverse.ts +0 -246
- package/skills/jitter/utils/types.ts +0 -279
- package/skills/jitter/utils/wait.ts +0 -133
- package/skills/parallel-security-review/SKILL.md +0 -332
- package/skills/proxyman/SKILL.md +0 -215
- package/skills/x-articles/SKILL.md +0 -554
- package/skills/zustand-centralized-state/SKILL.md +0 -1004
- package/src/generated/browser.ts +0 -114
- package/src/generated/client.ts +0 -138
- package/src/generated/commonInputTypes.ts +0 -736
- package/src/generated/enums.ts +0 -88
- package/src/generated/internal/class.ts +0 -384
- package/src/generated/internal/prismaNamespace.ts +0 -2387
- package/src/generated/internal/prismaNamespaceBrowser.ts +0 -327
- package/src/generated/models/bot_api_keys.ts +0 -1288
- package/src/generated/models/bot_tokens.ts +0 -1652
- package/src/generated/models/channel_agents.ts +0 -1256
- package/src/generated/models/channel_directories.ts +0 -1859
- package/src/generated/models/channel_mention_mode.ts +0 -1300
- package/src/generated/models/channel_models.ts +0 -1288
- package/src/generated/models/channel_verbosity.ts +0 -1228
- package/src/generated/models/channel_worktrees.ts +0 -1300
- package/src/generated/models/forum_sync_configs.ts +0 -1452
- package/src/generated/models/global_models.ts +0 -1288
- package/src/generated/models/ipc_requests.ts +0 -1485
- package/src/generated/models/part_messages.ts +0 -1302
- package/src/generated/models/scheduled_tasks.ts +0 -2320
- package/src/generated/models/session_agents.ts +0 -1086
- package/src/generated/models/session_events.ts +0 -1439
- package/src/generated/models/session_models.ts +0 -1114
- package/src/generated/models/session_start_sources.ts +0 -1408
- package/src/generated/models/thread_sessions.ts +0 -1833
- package/src/generated/models/thread_worktrees.ts +0 -1356
- package/src/generated/models.ts +0 -30
|
@@ -20,7 +20,9 @@ import { setDataDir } from './config.js';
|
|
|
20
20
|
import { store } from './store.js';
|
|
21
21
|
import { startDiscordBot } from './discord-bot.js';
|
|
22
22
|
import { setBotToken, initDatabase, closeDatabase, setChannelDirectory, setChannelVerbosity, setChannelAgent, setChannelModel, } from './database.js';
|
|
23
|
-
import {
|
|
23
|
+
import { getDb } from './db.js';
|
|
24
|
+
import * as orm from 'drizzle-orm';
|
|
25
|
+
import * as schema from './schema.js';
|
|
24
26
|
import { startHranaServer, stopHranaServer } from './hrana-server.js';
|
|
25
27
|
import { initializeOpencodeForDirectory, stopOpencodeServer } from './opencode.js';
|
|
26
28
|
import { chooseLockPort, cleanupTestSessions, initTestGitRepo, waitForBotMessageContaining, waitForFooterMessage, } from './test-utils.js';
|
|
@@ -379,13 +381,9 @@ describe('agent model resolution', () => {
|
|
|
379
381
|
`);
|
|
380
382
|
}, 15_000);
|
|
381
383
|
test('reply message injects replied-message context', async () => {
|
|
382
|
-
const
|
|
383
|
-
await
|
|
384
|
-
|
|
385
|
-
});
|
|
386
|
-
await prisma.channel_models.deleteMany({
|
|
387
|
-
where: { channel_id: TEXT_CHANNEL_ID },
|
|
388
|
-
});
|
|
384
|
+
const db = await getDb();
|
|
385
|
+
await db.delete(schema.channel_agents).where(orm.eq(schema.channel_agents.channel_id, TEXT_CHANNEL_ID));
|
|
386
|
+
await db.delete(schema.channel_models).where(orm.eq(schema.channel_models.channel_id, TEXT_CHANNEL_ID));
|
|
389
387
|
const existingThreadIds = new Set((await discord.channel(TEXT_CHANNEL_ID).getThreads()).map((thread) => {
|
|
390
388
|
return thread.id;
|
|
391
389
|
}));
|
|
@@ -429,10 +427,8 @@ describe('agent model resolution', () => {
|
|
|
429
427
|
}, 15_000);
|
|
430
428
|
test('new thread uses channel model when channel model preference is set', async () => {
|
|
431
429
|
// Clear channel agent so model resolution falls through to channel model
|
|
432
|
-
const
|
|
433
|
-
await
|
|
434
|
-
where: { channel_id: TEXT_CHANNEL_ID },
|
|
435
|
-
});
|
|
430
|
+
const db = await getDb();
|
|
431
|
+
await db.delete(schema.channel_agents).where(orm.eq(schema.channel_agents.channel_id, TEXT_CHANNEL_ID));
|
|
436
432
|
// Set channel model preference — simulates /model selecting a model at channel scope
|
|
437
433
|
await setChannelModel({
|
|
438
434
|
channelId: TEXT_CHANNEL_ID,
|
|
@@ -480,10 +476,8 @@ describe('agent model resolution', () => {
|
|
|
480
476
|
}, 15_000);
|
|
481
477
|
test('channel model with variant preference completes without error', async () => {
|
|
482
478
|
// Clear channel agent so model resolution falls through to channel model
|
|
483
|
-
const
|
|
484
|
-
await
|
|
485
|
-
where: { channel_id: TEXT_CHANNEL_ID },
|
|
486
|
-
});
|
|
479
|
+
const db = await getDb();
|
|
480
|
+
await db.delete(schema.channel_agents).where(orm.eq(schema.channel_agents.channel_id, TEXT_CHANNEL_ID));
|
|
487
481
|
// Set channel model with a variant (thinking level)
|
|
488
482
|
// The deterministic provider doesn't support thinking, so the variant
|
|
489
483
|
// is resolved but silently dropped (no matching thinking values).
|
|
@@ -610,14 +604,10 @@ describe('agent model resolution', () => {
|
|
|
610
604
|
}, 20_000);
|
|
611
605
|
test('thread created with no agent keeps default model after channel agent is set', async () => {
|
|
612
606
|
// Clear any channel agent — thread starts with default (no agent)
|
|
613
|
-
const
|
|
614
|
-
await
|
|
615
|
-
where: { channel_id: TEXT_CHANNEL_ID },
|
|
616
|
-
});
|
|
607
|
+
const db = await getDb();
|
|
608
|
+
await db.delete(schema.channel_agents).where(orm.eq(schema.channel_agents.channel_id, TEXT_CHANNEL_ID));
|
|
617
609
|
// Also clear channel model so we get the pure default
|
|
618
|
-
await
|
|
619
|
-
where: { channel_id: TEXT_CHANNEL_ID },
|
|
620
|
-
});
|
|
610
|
+
await db.delete(schema.channel_models).where(orm.eq(schema.channel_models.channel_id, TEXT_CHANNEL_ID));
|
|
621
611
|
// 1. Send a message to create a thread (no channel agent set)
|
|
622
612
|
await discord.channel(TEXT_CHANNEL_ID).user(TEST_USER_ID).sendMessage({
|
|
623
613
|
content: 'Reply with exactly: default-thread-msg',
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
// E2e tests for granting external_directory permissions from #channel references.
|
|
2
|
+
import { describe, expect, test } from 'vitest';
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import { CHANNEL_REFERENCE_EXTERNAL_DIR, CHANNEL_REFERENCE_EXTERNAL_FILE, setupQueueAdvancedSuite, TEST_USER_ID, } from './queue-advanced-e2e-setup.js';
|
|
5
|
+
import { setChannelDirectory } from './database.js';
|
|
6
|
+
import { waitForBotMessageContaining, waitForFooterMessage, } from './test-utils.js';
|
|
7
|
+
const TEXT_CHANNEL_ID = '200000000000001021';
|
|
8
|
+
const EXTERNAL_CHANNEL_ID = '200000000000001022';
|
|
9
|
+
describe('channel reference permissions', () => {
|
|
10
|
+
const ctx = setupQueueAdvancedSuite({
|
|
11
|
+
channelId: TEXT_CHANNEL_ID,
|
|
12
|
+
channelName: 'qa-channel-reference-e2e',
|
|
13
|
+
extraChannels: [{ id: EXTERNAL_CHANNEL_ID, name: 'external-project' }],
|
|
14
|
+
dirName: 'qa-channel-reference-e2e',
|
|
15
|
+
username: 'channel-reference-tester',
|
|
16
|
+
});
|
|
17
|
+
test('allows referenced project channel directories on new and existing sessions', async () => {
|
|
18
|
+
fs.mkdirSync(CHANNEL_REFERENCE_EXTERNAL_DIR, { recursive: true });
|
|
19
|
+
fs.writeFileSync(CHANNEL_REFERENCE_EXTERNAL_FILE, 'referenced channel file');
|
|
20
|
+
await setChannelDirectory({
|
|
21
|
+
channelId: EXTERNAL_CHANNEL_ID,
|
|
22
|
+
directory: CHANNEL_REFERENCE_EXTERNAL_DIR,
|
|
23
|
+
channelType: 'text',
|
|
24
|
+
});
|
|
25
|
+
await ctx.discord.channel(TEXT_CHANNEL_ID).user(TEST_USER_ID).sendMessage({
|
|
26
|
+
content: `Use <#${EXTERNAL_CHANNEL_ID}> CHANNEL_REFERENCE_PERMISSION_MARKER first`,
|
|
27
|
+
});
|
|
28
|
+
const thread = await ctx.discord.channel(TEXT_CHANNEL_ID).waitForThread({
|
|
29
|
+
timeout: 4_000,
|
|
30
|
+
predicate: (t) => {
|
|
31
|
+
return t.name?.includes('CHANNEL_REFERENCE_PERMISSION_MARKER') ?? false;
|
|
32
|
+
},
|
|
33
|
+
});
|
|
34
|
+
const th = ctx.discord.thread(thread.id);
|
|
35
|
+
await waitForBotMessageContaining({
|
|
36
|
+
discord: ctx.discord,
|
|
37
|
+
threadId: thread.id,
|
|
38
|
+
userId: TEST_USER_ID,
|
|
39
|
+
text: 'channel-reference-read-done',
|
|
40
|
+
timeout: 8_000,
|
|
41
|
+
});
|
|
42
|
+
await waitForFooterMessage({
|
|
43
|
+
discord: ctx.discord,
|
|
44
|
+
threadId: thread.id,
|
|
45
|
+
timeout: 4_000,
|
|
46
|
+
afterMessageIncludes: 'channel-reference-read-done',
|
|
47
|
+
afterAuthorId: ctx.discord.botUserId,
|
|
48
|
+
});
|
|
49
|
+
await th.user(TEST_USER_ID).sendMessage({
|
|
50
|
+
content: `Use <#${EXTERNAL_CHANNEL_ID}> CHANNEL_REFERENCE_PERMISSION_MARKER followup`,
|
|
51
|
+
});
|
|
52
|
+
await waitForBotMessageContaining({
|
|
53
|
+
discord: ctx.discord,
|
|
54
|
+
threadId: thread.id,
|
|
55
|
+
userId: TEST_USER_ID,
|
|
56
|
+
text: 'channel-reference-read-done',
|
|
57
|
+
afterUserMessageIncludes: 'followup',
|
|
58
|
+
timeout: 8_000,
|
|
59
|
+
});
|
|
60
|
+
await waitForFooterMessage({
|
|
61
|
+
discord: ctx.discord,
|
|
62
|
+
threadId: thread.id,
|
|
63
|
+
timeout: 4_000,
|
|
64
|
+
afterMessageIncludes: 'channel-reference-read-done',
|
|
65
|
+
afterAuthorId: ctx.discord.botUserId,
|
|
66
|
+
});
|
|
67
|
+
const text = await th.text();
|
|
68
|
+
expect(text).toMatchInlineSnapshot(`
|
|
69
|
+
"--- from: user (channel-reference-tester)
|
|
70
|
+
Use <#200000000000001022> CHANNEL_REFERENCE_PERMISSION_MARKER first
|
|
71
|
+
--- from: assistant (TestBot)
|
|
72
|
+
*using deterministic-provider/deterministic-v2*
|
|
73
|
+
⬥ reading referenced channel directory
|
|
74
|
+
⬥ channel-reference-read-done
|
|
75
|
+
*project ⋅ main ⋅ Ns ⋅ N% ⋅ deterministic-v2*
|
|
76
|
+
--- from: user (channel-reference-tester)
|
|
77
|
+
Use <#200000000000001022> CHANNEL_REFERENCE_PERMISSION_MARKER followup
|
|
78
|
+
--- from: assistant (TestBot)
|
|
79
|
+
⬥ reading referenced channel directory
|
|
80
|
+
⬥ channel-reference-read-done
|
|
81
|
+
*project ⋅ main ⋅ Ns ⋅ N% ⋅ deterministic-v2*"
|
|
82
|
+
`);
|
|
83
|
+
expect(text).not.toContain('Permission Required');
|
|
84
|
+
});
|
|
85
|
+
});
|
package/dist/cli-commands/bot.js
CHANGED
|
@@ -11,7 +11,7 @@ import { fileURLToPath } from 'node:url';
|
|
|
11
11
|
import { spawn, execSync } from 'node:child_process';
|
|
12
12
|
import { createLogger, LogPrefix, initLogFile } from '../logger.js';
|
|
13
13
|
import { createDiscordClient, initDatabase, getChannelDirectory, initializeOpencodeForDirectory, createProjectChannels } from '../discord-bot.js';
|
|
14
|
-
import { getBotTokenWithMode, getThreadSession, getThreadIdBySessionId, getSessionEventSnapshot,
|
|
14
|
+
import { getBotTokenWithMode, getThreadSession, getThreadIdBySessionId, getSessionEventSnapshot, createScheduledTask, listScheduledTasks, cancelScheduledTask, getScheduledTask, updateScheduledTask, getSessionStartSourcesBySessionIds, deleteChannelDirectoryById, findChannelsByDirectory } from '../database.js';
|
|
15
15
|
import { ShareMarkdown } from '../markdown.js';
|
|
16
16
|
import { parseSessionSearchPattern, findFirstSessionSearchHit, buildSessionSearchSnippet, getPartSearchTexts } from '../session-search.js';
|
|
17
17
|
import { formatWorktreeName, formatAutoWorktreeName } from '../commands/new-worktree.js';
|
|
@@ -11,7 +11,7 @@ import { fileURLToPath } from 'node:url';
|
|
|
11
11
|
import { spawn, execSync } from 'node:child_process';
|
|
12
12
|
import { createLogger, LogPrefix, initLogFile } from '../logger.js';
|
|
13
13
|
import { createDiscordClient, initDatabase, getChannelDirectory, initializeOpencodeForDirectory, createProjectChannels } from '../discord-bot.js';
|
|
14
|
-
import { getBotTokenWithMode, getThreadSession, getThreadIdBySessionId, getSessionEventSnapshot,
|
|
14
|
+
import { getBotTokenWithMode, getThreadSession, getThreadIdBySessionId, getSessionEventSnapshot, createScheduledTask, listScheduledTasks, cancelScheduledTask, getScheduledTask, updateScheduledTask, getSessionStartSourcesBySessionIds, deleteChannelDirectoryById, findChannelsByDirectory } from '../database.js';
|
|
15
15
|
import { ShareMarkdown } from '../markdown.js';
|
|
16
16
|
import { parseSessionSearchPattern, findFirstSessionSearchHit, buildSessionSearchSnippet, getPartSearchTexts } from '../session-search.js';
|
|
17
17
|
import { formatWorktreeName, formatAutoWorktreeName } from '../commands/new-worktree.js';
|
|
@@ -11,7 +11,7 @@ import { fileURLToPath } from 'node:url';
|
|
|
11
11
|
import { spawn, execSync } from 'node:child_process';
|
|
12
12
|
import { createLogger, LogPrefix, initLogFile } from '../logger.js';
|
|
13
13
|
import { createDiscordClient, initDatabase, getChannelDirectory, initializeOpencodeForDirectory, createProjectChannels } from '../discord-bot.js';
|
|
14
|
-
import { getBotTokenWithMode, getThreadSession, getThreadIdBySessionId, getSessionEventSnapshot,
|
|
14
|
+
import { getBotTokenWithMode, getThreadSession, getThreadIdBySessionId, getSessionEventSnapshot, createScheduledTask, listScheduledTasks, cancelScheduledTask, getScheduledTask, updateScheduledTask, getSessionStartSourcesBySessionIds, deleteChannelDirectoryById, findChannelsByDirectory } from '../database.js';
|
|
15
15
|
import { ShareMarkdown } from '../markdown.js';
|
|
16
16
|
import { parseSessionSearchPattern, findFirstSessionSearchHit, buildSessionSearchSnippet, getPartSearchTexts } from '../session-search.js';
|
|
17
17
|
import { formatWorktreeName, formatAutoWorktreeName } from '../commands/new-worktree.js';
|
|
@@ -11,7 +11,7 @@ import { fileURLToPath } from 'node:url';
|
|
|
11
11
|
import { spawn, execSync } from 'node:child_process';
|
|
12
12
|
import { createLogger, LogPrefix, initLogFile } from '../logger.js';
|
|
13
13
|
import { createDiscordClient, initDatabase, getChannelDirectory, initializeOpencodeForDirectory, createProjectChannels } from '../discord-bot.js';
|
|
14
|
-
import { getBotTokenWithMode, getThreadSession, getThreadIdBySessionId, getSessionEventSnapshot,
|
|
14
|
+
import { getBotTokenWithMode, getThreadSession, getThreadIdBySessionId, getSessionEventSnapshot, getDb, createScheduledTask, listScheduledTasks, cancelScheduledTask, getScheduledTask, updateScheduledTask, getSessionStartSourcesBySessionIds, deleteChannelDirectoryById, findChannelsByDirectory } from '../database.js';
|
|
15
15
|
import { ShareMarkdown } from '../markdown.js';
|
|
16
16
|
import { parseSessionSearchPattern, findFirstSessionSearchHit, buildSessionSearchSnippet, getPartSearchTexts } from '../session-search.js';
|
|
17
17
|
import { formatWorktreeName, formatAutoWorktreeName } from '../commands/new-worktree.js';
|
|
@@ -70,10 +70,10 @@ cli
|
|
|
70
70
|
guild = foundGuild;
|
|
71
71
|
}
|
|
72
72
|
else {
|
|
73
|
-
const existingChannelId = await (await
|
|
73
|
+
const existingChannelId = await (await getDb()).query.channel_directories.findFirst({
|
|
74
74
|
where: { channel_type: 'text' },
|
|
75
75
|
orderBy: { created_at: 'desc' },
|
|
76
|
-
|
|
76
|
+
columns: { channel_id: true },
|
|
77
77
|
}).then((row) => row?.channel_id);
|
|
78
78
|
if (existingChannelId) {
|
|
79
79
|
try {
|
|
@@ -168,8 +168,8 @@ cli
|
|
|
168
168
|
.option('--prune', 'Remove stale entries whose Discord channel no longer exists')
|
|
169
169
|
.action(async (options) => {
|
|
170
170
|
await initDatabase();
|
|
171
|
-
const
|
|
172
|
-
const channels = await
|
|
171
|
+
const db = await getDb();
|
|
172
|
+
const channels = await db.query.channel_directories.findMany({
|
|
173
173
|
where: { channel_type: 'text' },
|
|
174
174
|
orderBy: { created_at: 'desc' },
|
|
175
175
|
});
|
|
@@ -11,7 +11,7 @@ import { fileURLToPath } from 'node:url';
|
|
|
11
11
|
import { spawn, execSync } from 'node:child_process';
|
|
12
12
|
import { createLogger, LogPrefix, initLogFile } from '../logger.js';
|
|
13
13
|
import { createDiscordClient, initDatabase, getChannelDirectory, initializeOpencodeForDirectory, createProjectChannels } from '../discord-bot.js';
|
|
14
|
-
import { getBotTokenWithMode, getThreadSession, getThreadIdBySessionId, getSessionEventSnapshot,
|
|
14
|
+
import { getBotTokenWithMode, getThreadSession, getThreadIdBySessionId, getSessionEventSnapshot, getDb, createScheduledTask, listScheduledTasks, cancelScheduledTask, getScheduledTask, updateScheduledTask, getSessionStartSourcesBySessionIds, deleteChannelDirectoryById, findChannelsByDirectory } from '../database.js';
|
|
15
15
|
import { ShareMarkdown } from '../markdown.js';
|
|
16
16
|
import { parseSessionSearchPattern, findFirstSessionSearchHit, buildSessionSearchSnippet, getPartSearchTexts } from '../session-search.js';
|
|
17
17
|
import { formatWorktreeName, formatAutoWorktreeName } from '../commands/new-worktree.js';
|
|
@@ -20,7 +20,7 @@ import { buildOpencodeEventLogLine } from '../session-handler/opencode-session-e
|
|
|
20
20
|
import { createDiscordRest } from '../discord-urls.js';
|
|
21
21
|
import { archiveThread, uploadFilesToDiscord, stripMentions } from '../discord-utils.js';
|
|
22
22
|
import { setDataDir, setProjectsDir, getDataDir, getProjectsDir } from '../config.js';
|
|
23
|
-
import { execAsync,
|
|
23
|
+
import { execAsync, resolveSessionWorkingDirectory } from '../worktrees.js';
|
|
24
24
|
import { upgrade, getCurrentVersion } from '../upgrade.js';
|
|
25
25
|
import { getPromptPreview, parseSendAtValue, parseScheduledTaskPayload, serializeScheduledTaskPayload } from '../task-schedule.js';
|
|
26
26
|
import { EXIT_NO_RESTART, formatMemberLookupUnavailableMessage, formatRelativeTime, formatTaskScheduleLine, isDiscordMemberLookupUnavailable, isGuildMemberSearchResult, isThreadChannelType, printDiscordInstallUrlAndExit, resolveBotCredentials, resolveDiscordUserOption, sendDiscordMessageWithOptionalAttachment, } from '../cli-runner.js';
|
|
@@ -36,7 +36,7 @@ cli
|
|
|
36
36
|
.option('-a, --app-id [appId]', 'Bot application ID (required if no local database)')
|
|
37
37
|
.option('--notify-only', 'Create notification thread without starting AI session')
|
|
38
38
|
.option('--worktree [name]', 'Create git worktree for session (name optional, derives from thread name)')
|
|
39
|
-
.option('--cwd <path>', 'Start session in an existing
|
|
39
|
+
.option('--cwd <path>', 'Start session in an existing project subfolder or git worktree directory')
|
|
40
40
|
.option('-u, --user <user>', 'Discord user ID, mention, or username to add to thread')
|
|
41
41
|
.option('--agent <agent>', 'Agent to use for the session')
|
|
42
42
|
.option('--model <model>', 'Model to use (format: provider/model)')
|
|
@@ -104,6 +104,7 @@ cli
|
|
|
104
104
|
}
|
|
105
105
|
process.exit(EXIT_NO_RESTART);
|
|
106
106
|
}
|
|
107
|
+
const waitStartedAtMs = options.wait ? Date.now() : undefined;
|
|
107
108
|
if (!existingThreadMode && options.worktree && notifyOnly) {
|
|
108
109
|
cliLogger.error('Cannot use --worktree with --notify-only');
|
|
109
110
|
process.exit(EXIT_NO_RESTART);
|
|
@@ -208,10 +209,10 @@ cli
|
|
|
208
209
|
});
|
|
209
210
|
// Get guild from existing channels or first available
|
|
210
211
|
const guild = await (async () => {
|
|
211
|
-
const existingChannelId = await (await
|
|
212
|
+
const existingChannelId = await (await getDb()).query.channel_directories.findFirst({
|
|
212
213
|
where: { channel_type: 'text' },
|
|
213
214
|
orderBy: { created_at: 'desc' },
|
|
214
|
-
|
|
215
|
+
columns: { channel_id: true },
|
|
215
216
|
}).then((row) => row?.channel_id);
|
|
216
217
|
if (existingChannelId) {
|
|
217
218
|
try {
|
|
@@ -340,6 +341,7 @@ cli
|
|
|
340
341
|
await waitAndOutputSession({
|
|
341
342
|
threadId: targetThreadId,
|
|
342
343
|
projectDirectory: channelConfig.directory,
|
|
344
|
+
waitStartedAtMs,
|
|
343
345
|
});
|
|
344
346
|
}
|
|
345
347
|
process.exit(0);
|
|
@@ -356,10 +358,10 @@ cli
|
|
|
356
358
|
throw new Error(`Channel #${channelData.name} is not configured with a project directory. Run the bot first to sync channel data.`);
|
|
357
359
|
}
|
|
358
360
|
const projectDirectory = channelConfig.directory;
|
|
359
|
-
// Validate --cwd is an existing git worktree
|
|
361
|
+
// Validate --cwd is inside the project or an existing git worktree.
|
|
360
362
|
let resolvedCwd;
|
|
361
363
|
if (options.cwd) {
|
|
362
|
-
const cwdResult = await
|
|
364
|
+
const cwdResult = await resolveSessionWorkingDirectory({
|
|
363
365
|
projectDirectory,
|
|
364
366
|
candidatePath: options.cwd,
|
|
365
367
|
});
|
|
@@ -367,7 +369,7 @@ cli
|
|
|
367
369
|
cliLogger.error(cwdResult.message);
|
|
368
370
|
process.exit(EXIT_NO_RESTART);
|
|
369
371
|
}
|
|
370
|
-
resolvedCwd = cwdResult;
|
|
372
|
+
resolvedCwd = cwdResult.directory;
|
|
371
373
|
}
|
|
372
374
|
const resolvedUser = await resolveDiscordUserOption({
|
|
373
375
|
user: options.user,
|
|
@@ -483,6 +485,7 @@ cli
|
|
|
483
485
|
await waitAndOutputSession({
|
|
484
486
|
threadId: threadData.id,
|
|
485
487
|
projectDirectory,
|
|
488
|
+
waitStartedAtMs,
|
|
486
489
|
});
|
|
487
490
|
}
|
|
488
491
|
process.exit(0);
|
|
@@ -11,7 +11,7 @@ import { fileURLToPath } from 'node:url';
|
|
|
11
11
|
import { spawn, execSync } from 'node:child_process';
|
|
12
12
|
import { createLogger, LogPrefix, initLogFile } from '../logger.js';
|
|
13
13
|
import { createDiscordClient, initDatabase, getChannelDirectory, initializeOpencodeForDirectory, createProjectChannels } from '../discord-bot.js';
|
|
14
|
-
import { getBotTokenWithMode, getThreadSession, getThreadIdBySessionId, getSessionEventSnapshot,
|
|
14
|
+
import { getBotTokenWithMode, getThreadSession, getThreadIdBySessionId, getSessionEventSnapshot, getDb, createScheduledTask, listScheduledTasks, cancelScheduledTask, getScheduledTask, updateScheduledTask, getSessionStartSourcesBySessionIds, deleteChannelDirectoryById, findChannelsByDirectory, getThreadWorktree } from '../database.js';
|
|
15
15
|
import { ShareMarkdown } from '../markdown.js';
|
|
16
16
|
import { parseSessionSearchPattern, findFirstSessionSearchHit, buildSessionSearchSnippet, getPartSearchTexts } from '../session-search.js';
|
|
17
17
|
import { formatWorktreeName, formatAutoWorktreeName } from '../commands/new-worktree.js';
|
|
@@ -26,6 +26,30 @@ import { getPromptPreview, parseSendAtValue, parseScheduledTaskPayload, serializ
|
|
|
26
26
|
import { EXIT_NO_RESTART, formatMemberLookupUnavailableMessage, formatRelativeTime, formatTaskScheduleLine, isDiscordMemberLookupUnavailable, isGuildMemberSearchResult, isThreadChannelType, printDiscordInstallUrlAndExit, resolveBotCredentials, resolveDiscordUserOption, sendDiscordMessageWithOptionalAttachment, } from '../cli-runner.js';
|
|
27
27
|
const cliLogger = createLogger(LogPrefix.CLI);
|
|
28
28
|
const cli = goke();
|
|
29
|
+
async function resolveSessionDirectoryFromDatabase({ sessionId, }) {
|
|
30
|
+
const threadId = await getThreadIdBySessionId(sessionId);
|
|
31
|
+
if (threadId) {
|
|
32
|
+
const worktree = await getThreadWorktree(threadId);
|
|
33
|
+
if (worktree?.status === 'ready' && worktree.worktree_directory) {
|
|
34
|
+
return worktree.worktree_directory;
|
|
35
|
+
}
|
|
36
|
+
const { token: botToken } = await resolveBotCredentials({});
|
|
37
|
+
const rest = createDiscordRest(botToken);
|
|
38
|
+
const threadData = (await rest.get(Routes.channel(threadId)));
|
|
39
|
+
if (!isThreadChannelType(threadData.type)) {
|
|
40
|
+
return new Error(`Channel is not a thread: ${threadId}`);
|
|
41
|
+
}
|
|
42
|
+
if (!threadData.parent_id) {
|
|
43
|
+
return new Error(`Thread has no parent channel: ${threadId}`);
|
|
44
|
+
}
|
|
45
|
+
const channelConfig = await getChannelDirectory(threadData.parent_id);
|
|
46
|
+
if (!channelConfig) {
|
|
47
|
+
return new Error(`Thread parent channel is not configured with a project directory: ${threadData.parent_id}`);
|
|
48
|
+
}
|
|
49
|
+
return channelConfig.directory;
|
|
50
|
+
}
|
|
51
|
+
return new Error(`Session is not linked to a Kimaki thread in the local database: ${sessionId}`);
|
|
52
|
+
}
|
|
29
53
|
cli
|
|
30
54
|
.command('session list', 'List all OpenCode sessions, marking which were started via Kimaki')
|
|
31
55
|
.option('--project <path>', 'Project directory to list sessions for (defaults to cwd)')
|
|
@@ -47,9 +71,9 @@ cli
|
|
|
47
71
|
process.exit(0);
|
|
48
72
|
}
|
|
49
73
|
// Look up which sessions were started via kimaki (have a thread mapping)
|
|
50
|
-
const
|
|
51
|
-
const threadSessions = await
|
|
52
|
-
|
|
74
|
+
const db = await getDb();
|
|
75
|
+
const threadSessions = await db.query.thread_sessions.findMany({
|
|
76
|
+
columns: { thread_id: true, session_id: true },
|
|
53
77
|
});
|
|
54
78
|
const sessionToThread = new Map(threadSessions
|
|
55
79
|
.filter((row) => row.session_id !== '')
|
|
@@ -162,6 +186,30 @@ cli
|
|
|
162
186
|
process.exit(EXIT_NO_RESTART);
|
|
163
187
|
}
|
|
164
188
|
});
|
|
189
|
+
cli
|
|
190
|
+
.command('session wait <sessionId>', 'Wait for a session to finish, then print its conversation as markdown')
|
|
191
|
+
.action(async (sessionId) => {
|
|
192
|
+
try {
|
|
193
|
+
await initDatabase();
|
|
194
|
+
const projectDirectory = await resolveSessionDirectoryFromDatabase({
|
|
195
|
+
sessionId,
|
|
196
|
+
});
|
|
197
|
+
if (projectDirectory instanceof Error) {
|
|
198
|
+
cliLogger.error(projectDirectory.message);
|
|
199
|
+
process.exit(EXIT_NO_RESTART);
|
|
200
|
+
}
|
|
201
|
+
const { waitAndOutputExistingSession } = await import('../wait-session.js');
|
|
202
|
+
await waitAndOutputExistingSession({
|
|
203
|
+
sessionId,
|
|
204
|
+
projectDirectory,
|
|
205
|
+
});
|
|
206
|
+
process.exit(0);
|
|
207
|
+
}
|
|
208
|
+
catch (error) {
|
|
209
|
+
cliLogger.error('Error:', error instanceof Error ? error.stack : String(error));
|
|
210
|
+
process.exit(EXIT_NO_RESTART);
|
|
211
|
+
}
|
|
212
|
+
});
|
|
165
213
|
cli
|
|
166
214
|
.command('session search <query>', 'Search past sessions for text or /regex/flags in the selected project')
|
|
167
215
|
.option('--project <path>', 'Project directory (defaults to cwd)')
|
|
@@ -223,9 +271,9 @@ cli
|
|
|
223
271
|
cliLogger.log('No sessions found');
|
|
224
272
|
process.exit(0);
|
|
225
273
|
}
|
|
226
|
-
const
|
|
227
|
-
const threadSessions = await
|
|
228
|
-
|
|
274
|
+
const db = await getDb();
|
|
275
|
+
const threadSessions = await db.query.thread_sessions.findMany({
|
|
276
|
+
columns: { thread_id: true, session_id: true },
|
|
229
277
|
});
|
|
230
278
|
const sessionToThread = new Map(threadSessions
|
|
231
279
|
.filter((row) => row.session_id !== '')
|
|
@@ -11,7 +11,7 @@ import { fileURLToPath } from 'node:url';
|
|
|
11
11
|
import { spawn, execSync } from 'node:child_process';
|
|
12
12
|
import { createLogger, LogPrefix, initLogFile } from '../logger.js';
|
|
13
13
|
import { createDiscordClient, initDatabase, getChannelDirectory, initializeOpencodeForDirectory, createProjectChannels } from '../discord-bot.js';
|
|
14
|
-
import { getBotTokenWithMode, getThreadSession, getThreadIdBySessionId, getSessionEventSnapshot,
|
|
14
|
+
import { getBotTokenWithMode, getThreadSession, getThreadIdBySessionId, getSessionEventSnapshot, createScheduledTask, listScheduledTasks, cancelScheduledTask, getScheduledTask, updateScheduledTask, getSessionStartSourcesBySessionIds, deleteChannelDirectoryById, findChannelsByDirectory } from '../database.js';
|
|
15
15
|
import { ShareMarkdown } from '../markdown.js';
|
|
16
16
|
import { parseSessionSearchPattern, findFirstSessionSearchHit, buildSessionSearchSnippet, getPartSearchTexts } from '../session-search.js';
|
|
17
17
|
import { formatWorktreeName, formatAutoWorktreeName } from '../commands/new-worktree.js';
|
|
@@ -11,7 +11,7 @@ import { fileURLToPath } from 'node:url';
|
|
|
11
11
|
import { spawn, execSync } from 'node:child_process';
|
|
12
12
|
import { createLogger, LogPrefix, initLogFile } from '../logger.js';
|
|
13
13
|
import { createDiscordClient, initDatabase, getChannelDirectory, initializeOpencodeForDirectory, createProjectChannels } from '../discord-bot.js';
|
|
14
|
-
import { getBotTokenWithMode, getThreadSession, getThreadIdBySessionId, getSessionEventSnapshot,
|
|
14
|
+
import { getBotTokenWithMode, getThreadSession, getThreadIdBySessionId, getSessionEventSnapshot, createScheduledTask, listScheduledTasks, cancelScheduledTask, getScheduledTask, updateScheduledTask, getSessionStartSourcesBySessionIds, deleteChannelDirectoryById, findChannelsByDirectory } from '../database.js';
|
|
15
15
|
import { ShareMarkdown } from '../markdown.js';
|
|
16
16
|
import { parseSessionSearchPattern, findFirstSessionSearchHit, buildSessionSearchSnippet, getPartSearchTexts } from '../session-search.js';
|
|
17
17
|
import { formatWorktreeName, formatAutoWorktreeName } from '../commands/new-worktree.js';
|
package/dist/cli-runner.js
CHANGED
|
@@ -3,7 +3,9 @@
|
|
|
3
3
|
import { intro, outro, text, password, note, cancel, isCancel, confirm, log, multiselect, select, spinner, } from '@clack/prompts';
|
|
4
4
|
import { deduplicateByKey, generateBotInstallUrl, generateDiscordInstallUrlForBot, KIMAKI_GATEWAY_APP_ID, KIMAKI_WEBSITE_URL, abbreviatePath, } from './utils.js';
|
|
5
5
|
import { getChannelsWithDescriptions, createDiscordClient, initDatabase, getChannelDirectory, startDiscordBot, initializeOpencodeForDirectory, createProjectChannels, createDefaultKimakiChannel, } from './discord-bot.js';
|
|
6
|
-
import { getBotTokenWithMode, ensureServiceAuthToken, setBotToken, setBotMode, setChannelDirectory, findChannelsByDirectory,
|
|
6
|
+
import { getBotTokenWithMode, ensureServiceAuthToken, setBotToken, setBotMode, setChannelDirectory, findChannelsByDirectory, getDb, } from './database.js';
|
|
7
|
+
import * as orm from 'drizzle-orm';
|
|
8
|
+
import * as dbSchema from './schema.js';
|
|
7
9
|
import { selectResolvedCommand } from './opencode-command.js';
|
|
8
10
|
import { Events, ChannelType, Routes, AttachmentBuilder, } from 'discord.js';
|
|
9
11
|
import { discordApiUrl, getDiscordRestApiUrl, getGatewayProxyRestBaseUrl, getInternetReachableBaseUrl } from './discord-urls.js';
|
|
@@ -327,8 +329,8 @@ export async function resolveGatewayInstallCredentials() {
|
|
|
327
329
|
if (!KIMAKI_GATEWAY_APP_ID) {
|
|
328
330
|
return new Error('Gateway mode is not available yet. KIMAKI_GATEWAY_APP_ID is not configured.');
|
|
329
331
|
}
|
|
330
|
-
const
|
|
331
|
-
const gatewayBot = await
|
|
332
|
+
const db = await getDb();
|
|
333
|
+
const gatewayBot = await db.query.bot_tokens.findFirst({
|
|
332
334
|
where: { app_id: KIMAKI_GATEWAY_APP_ID },
|
|
333
335
|
});
|
|
334
336
|
if (gatewayBot?.client_id && gatewayBot.client_secret) {
|
|
@@ -687,7 +689,7 @@ export async function resolveCredentials({ forceRestartOnboarding, forceGateway,
|
|
|
687
689
|
// directly. This lets users switch back and forth between modes without
|
|
688
690
|
// re-running the onboarding wizard each time.
|
|
689
691
|
const hasGatewayCreds = (forceGateway && existingBot?.mode !== 'gateway')
|
|
690
|
-
? await (await
|
|
692
|
+
? await (await getDb()).query.bot_tokens.findFirst({
|
|
691
693
|
where: { app_id: KIMAKI_GATEWAY_APP_ID },
|
|
692
694
|
})
|
|
693
695
|
: undefined;
|
|
@@ -1029,10 +1031,9 @@ export async function run({ restartOnboarding, addChannels, useWorktrees, enable
|
|
|
1029
1031
|
// processes (send, upload-to-discord, project list) pick the correct bot.
|
|
1030
1032
|
// getBotTokenWithMode() orders by last_used_at DESC as cross-process
|
|
1031
1033
|
// source of truth.
|
|
1032
|
-
await (await
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
});
|
|
1034
|
+
await (await getDb()).update(dbSchema.bot_tokens)
|
|
1035
|
+
.set({ last_used_at: new Date() })
|
|
1036
|
+
.where(orm.eq(dbSchema.bot_tokens.app_id, appId));
|
|
1036
1037
|
// skipChannelSetup: when true, skip interactive project/channel selection
|
|
1037
1038
|
// and go straight to bot startup. Channel sync happens in the background.
|
|
1038
1039
|
//
|
|
@@ -1041,9 +1042,9 @@ export async function run({ restartOnboarding, addChannels, useWorktrees, enable
|
|
|
1041
1042
|
// Force channel setup when: first-time quick-start with no channels configured
|
|
1042
1043
|
// and TTY is available, or user explicitly passed --add-channels.
|
|
1043
1044
|
const isHeadlessGateway = isGatewayMode && !canUseInteractivePrompts();
|
|
1044
|
-
const hasConfiguredTextChannels = Boolean(await (await
|
|
1045
|
+
const hasConfiguredTextChannels = Boolean(await (await getDb()).query.channel_directories.findFirst({
|
|
1045
1046
|
where: { channel_type: 'text' },
|
|
1046
|
-
|
|
1047
|
+
columns: { channel_id: true },
|
|
1047
1048
|
}));
|
|
1048
1049
|
const skipChannelSetup = isHeadlessGateway || (() => {
|
|
1049
1050
|
// Wizard source always shows channel setup (user just completed onboarding)
|
|
@@ -23,7 +23,7 @@ import { startDiscordBot } from './discord-bot.js';
|
|
|
23
23
|
import { setBotToken, initDatabase, closeDatabase, setChannelDirectory, setChannelMentionMode, setChannelVerbosity, } from './database.js';
|
|
24
24
|
import { startHranaServer, stopHranaServer } from './hrana-server.js';
|
|
25
25
|
import { initializeOpencodeForDirectory, stopOpencodeServer, } from './opencode.js';
|
|
26
|
-
import {
|
|
26
|
+
import { chooseAvailableLockPort, cleanupTestSessions, initTestGitRepo, waitForBotMessageContaining, waitForFooterMessage, } from './test-utils.js';
|
|
27
27
|
import YAML from 'yaml';
|
|
28
28
|
const TEST_USER_ID = '200000000000000830';
|
|
29
29
|
const TEXT_CHANNEL_ID = '200000000000000831';
|
|
@@ -113,7 +113,7 @@ describe('kimaki send --channel thread creation', () => {
|
|
|
113
113
|
beforeAll(async () => {
|
|
114
114
|
testStartTime = Date.now();
|
|
115
115
|
directories = createRunDirectories();
|
|
116
|
-
const lockPort =
|
|
116
|
+
const lockPort = await chooseAvailableLockPort({ key: 'cli-send-thread-e2e' });
|
|
117
117
|
process.env['KIMAKI_LOCK_PORT'] = String(lockPort);
|
|
118
118
|
setDataDir(directories.dataDir);
|
|
119
119
|
previousDefaultVerbosity = store.getState().defaultVerbosity;
|
|
@@ -3,25 +3,26 @@
|
|
|
3
3
|
// clickable thread links and project names via Discord CV2 components.
|
|
4
4
|
import { ChatInputCommandInteraction, ComponentType, MessageFlags, } from 'discord.js';
|
|
5
5
|
import path from 'node:path';
|
|
6
|
-
import {
|
|
6
|
+
import { getDb } from '../db.js';
|
|
7
7
|
import { getChannelDirectory } from '../database.js';
|
|
8
8
|
import { splitTablesFromMarkdown } from '../format-tables.js';
|
|
9
9
|
import { formatTimeAgo } from './worktrees.js';
|
|
10
10
|
const MAX_ROWS = 20;
|
|
11
11
|
async function fetchRecentSessions({ client, }) {
|
|
12
|
-
const
|
|
12
|
+
const db = await getDb();
|
|
13
13
|
// Fetch all thread sessions with their most recent event timestamp.
|
|
14
|
-
//
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
select: {
|
|
14
|
+
// Fetch all sessions with their latest event and sort in JS.
|
|
15
|
+
const sessions = await db.query.thread_sessions.findMany({
|
|
16
|
+
columns: {
|
|
18
17
|
thread_id: true,
|
|
19
18
|
session_id: true,
|
|
20
19
|
created_at: true,
|
|
20
|
+
},
|
|
21
|
+
with: {
|
|
21
22
|
session_events: {
|
|
22
23
|
orderBy: { timestamp: 'desc' },
|
|
23
|
-
|
|
24
|
-
|
|
24
|
+
limit: 1,
|
|
25
|
+
columns: { timestamp: true },
|
|
25
26
|
},
|
|
26
27
|
},
|
|
27
28
|
});
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
// /unset-model-override command - Remove model overrides and use default instead.
|
|
2
2
|
import { ChatInputCommandInteraction, ChannelType, MessageFlags, } from 'discord.js';
|
|
3
3
|
import { getChannelModel, getSessionModel, getThreadSession, clearSessionModel, } from '../database.js';
|
|
4
|
-
import {
|
|
4
|
+
import { getDb } from '../db.js';
|
|
5
|
+
import * as orm from 'drizzle-orm';
|
|
6
|
+
import * as schema from '../schema.js';
|
|
5
7
|
import { initializeOpencodeForDirectory } from '../opencode.js';
|
|
6
8
|
import { resolveTextChannel, getKimakiMetadata } from '../discord-utils.js';
|
|
7
9
|
import { getRuntime } from '../session-handler/thread-session-runtime.js';
|
|
@@ -90,10 +92,8 @@ export async function handleUnsetModelCommand({ interaction, appId, }) {
|
|
|
90
92
|
}
|
|
91
93
|
else if (channelPref) {
|
|
92
94
|
// Clear channel override
|
|
93
|
-
const
|
|
94
|
-
await
|
|
95
|
-
where: { channel_id: targetChannelId },
|
|
96
|
-
});
|
|
95
|
+
const db = await getDb();
|
|
96
|
+
await db.delete(schema.channel_models).where(orm.eq(schema.channel_models.channel_id, targetChannelId));
|
|
97
97
|
clearedType = 'channel';
|
|
98
98
|
clearedModel = channelPref.modelId;
|
|
99
99
|
unsetModelLogger.log(`[UNSET-MODEL] Cleared channel model for ${targetChannelId}`);
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
// 'text_only': only shows text responses
|
|
6
6
|
import { ChatInputCommandInteraction, StringSelectMenuInteraction, StringSelectMenuBuilder, ActionRowBuilder, MessageFlags, ChannelType, } from 'discord.js';
|
|
7
7
|
import { getChannelVerbosity, setChannelVerbosity, } from '../database.js';
|
|
8
|
-
import {
|
|
8
|
+
import { getDb } from '../db.js';
|
|
9
9
|
import { store } from '../store.js';
|
|
10
10
|
import { createLogger, LogPrefix } from '../logger.js';
|
|
11
11
|
const verbosityLogger = createLogger(LogPrefix.VERBOSITY);
|
|
@@ -45,8 +45,8 @@ function resolveChannelId(channel) {
|
|
|
45
45
|
* Returns the override value if it exists, null otherwise.
|
|
46
46
|
*/
|
|
47
47
|
async function getChannelVerbosityOverride(channelId) {
|
|
48
|
-
const
|
|
49
|
-
const row = await
|
|
48
|
+
const db = await getDb();
|
|
49
|
+
const row = await db.query.channel_verbosity.findFirst({
|
|
50
50
|
where: { channel_id: channelId },
|
|
51
51
|
});
|
|
52
52
|
if (row?.verbosity) {
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
// including HTML-backed action buttons for deletable worktrees.
|
|
7
7
|
import { ButtonInteraction, ChatInputCommandInteraction, ChannelType, ComponentType, MessageFlags, } from 'discord.js';
|
|
8
8
|
import { deleteThreadWorktree, } from '../database.js';
|
|
9
|
-
import {
|
|
9
|
+
import { getDb } from '../db.js';
|
|
10
10
|
import { splitTablesFromMarkdown } from '../format-tables.js';
|
|
11
11
|
import { buildHtmlActionCustomId, cancelHtmlActionsForOwner, registerHtmlAction, } from '../html-actions.js';
|
|
12
12
|
import * as errore from 'errore';
|
|
@@ -195,8 +195,8 @@ async function resolveGitStatuses({ rows, projectDirectory, timeout, }) {
|
|
|
195
195
|
// Git is the source of truth for what exists on disk. DB rows that aren't
|
|
196
196
|
// in the git list (pending/error) are appended at the end.
|
|
197
197
|
async function buildWorktreeRows({ projectDirectory, gitWorktrees, }) {
|
|
198
|
-
const
|
|
199
|
-
const dbWorktrees = await
|
|
198
|
+
const db = await getDb();
|
|
199
|
+
const dbWorktrees = await db.query.thread_worktrees.findMany({
|
|
200
200
|
where: { project_directory: projectDirectory },
|
|
201
201
|
});
|
|
202
202
|
// Index DB worktrees by directory for fast lookup
|