@brianli/kimaki 0.4.72-brianli.1
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/bin.js +2 -0
- package/dist/ai-tool-to-genai.js +233 -0
- package/dist/ai-tool-to-genai.test.js +267 -0
- package/dist/ai-tool.js +6 -0
- package/dist/bin.js +87 -0
- package/dist/bot-token.js +121 -0
- package/dist/bot-token.test.js +134 -0
- package/dist/channel-management.js +101 -0
- package/dist/cli-parsing.test.js +89 -0
- package/dist/cli.js +2529 -0
- package/dist/commands/abort.js +82 -0
- package/dist/commands/action-buttons.js +257 -0
- package/dist/commands/add-project.js +114 -0
- package/dist/commands/agent.js +291 -0
- package/dist/commands/ask-question.js +223 -0
- package/dist/commands/compact.js +120 -0
- package/dist/commands/context-usage.js +140 -0
- package/dist/commands/create-new-project.js +118 -0
- package/dist/commands/diff.js +128 -0
- package/dist/commands/file-upload.js +275 -0
- package/dist/commands/fork.js +217 -0
- package/dist/commands/gemini-apikey.js +70 -0
- package/dist/commands/login.js +490 -0
- package/dist/commands/mention-mode.js +51 -0
- package/dist/commands/merge-worktree.js +124 -0
- package/dist/commands/model.js +694 -0
- package/dist/commands/permissions.js +163 -0
- package/dist/commands/queue.js +217 -0
- package/dist/commands/remove-project.js +115 -0
- package/dist/commands/restart-opencode-server.js +116 -0
- package/dist/commands/resume.js +159 -0
- package/dist/commands/run-command.js +79 -0
- package/dist/commands/session-id.js +78 -0
- package/dist/commands/session.js +192 -0
- package/dist/commands/share.js +80 -0
- package/dist/commands/types.js +2 -0
- package/dist/commands/undo-redo.js +159 -0
- package/dist/commands/unset-model.js +152 -0
- package/dist/commands/upgrade.js +42 -0
- package/dist/commands/user-command.js +148 -0
- package/dist/commands/verbosity.js +60 -0
- package/dist/commands/worktree-settings.js +50 -0
- package/dist/commands/worktree.js +299 -0
- package/dist/condense-memory.js +33 -0
- package/dist/config.js +110 -0
- package/dist/database.js +1050 -0
- package/dist/db.js +159 -0
- package/dist/db.test.js +49 -0
- package/dist/discord-api.js +28 -0
- package/dist/discord-auth.js +231 -0
- package/dist/discord-auth.test.js +80 -0
- package/dist/discord-bot.js +997 -0
- package/dist/discord-utils.js +560 -0
- package/dist/discord-utils.test.js +115 -0
- package/dist/errors.js +167 -0
- package/dist/escape-backticks.test.js +429 -0
- package/dist/format-tables.js +122 -0
- package/dist/format-tables.test.js +199 -0
- package/dist/forum-sync/config.js +79 -0
- package/dist/forum-sync/discord-operations.js +154 -0
- package/dist/forum-sync/index.js +5 -0
- package/dist/forum-sync/markdown.js +117 -0
- package/dist/forum-sync/sync-to-discord.js +417 -0
- package/dist/forum-sync/sync-to-files.js +190 -0
- package/dist/forum-sync/types.js +53 -0
- package/dist/forum-sync/watchers.js +307 -0
- package/dist/gateway-consumer.js +232 -0
- package/dist/gateway-consumer.test.js +18 -0
- package/dist/genai-worker-wrapper.js +111 -0
- package/dist/genai-worker.js +311 -0
- package/dist/genai.js +232 -0
- package/dist/generated/browser.js +17 -0
- package/dist/generated/client.js +35 -0
- package/dist/generated/commonInputTypes.js +10 -0
- package/dist/generated/enums.js +30 -0
- package/dist/generated/internal/class.js +41 -0
- package/dist/generated/internal/prismaNamespace.js +239 -0
- package/dist/generated/internal/prismaNamespaceBrowser.js +209 -0
- package/dist/generated/models/bot_api_keys.js +1 -0
- package/dist/generated/models/bot_tokens.js +1 -0
- package/dist/generated/models/channel_agents.js +1 -0
- package/dist/generated/models/channel_directories.js +1 -0
- package/dist/generated/models/channel_mention_mode.js +1 -0
- package/dist/generated/models/channel_models.js +1 -0
- package/dist/generated/models/channel_verbosity.js +1 -0
- package/dist/generated/models/channel_worktrees.js +1 -0
- package/dist/generated/models/forum_sync_configs.js +1 -0
- package/dist/generated/models/global_models.js +1 -0
- package/dist/generated/models/ipc_requests.js +1 -0
- package/dist/generated/models/part_messages.js +1 -0
- package/dist/generated/models/scheduled_tasks.js +1 -0
- package/dist/generated/models/session_agents.js +1 -0
- package/dist/generated/models/session_models.js +1 -0
- package/dist/generated/models/session_start_sources.js +1 -0
- package/dist/generated/models/thread_sessions.js +1 -0
- package/dist/generated/models/thread_worktrees.js +1 -0
- package/dist/generated/models.js +1 -0
- package/dist/heap-monitor.js +95 -0
- package/dist/hrana-server.js +416 -0
- package/dist/hrana-server.test.js +368 -0
- package/dist/image-utils.js +112 -0
- package/dist/interaction-handler.js +327 -0
- package/dist/ipc-polling.js +251 -0
- package/dist/kimaki-digital-twin.e2e.test.js +165 -0
- package/dist/limit-heading-depth.js +25 -0
- package/dist/limit-heading-depth.test.js +105 -0
- package/dist/logger.js +160 -0
- package/dist/markdown.js +342 -0
- package/dist/markdown.test.js +253 -0
- package/dist/message-formatting.js +433 -0
- package/dist/message-formatting.test.js +73 -0
- package/dist/openai-realtime.js +228 -0
- package/dist/opencode-plugin-loading.e2e.test.js +91 -0
- package/dist/opencode-plugin.js +536 -0
- package/dist/opencode-plugin.test.js +98 -0
- package/dist/opencode.js +409 -0
- package/dist/privacy-sanitizer.js +105 -0
- package/dist/runtime-mode.js +51 -0
- package/dist/runtime-mode.test.js +115 -0
- package/dist/sentry.js +127 -0
- package/dist/session-handler/state.js +151 -0
- package/dist/session-handler.js +1874 -0
- package/dist/session-search.js +100 -0
- package/dist/session-search.test.js +40 -0
- package/dist/startup-service.js +153 -0
- package/dist/system-message.js +499 -0
- package/dist/task-runner.js +282 -0
- package/dist/task-schedule.js +191 -0
- package/dist/task-schedule.test.js +71 -0
- package/dist/thinking-utils.js +35 -0
- package/dist/thread-message-queue.e2e.test.js +781 -0
- package/dist/tools.js +359 -0
- package/dist/unnest-code-blocks.js +136 -0
- package/dist/unnest-code-blocks.test.js +641 -0
- package/dist/upgrade.js +114 -0
- package/dist/utils.js +109 -0
- package/dist/voice-handler.js +606 -0
- package/dist/voice.js +304 -0
- package/dist/voice.test.js +187 -0
- package/dist/wait-session.js +94 -0
- package/dist/worker-types.js +4 -0
- package/dist/worktree-utils.js +727 -0
- package/dist/xml.js +92 -0
- package/dist/xml.test.js +32 -0
- package/package.json +82 -0
- package/schema.prisma +246 -0
- package/skills/batch/SKILL.md +87 -0
- package/skills/critique/SKILL.md +129 -0
- package/skills/errore/SKILL.md +589 -0
- package/skills/goke/.prettierrc +5 -0
- package/skills/goke/CHANGELOG.md +40 -0
- package/skills/goke/LICENSE +21 -0
- package/skills/goke/README.md +666 -0
- package/skills/goke/SKILL.md +458 -0
- package/skills/goke/package.json +43 -0
- package/skills/goke/src/__test__/coerce.test.ts +411 -0
- package/skills/goke/src/__test__/index.test.ts +1798 -0
- package/skills/goke/src/__test__/types.test-d.ts +111 -0
- package/skills/goke/src/coerce.ts +547 -0
- package/skills/goke/src/goke.ts +1362 -0
- package/skills/goke/src/index.ts +16 -0
- package/skills/goke/src/mri.ts +164 -0
- package/skills/goke/tsconfig.json +15 -0
- package/skills/jitter/EDITOR.md +219 -0
- package/skills/jitter/EXPORT-INTERNALS.md +309 -0
- package/skills/jitter/SKILL.md +158 -0
- package/skills/jitter/jitter-clipboard.json +1042 -0
- package/skills/jitter/package.json +14 -0
- package/skills/jitter/tsconfig.json +15 -0
- package/skills/jitter/utils/actions.ts +212 -0
- package/skills/jitter/utils/export.ts +114 -0
- package/skills/jitter/utils/index.ts +141 -0
- package/skills/jitter/utils/snapshot.ts +154 -0
- package/skills/jitter/utils/traverse.ts +246 -0
- package/skills/jitter/utils/types.ts +279 -0
- package/skills/jitter/utils/wait.ts +133 -0
- package/skills/playwriter/SKILL.md +31 -0
- package/skills/security-review/SKILL.md +208 -0
- package/skills/simplify/SKILL.md +58 -0
- package/skills/termcast/SKILL.md +945 -0
- package/skills/tuistory/SKILL.md +250 -0
- package/skills/zustand-centralized-state/SKILL.md +582 -0
- package/src/__snapshots__/compact-session-context-no-system.md +35 -0
- package/src/__snapshots__/compact-session-context.md +41 -0
- package/src/__snapshots__/first-session-no-info.md +17 -0
- package/src/__snapshots__/first-session-with-info.md +23 -0
- package/src/__snapshots__/session-1.md +17 -0
- package/src/__snapshots__/session-2.md +5871 -0
- package/src/__snapshots__/session-3.md +17 -0
- package/src/__snapshots__/session-with-tools.md +5871 -0
- package/src/ai-tool-to-genai.test.ts +296 -0
- package/src/ai-tool-to-genai.ts +282 -0
- package/src/ai-tool.ts +39 -0
- package/src/bin.ts +108 -0
- package/src/bot-token.test.ts +171 -0
- package/src/bot-token.ts +159 -0
- package/src/channel-management.ts +172 -0
- package/src/cli-parsing.test.ts +132 -0
- package/src/cli.ts +3605 -0
- package/src/commands/abort.ts +112 -0
- package/src/commands/action-buttons.ts +376 -0
- package/src/commands/add-project.ts +152 -0
- package/src/commands/agent.ts +404 -0
- package/src/commands/ask-question.ts +330 -0
- package/src/commands/compact.ts +157 -0
- package/src/commands/context-usage.ts +199 -0
- package/src/commands/create-new-project.ts +179 -0
- package/src/commands/diff.ts +165 -0
- package/src/commands/file-upload.ts +389 -0
- package/src/commands/fork.ts +320 -0
- package/src/commands/gemini-apikey.ts +104 -0
- package/src/commands/login.ts +634 -0
- package/src/commands/mention-mode.ts +77 -0
- package/src/commands/merge-worktree.ts +177 -0
- package/src/commands/model.ts +961 -0
- package/src/commands/permissions.ts +261 -0
- package/src/commands/queue.ts +296 -0
- package/src/commands/remove-project.ts +155 -0
- package/src/commands/restart-opencode-server.ts +162 -0
- package/src/commands/resume.ts +242 -0
- package/src/commands/run-command.ts +123 -0
- package/src/commands/session-id.ts +109 -0
- package/src/commands/session.ts +250 -0
- package/src/commands/share.ts +106 -0
- package/src/commands/types.ts +25 -0
- package/src/commands/undo-redo.ts +221 -0
- package/src/commands/unset-model.ts +189 -0
- package/src/commands/upgrade.ts +52 -0
- package/src/commands/user-command.ts +193 -0
- package/src/commands/verbosity.ts +88 -0
- package/src/commands/worktree-settings.ts +79 -0
- package/src/commands/worktree.ts +431 -0
- package/src/condense-memory.ts +36 -0
- package/src/config.ts +148 -0
- package/src/database.ts +1530 -0
- package/src/db.test.ts +60 -0
- package/src/db.ts +190 -0
- package/src/discord-api.ts +35 -0
- package/src/discord-bot.ts +1316 -0
- package/src/discord-utils.test.ts +132 -0
- package/src/discord-utils.ts +767 -0
- package/src/errors.ts +213 -0
- package/src/escape-backticks.test.ts +469 -0
- package/src/format-tables.test.ts +223 -0
- package/src/format-tables.ts +145 -0
- package/src/forum-sync/config.ts +92 -0
- package/src/forum-sync/discord-operations.ts +241 -0
- package/src/forum-sync/index.ts +9 -0
- package/src/forum-sync/markdown.ts +176 -0
- package/src/forum-sync/sync-to-discord.ts +595 -0
- package/src/forum-sync/sync-to-files.ts +294 -0
- package/src/forum-sync/types.ts +175 -0
- package/src/forum-sync/watchers.ts +454 -0
- package/src/genai-worker-wrapper.ts +164 -0
- package/src/genai-worker.ts +386 -0
- package/src/genai.ts +321 -0
- package/src/generated/browser.ts +109 -0
- package/src/generated/client.ts +131 -0
- package/src/generated/commonInputTypes.ts +512 -0
- package/src/generated/enums.ts +46 -0
- package/src/generated/internal/class.ts +362 -0
- package/src/generated/internal/prismaNamespace.ts +2251 -0
- package/src/generated/internal/prismaNamespaceBrowser.ts +308 -0
- package/src/generated/models/bot_api_keys.ts +1288 -0
- package/src/generated/models/bot_tokens.ts +1577 -0
- package/src/generated/models/channel_agents.ts +1256 -0
- package/src/generated/models/channel_directories.ts +2104 -0
- package/src/generated/models/channel_mention_mode.ts +1300 -0
- package/src/generated/models/channel_models.ts +1288 -0
- package/src/generated/models/channel_verbosity.ts +1224 -0
- package/src/generated/models/channel_worktrees.ts +1308 -0
- package/src/generated/models/forum_sync_configs.ts +1452 -0
- package/src/generated/models/global_models.ts +1288 -0
- package/src/generated/models/ipc_requests.ts +1485 -0
- package/src/generated/models/part_messages.ts +1302 -0
- package/src/generated/models/scheduled_tasks.ts +2320 -0
- package/src/generated/models/session_agents.ts +1086 -0
- package/src/generated/models/session_models.ts +1114 -0
- package/src/generated/models/session_start_sources.ts +1408 -0
- package/src/generated/models/thread_sessions.ts +1599 -0
- package/src/generated/models/thread_worktrees.ts +1352 -0
- package/src/generated/models.ts +29 -0
- package/src/heap-monitor.ts +121 -0
- package/src/hrana-server.test.ts +428 -0
- package/src/hrana-server.ts +547 -0
- package/src/image-utils.ts +149 -0
- package/src/interaction-handler.ts +461 -0
- package/src/ipc-polling.ts +325 -0
- package/src/kimaki-digital-twin.e2e.test.ts +201 -0
- package/src/limit-heading-depth.test.ts +116 -0
- package/src/limit-heading-depth.ts +26 -0
- package/src/logger.ts +203 -0
- package/src/markdown.test.ts +360 -0
- package/src/markdown.ts +410 -0
- package/src/message-formatting.test.ts +81 -0
- package/src/message-formatting.ts +549 -0
- package/src/openai-realtime.ts +362 -0
- package/src/opencode-plugin-loading.e2e.test.ts +112 -0
- package/src/opencode-plugin.test.ts +108 -0
- package/src/opencode-plugin.ts +652 -0
- package/src/opencode.ts +554 -0
- package/src/privacy-sanitizer.ts +142 -0
- package/src/schema.sql +158 -0
- package/src/sentry.ts +137 -0
- package/src/session-handler/state.ts +232 -0
- package/src/session-handler.ts +2668 -0
- package/src/session-search.test.ts +50 -0
- package/src/session-search.ts +148 -0
- package/src/startup-service.ts +200 -0
- package/src/system-message.ts +568 -0
- package/src/task-runner.ts +425 -0
- package/src/task-schedule.test.ts +84 -0
- package/src/task-schedule.ts +287 -0
- package/src/thinking-utils.ts +61 -0
- package/src/thread-message-queue.e2e.test.ts +997 -0
- package/src/tools.ts +432 -0
- package/src/unnest-code-blocks.test.ts +679 -0
- package/src/unnest-code-blocks.ts +168 -0
- package/src/upgrade.ts +127 -0
- package/src/utils.ts +145 -0
- package/src/voice-handler.ts +852 -0
- package/src/voice.test.ts +219 -0
- package/src/voice.ts +444 -0
- package/src/wait-session.ts +147 -0
- package/src/worker-types.ts +64 -0
- package/src/worktree-utils.ts +988 -0
- package/src/xml.test.ts +38 -0
- package/src/xml.ts +121 -0
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
// End-to-end test using discord-digital-twin + real Kimaki bot runtime.
|
|
2
|
+
// Verifies onboarding channel creation, message -> thread creation, and assistant reply.
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { expect, test } from 'vitest';
|
|
6
|
+
import { ChannelType, Client, GatewayIntentBits, Partials } from 'discord.js';
|
|
7
|
+
import { DigitalDiscord } from 'discord-digital-twin/src';
|
|
8
|
+
import { CachedOpencodeProviderProxy } from 'opencode-cached-provider';
|
|
9
|
+
import { setDataDir } from './config.js';
|
|
10
|
+
import { startDiscordBot } from './discord-bot.js';
|
|
11
|
+
import { setBotToken, initDatabase, closeDatabase, setChannelDirectory, } from './database.js';
|
|
12
|
+
import { startHranaServer, stopHranaServer } from './hrana-server.js';
|
|
13
|
+
import { getOpencodeServers } from './opencode.js';
|
|
14
|
+
const geminiApiKey = process.env['GEMINI_API_KEY'] ||
|
|
15
|
+
process.env['GOOGLE_GENERATIVE_AI_API_KEY'] ||
|
|
16
|
+
'';
|
|
17
|
+
const geminiModel = process.env['GEMINI_FLASH_MODEL'] || 'gemini-2.5-flash';
|
|
18
|
+
const e2eTest = geminiApiKey.length > 0 ? test : test.skip;
|
|
19
|
+
function createRunDirectories() {
|
|
20
|
+
const root = path.resolve(process.cwd(), 'tmp', 'kimaki-digital-twin-e2e');
|
|
21
|
+
fs.mkdirSync(root, { recursive: true });
|
|
22
|
+
const dataDir = fs.mkdtempSync(path.join(root, 'data-'));
|
|
23
|
+
const projectDirectory = path.join(root, 'project');
|
|
24
|
+
const providerCacheDbPath = path.join(root, 'provider-cache.db');
|
|
25
|
+
fs.mkdirSync(projectDirectory, { recursive: true });
|
|
26
|
+
return {
|
|
27
|
+
root,
|
|
28
|
+
dataDir,
|
|
29
|
+
projectDirectory,
|
|
30
|
+
providerCacheDbPath,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
function chooseLockPort() {
|
|
34
|
+
return 45_000 + (Date.now() % 2_000);
|
|
35
|
+
}
|
|
36
|
+
function createDiscordJsClient({ restUrl }) {
|
|
37
|
+
return new Client({
|
|
38
|
+
intents: [
|
|
39
|
+
GatewayIntentBits.Guilds,
|
|
40
|
+
GatewayIntentBits.GuildMessages,
|
|
41
|
+
GatewayIntentBits.MessageContent,
|
|
42
|
+
GatewayIntentBits.GuildVoiceStates,
|
|
43
|
+
],
|
|
44
|
+
partials: [
|
|
45
|
+
Partials.Channel,
|
|
46
|
+
Partials.Message,
|
|
47
|
+
Partials.User,
|
|
48
|
+
Partials.ThreadMember,
|
|
49
|
+
],
|
|
50
|
+
rest: {
|
|
51
|
+
api: restUrl,
|
|
52
|
+
version: '10',
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
async function cleanupOpencodeServers() {
|
|
57
|
+
const servers = getOpencodeServers();
|
|
58
|
+
for (const [, server] of servers) {
|
|
59
|
+
if (!server.process.killed) {
|
|
60
|
+
server.process.kill('SIGTERM');
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
servers.clear();
|
|
64
|
+
}
|
|
65
|
+
e2eTest('onboarding then message creates thread and assistant reply via digital twin', async () => {
|
|
66
|
+
const directories = createRunDirectories();
|
|
67
|
+
const lockPort = chooseLockPort();
|
|
68
|
+
process.env['KIMAKI_LOCK_PORT'] = String(lockPort);
|
|
69
|
+
setDataDir(directories.dataDir);
|
|
70
|
+
const proxy = new CachedOpencodeProviderProxy({
|
|
71
|
+
cacheDbPath: directories.providerCacheDbPath,
|
|
72
|
+
targetBaseUrl: 'https://generativelanguage.googleapis.com/v1beta',
|
|
73
|
+
apiKey: geminiApiKey,
|
|
74
|
+
cacheMethods: ['POST'],
|
|
75
|
+
});
|
|
76
|
+
const testUserId = '100000000000000777';
|
|
77
|
+
const textChannelId = '100000000000000778';
|
|
78
|
+
const discord = new DigitalDiscord({
|
|
79
|
+
guild: {
|
|
80
|
+
name: 'Kimaki E2E Guild',
|
|
81
|
+
ownerId: testUserId,
|
|
82
|
+
},
|
|
83
|
+
channels: [
|
|
84
|
+
{
|
|
85
|
+
id: textChannelId,
|
|
86
|
+
name: 'kimaki-e2e',
|
|
87
|
+
type: ChannelType.GuildText,
|
|
88
|
+
},
|
|
89
|
+
],
|
|
90
|
+
users: [
|
|
91
|
+
{
|
|
92
|
+
id: testUserId,
|
|
93
|
+
username: 'e2e-user',
|
|
94
|
+
},
|
|
95
|
+
],
|
|
96
|
+
});
|
|
97
|
+
let botClient = null;
|
|
98
|
+
try {
|
|
99
|
+
await Promise.all([proxy.start(), discord.start()]);
|
|
100
|
+
const opencodeConfig = proxy.buildOpencodeConfig({
|
|
101
|
+
providerName: 'cached-google',
|
|
102
|
+
providerNpm: '@ai-sdk/google',
|
|
103
|
+
model: geminiModel,
|
|
104
|
+
smallModel: geminiModel,
|
|
105
|
+
});
|
|
106
|
+
fs.writeFileSync(path.join(directories.projectDirectory, 'opencode.json'), JSON.stringify(opencodeConfig, null, 2));
|
|
107
|
+
const dbPath = path.join(directories.dataDir, 'discord-sessions.db');
|
|
108
|
+
const hranaResult = await startHranaServer({ dbPath });
|
|
109
|
+
if (hranaResult instanceof Error) {
|
|
110
|
+
throw hranaResult;
|
|
111
|
+
}
|
|
112
|
+
process.env['KIMAKI_DB_URL'] = hranaResult;
|
|
113
|
+
await initDatabase();
|
|
114
|
+
await setBotToken(discord.botUserId, discord.botToken);
|
|
115
|
+
await setChannelDirectory({
|
|
116
|
+
channelId: textChannelId,
|
|
117
|
+
directory: directories.projectDirectory,
|
|
118
|
+
channelType: 'text',
|
|
119
|
+
appId: discord.botUserId,
|
|
120
|
+
});
|
|
121
|
+
botClient = createDiscordJsClient({ restUrl: discord.restUrl });
|
|
122
|
+
await startDiscordBot({
|
|
123
|
+
token: discord.botToken,
|
|
124
|
+
appId: discord.botUserId,
|
|
125
|
+
discordClient: botClient,
|
|
126
|
+
});
|
|
127
|
+
await discord.channel(textChannelId).user(testUserId).sendMessage({
|
|
128
|
+
content: 'Reply with exactly: kimaki digital twin ok',
|
|
129
|
+
});
|
|
130
|
+
const createdThread = await discord.channel(textChannelId).waitForThread({
|
|
131
|
+
timeout: 60_000,
|
|
132
|
+
predicate: (thread) => {
|
|
133
|
+
return thread.name === 'Reply with exactly: kimaki digital twin ok';
|
|
134
|
+
},
|
|
135
|
+
});
|
|
136
|
+
const botReply = await discord.thread(createdThread.id).waitForBotReply({
|
|
137
|
+
timeout: 120_000,
|
|
138
|
+
});
|
|
139
|
+
expect(createdThread.id.length).toBeGreaterThan(0);
|
|
140
|
+
expect(botReply.content.trim().length).toBeGreaterThan(0);
|
|
141
|
+
}
|
|
142
|
+
finally {
|
|
143
|
+
if (botClient) {
|
|
144
|
+
botClient.destroy();
|
|
145
|
+
}
|
|
146
|
+
await cleanupOpencodeServers();
|
|
147
|
+
await Promise.all([
|
|
148
|
+
closeDatabase().catch(() => {
|
|
149
|
+
return;
|
|
150
|
+
}),
|
|
151
|
+
stopHranaServer().catch(() => {
|
|
152
|
+
return;
|
|
153
|
+
}),
|
|
154
|
+
proxy.stop().catch(() => {
|
|
155
|
+
return;
|
|
156
|
+
}),
|
|
157
|
+
discord.stop().catch(() => {
|
|
158
|
+
return;
|
|
159
|
+
}),
|
|
160
|
+
]);
|
|
161
|
+
delete process.env['KIMAKI_LOCK_PORT'];
|
|
162
|
+
delete process.env['KIMAKI_DB_URL'];
|
|
163
|
+
fs.rmSync(directories.dataDir, { recursive: true, force: true });
|
|
164
|
+
}
|
|
165
|
+
}, 360_000);
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// Limit heading depth for Discord.
|
|
2
|
+
// Discord only supports headings up to ### (h3), so this converts
|
|
3
|
+
// ####, #####, etc. to ### to maintain consistent rendering.
|
|
4
|
+
import { Lexer } from 'marked';
|
|
5
|
+
export function limitHeadingDepth(markdown, maxDepth = 3) {
|
|
6
|
+
const lexer = new Lexer();
|
|
7
|
+
const tokens = lexer.lex(markdown);
|
|
8
|
+
let result = '';
|
|
9
|
+
for (const token of tokens) {
|
|
10
|
+
if (token.type === 'heading') {
|
|
11
|
+
const heading = token;
|
|
12
|
+
if (heading.depth > maxDepth) {
|
|
13
|
+
const hashes = '#'.repeat(maxDepth);
|
|
14
|
+
result += hashes + ' ' + heading.text + '\n';
|
|
15
|
+
}
|
|
16
|
+
else {
|
|
17
|
+
result += token.raw;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
else {
|
|
21
|
+
result += token.raw;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return result;
|
|
25
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { expect, test } from 'vitest';
|
|
2
|
+
import { limitHeadingDepth } from './limit-heading-depth.js';
|
|
3
|
+
test('converts h4 to h3', () => {
|
|
4
|
+
const input = '#### Fourth level heading';
|
|
5
|
+
const result = limitHeadingDepth(input);
|
|
6
|
+
expect(result).toMatchInlineSnapshot(`
|
|
7
|
+
"### Fourth level heading
|
|
8
|
+
"
|
|
9
|
+
`);
|
|
10
|
+
});
|
|
11
|
+
test('converts h5 to h3', () => {
|
|
12
|
+
const input = '##### Fifth level heading';
|
|
13
|
+
const result = limitHeadingDepth(input);
|
|
14
|
+
expect(result).toMatchInlineSnapshot(`
|
|
15
|
+
"### Fifth level heading
|
|
16
|
+
"
|
|
17
|
+
`);
|
|
18
|
+
});
|
|
19
|
+
test('converts h6 to h3', () => {
|
|
20
|
+
const input = '###### Sixth level heading';
|
|
21
|
+
const result = limitHeadingDepth(input);
|
|
22
|
+
expect(result).toMatchInlineSnapshot(`
|
|
23
|
+
"### Sixth level heading
|
|
24
|
+
"
|
|
25
|
+
`);
|
|
26
|
+
});
|
|
27
|
+
test('preserves h3 unchanged', () => {
|
|
28
|
+
const input = '### Third level heading';
|
|
29
|
+
const result = limitHeadingDepth(input);
|
|
30
|
+
expect(result).toMatchInlineSnapshot(`"### Third level heading"`);
|
|
31
|
+
});
|
|
32
|
+
test('preserves h2 unchanged', () => {
|
|
33
|
+
const input = '## Second level heading';
|
|
34
|
+
const result = limitHeadingDepth(input);
|
|
35
|
+
expect(result).toMatchInlineSnapshot(`"## Second level heading"`);
|
|
36
|
+
});
|
|
37
|
+
test('preserves h1 unchanged', () => {
|
|
38
|
+
const input = '# First level heading';
|
|
39
|
+
const result = limitHeadingDepth(input);
|
|
40
|
+
expect(result).toMatchInlineSnapshot(`"# First level heading"`);
|
|
41
|
+
});
|
|
42
|
+
test('handles multiple headings in document', () => {
|
|
43
|
+
const input = `# Title
|
|
44
|
+
|
|
45
|
+
Some text
|
|
46
|
+
|
|
47
|
+
## Section
|
|
48
|
+
|
|
49
|
+
### Subsection
|
|
50
|
+
|
|
51
|
+
#### Too deep
|
|
52
|
+
|
|
53
|
+
##### Even deeper
|
|
54
|
+
|
|
55
|
+
Regular paragraph
|
|
56
|
+
|
|
57
|
+
### Back to normal
|
|
58
|
+
`;
|
|
59
|
+
const result = limitHeadingDepth(input);
|
|
60
|
+
expect(result).toMatchInlineSnapshot(`
|
|
61
|
+
"# Title
|
|
62
|
+
|
|
63
|
+
Some text
|
|
64
|
+
|
|
65
|
+
## Section
|
|
66
|
+
|
|
67
|
+
### Subsection
|
|
68
|
+
|
|
69
|
+
### Too deep
|
|
70
|
+
### Even deeper
|
|
71
|
+
Regular paragraph
|
|
72
|
+
|
|
73
|
+
### Back to normal
|
|
74
|
+
"
|
|
75
|
+
`);
|
|
76
|
+
});
|
|
77
|
+
test('preserves heading with inline formatting', () => {
|
|
78
|
+
const input = '#### Heading with **bold** and `code`';
|
|
79
|
+
const result = limitHeadingDepth(input);
|
|
80
|
+
expect(result).toMatchInlineSnapshot(`
|
|
81
|
+
"### Heading with **bold** and \`code\`
|
|
82
|
+
"
|
|
83
|
+
`);
|
|
84
|
+
});
|
|
85
|
+
test('handles empty markdown', () => {
|
|
86
|
+
const result = limitHeadingDepth('');
|
|
87
|
+
expect(result).toMatchInlineSnapshot(`""`);
|
|
88
|
+
});
|
|
89
|
+
test('handles markdown with no headings', () => {
|
|
90
|
+
const input = 'Just some text\n\nAnd more text';
|
|
91
|
+
const result = limitHeadingDepth(input);
|
|
92
|
+
expect(result).toMatchInlineSnapshot(`
|
|
93
|
+
"Just some text
|
|
94
|
+
|
|
95
|
+
And more text"
|
|
96
|
+
`);
|
|
97
|
+
});
|
|
98
|
+
test('allows custom maxDepth', () => {
|
|
99
|
+
const input = '### Third level';
|
|
100
|
+
const result = limitHeadingDepth(input, 2);
|
|
101
|
+
expect(result).toMatchInlineSnapshot(`
|
|
102
|
+
"## Third level
|
|
103
|
+
"
|
|
104
|
+
`);
|
|
105
|
+
});
|
package/dist/logger.js
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
// Prefixed logging utility using @clack/prompts for consistent visual style.
|
|
2
|
+
// All log methods use clack's log.message() with appropriate symbols to prevent
|
|
3
|
+
// output interleaving from concurrent async operations.
|
|
4
|
+
import { log as clackLog } from '@clack/prompts';
|
|
5
|
+
import fs from 'node:fs';
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
import util from 'node:util';
|
|
8
|
+
import pc from 'picocolors';
|
|
9
|
+
import { sanitizeSensitiveText, sanitizeUnknownValue } from './privacy-sanitizer.js';
|
|
10
|
+
// All known log prefixes - add new ones here to keep alignment consistent
|
|
11
|
+
export const LogPrefix = {
|
|
12
|
+
ABORT: 'ABORT',
|
|
13
|
+
ADD_PROJECT: 'ADD_PROJ',
|
|
14
|
+
AGENT: 'AGENT',
|
|
15
|
+
ASK_QUESTION: 'QUESTION',
|
|
16
|
+
CLI: 'CLI',
|
|
17
|
+
COMPACT: 'COMPACT',
|
|
18
|
+
CREATE_PROJECT: 'NEW_PROJ',
|
|
19
|
+
DB: 'DB',
|
|
20
|
+
DIFF: 'DIFF',
|
|
21
|
+
FILE_UPLOAD: 'FILEUP',
|
|
22
|
+
DISCORD: 'DISCORD',
|
|
23
|
+
FORK: 'FORK',
|
|
24
|
+
FORMATTING: 'FORMAT',
|
|
25
|
+
GENAI: 'GENAI',
|
|
26
|
+
HEAP: 'HEAP',
|
|
27
|
+
GENAI_WORKER: 'GENAI_W',
|
|
28
|
+
INTERACTION: 'INTERACT',
|
|
29
|
+
IPC: 'IPC',
|
|
30
|
+
LOGIN: 'LOGIN',
|
|
31
|
+
MARKDOWN: 'MARKDOWN',
|
|
32
|
+
MODEL: 'MODEL',
|
|
33
|
+
OPENAI: 'OPENAI',
|
|
34
|
+
OPENCODE: 'OPENCODE',
|
|
35
|
+
PERMISSIONS: 'PERMS',
|
|
36
|
+
QUEUE: 'QUEUE',
|
|
37
|
+
REMOVE_PROJECT: 'RM_PROJ',
|
|
38
|
+
RESUME: 'RESUME',
|
|
39
|
+
SESSION: 'SESSION',
|
|
40
|
+
SHARE: 'SHARE',
|
|
41
|
+
TASK: 'TASK',
|
|
42
|
+
TOOLS: 'TOOLS',
|
|
43
|
+
UNDO_REDO: 'UNDO',
|
|
44
|
+
USER_CMD: 'USER_CMD',
|
|
45
|
+
VERBOSITY: 'VERBOSE',
|
|
46
|
+
VOICE: 'VOICE',
|
|
47
|
+
WORKER: 'WORKER',
|
|
48
|
+
THINKING: 'THINK',
|
|
49
|
+
WORKTREE: 'WORKTREE',
|
|
50
|
+
XML: 'XML',
|
|
51
|
+
};
|
|
52
|
+
// compute max length from all known prefixes for alignment
|
|
53
|
+
const MAX_PREFIX_LENGTH = Math.max(...Object.values(LogPrefix).map((p) => p.length));
|
|
54
|
+
// Log file path is set by initLogFile() after the data directory is known.
|
|
55
|
+
// Before initLogFile() is called, file logging is skipped.
|
|
56
|
+
let logFilePath = null;
|
|
57
|
+
/**
|
|
58
|
+
* Initialize file logging. Call this after setDataDir() so the log file
|
|
59
|
+
* is written to `<dataDir>/kimaki.log`. The log file is truncated on
|
|
60
|
+
* every bot startup so it contains only the current run's logs.
|
|
61
|
+
*/
|
|
62
|
+
export function initLogFile(dataDir) {
|
|
63
|
+
logFilePath = path.join(dataDir, 'kimaki.log');
|
|
64
|
+
const logDir = path.dirname(logFilePath);
|
|
65
|
+
if (!fs.existsSync(logDir)) {
|
|
66
|
+
fs.mkdirSync(logDir, { recursive: true });
|
|
67
|
+
}
|
|
68
|
+
fs.writeFileSync(logFilePath, `--- kimaki log started at ${new Date().toISOString()} ---\n`);
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Set the log file path without truncating. Use this in child processes
|
|
72
|
+
* (like the opencode plugin) that should append to the same log file
|
|
73
|
+
* the bot process already created with initLogFile().
|
|
74
|
+
*/
|
|
75
|
+
export function setLogFilePath(dataDir) {
|
|
76
|
+
logFilePath = path.join(dataDir, 'kimaki.log');
|
|
77
|
+
}
|
|
78
|
+
export function getLogFilePath() {
|
|
79
|
+
return logFilePath;
|
|
80
|
+
}
|
|
81
|
+
function formatArg(arg) {
|
|
82
|
+
if (typeof arg === 'string') {
|
|
83
|
+
return sanitizeSensitiveText(arg, { redactPaths: false });
|
|
84
|
+
}
|
|
85
|
+
const safeArg = sanitizeUnknownValue(arg, { redactPaths: false });
|
|
86
|
+
return util.inspect(safeArg, { colors: true, depth: 4 });
|
|
87
|
+
}
|
|
88
|
+
export function formatErrorWithStack(error) {
|
|
89
|
+
if (error instanceof Error) {
|
|
90
|
+
return sanitizeSensitiveText(error.stack ?? `${error.name}: ${error.message}`, { redactPaths: false });
|
|
91
|
+
}
|
|
92
|
+
if (typeof error === 'string') {
|
|
93
|
+
return sanitizeSensitiveText(error, { redactPaths: false });
|
|
94
|
+
}
|
|
95
|
+
// Keep this stable and safe for unknown values (handles circular structures).
|
|
96
|
+
const safeError = sanitizeUnknownValue(error, { redactPaths: false });
|
|
97
|
+
return sanitizeSensitiveText(util.inspect(safeError, { colors: false, depth: 4 }), {
|
|
98
|
+
redactPaths: false,
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
function writeToFile(level, prefix, args) {
|
|
102
|
+
if (!logFilePath) {
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
const timestamp = new Date().toISOString();
|
|
106
|
+
const message = `[${timestamp}] [${level}] [${prefix}] ${args.map(formatArg).join(' ')}\n`;
|
|
107
|
+
fs.appendFileSync(logFilePath, message);
|
|
108
|
+
}
|
|
109
|
+
function getTimestamp() {
|
|
110
|
+
const now = new Date();
|
|
111
|
+
return `${String(now.getHours()).padStart(2, '0')}:${String(now.getMinutes()).padStart(2, '0')}`;
|
|
112
|
+
}
|
|
113
|
+
function padPrefix(prefix) {
|
|
114
|
+
return prefix.padEnd(MAX_PREFIX_LENGTH);
|
|
115
|
+
}
|
|
116
|
+
function formatMessage(timestamp, prefix, args) {
|
|
117
|
+
return [pc.dim(timestamp), prefix, ...args.map(formatArg)].join(' ');
|
|
118
|
+
}
|
|
119
|
+
const noSpacing = { spacing: 0 };
|
|
120
|
+
// Suppress clack terminal output during vitest runs to avoid flooding
|
|
121
|
+
// test output with hundreds of log lines. File logging still works.
|
|
122
|
+
const isVitest = !!process.env['KIMAKI_VITEST'];
|
|
123
|
+
export function createLogger(prefix) {
|
|
124
|
+
const paddedPrefix = padPrefix(prefix);
|
|
125
|
+
const log = (...args) => {
|
|
126
|
+
writeToFile('LOG', prefix, args);
|
|
127
|
+
if (isVitest) {
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
clackLog.message(formatMessage(getTimestamp(), pc.cyan(paddedPrefix), args), {
|
|
131
|
+
...noSpacing,
|
|
132
|
+
// symbol: `|`,
|
|
133
|
+
});
|
|
134
|
+
};
|
|
135
|
+
return {
|
|
136
|
+
log,
|
|
137
|
+
error: (...args) => {
|
|
138
|
+
writeToFile('ERROR', prefix, args);
|
|
139
|
+
if (isVitest) {
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
clackLog.error(formatMessage(getTimestamp(), pc.red(paddedPrefix), args), noSpacing);
|
|
143
|
+
},
|
|
144
|
+
warn: (...args) => {
|
|
145
|
+
writeToFile('WARN', prefix, args);
|
|
146
|
+
if (isVitest) {
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
clackLog.warn(formatMessage(getTimestamp(), pc.yellow(paddedPrefix), args), noSpacing);
|
|
150
|
+
},
|
|
151
|
+
info: (...args) => {
|
|
152
|
+
writeToFile('INFO', prefix, args);
|
|
153
|
+
if (isVitest) {
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
clackLog.info(formatMessage(getTimestamp(), pc.blue(paddedPrefix), args), noSpacing);
|
|
157
|
+
},
|
|
158
|
+
debug: log,
|
|
159
|
+
};
|
|
160
|
+
}
|