kimaki 0.26.0 → 0.28.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 +250 -164
- package/dist/anthropic-account-identity.js +9 -1
- package/dist/anthropic-auth-plugin.js +10 -13
- package/dist/anthropic-auth-plugin.test.js +17 -0
- package/dist/anthropic-auth-state.js +1 -0
- package/dist/bash-tool-schema-plugin.js +32 -0
- package/dist/bash-tool-schema-plugin.test.js +49 -0
- package/dist/bin.js +73 -19
- package/dist/cli-commands/maintenance.js +46 -7
- package/dist/cli-commands/project.js +11 -27
- package/dist/cli-commands/send.js +30 -1
- package/dist/cli-commands/session.js +322 -90
- package/dist/cli-commands/task.js +34 -15
- package/dist/cli-parsing.test.js +83 -4
- package/dist/cli-runner.js +44 -8
- package/dist/cli-runner.test.js +27 -0
- package/dist/cli-send-thread.e2e.test.js +9 -9
- package/dist/cli.js +32 -0
- package/dist/commands/abort.js +5 -1
- package/dist/commands/action-buttons.js +2 -1
- package/dist/commands/add-project.js +4 -7
- package/dist/commands/agent.js +164 -32
- package/dist/commands/ask-question.js +23 -33
- package/dist/commands/ask-question.test.js +43 -1
- package/dist/commands/btw.js +28 -6
- package/dist/commands/file-upload.js +2 -1
- package/dist/commands/fork-subagent.js +2 -4
- package/dist/commands/fork.js +4 -4
- package/dist/commands/last-sessions.js +17 -10
- package/dist/commands/login.js +6 -0
- package/dist/commands/merge-worktree.js +47 -14
- package/dist/commands/model-variant.js +8 -1
- package/dist/commands/model.js +16 -9
- package/dist/commands/multioauth.js +1 -0
- package/dist/commands/new-worktree.js +170 -102
- package/dist/commands/queue.js +3 -2
- package/dist/commands/resume.js +4 -4
- package/dist/commands/session.js +78 -63
- package/dist/commands/user-command.js +6 -8
- package/dist/commands/worktrees.js +5 -13
- package/dist/config.js +16 -1
- package/dist/cpu-profiler.js +211 -0
- package/dist/cpu-profiler.test.js +160 -0
- package/dist/database.js +152 -3
- package/dist/db.js +33 -0
- package/dist/db.test.js +267 -1
- package/dist/default-channel-provisioning.test.js +148 -0
- package/dist/discord-bot.js +517 -488
- package/dist/discord-command-registration.js +25 -10
- package/dist/discord-command-registration.test.js +17 -0
- package/dist/discord-utils.js +97 -5
- package/dist/discord-utils.test.js +151 -1
- package/dist/errors.js +10 -0
- package/dist/exec-async.js +17 -5
- package/dist/external-directory-permissions.e2e.test.js +11 -7
- package/dist/external-directory-project-deny.e2e.test.js +5 -3
- package/dist/external-opencode-sync.js +94 -17
- package/dist/external-opencode-sync.test.js +206 -0
- package/dist/extract-sdk-error-message.test.js +13 -0
- package/dist/file-edit-log.js +197 -0
- package/dist/file-edit-log.test.js +258 -0
- package/dist/format-tables.js +52 -0
- package/dist/format-tables.test.js +93 -0
- package/dist/gateway-proxy-reconnect.e2e.test.js +28 -8
- package/dist/gateway-proxy.e2e.test.js +18 -18
- package/dist/git-worktree-core.js +182 -17
- package/dist/interaction-handler.js +416 -398
- package/dist/ipc-tools-plugin.js +68 -3
- package/dist/kimaki-opencode-plugin-loading.e2e.test.js +122 -58
- package/dist/kimaki-opencode-plugin.js +9 -3
- package/dist/kimaki-workspace-adaptor.js +33 -10
- package/dist/logger.js +11 -1
- package/dist/markdown.js +3 -1
- package/dist/markdown.test.js +11 -3
- package/dist/message-formatting.js +181 -51
- package/dist/message-formatting.test.js +529 -22
- package/dist/message-preprocessing.js +105 -1
- package/dist/oauth-rotation-shared.js +10 -0
- package/dist/openai-auth-plugin.js +1 -0
- package/dist/openai-auth-state.js +1 -0
- package/dist/opencode-command.js +28 -0
- package/dist/opencode-command.test.js +109 -1
- package/dist/opencode-interrupt-plugin.js +4 -0
- package/dist/opencode-interrupt-plugin.test.js +24 -0
- package/dist/opencode.js +82 -15
- package/dist/project-add.test.js +62 -0
- package/dist/question-abort-clears-dropdown.e2e.test.js +89 -0
- package/dist/question-abort-retry-text-before-dropdown.e2e.test.js +150 -0
- package/dist/question-text-before-dropdown.e2e.test.js +52 -0
- package/dist/queue-advanced-abort.e2e.test.js +8 -10
- package/dist/queue-advanced-action-buttons.e2e.test.js +8 -8
- package/dist/queue-advanced-e2e-setup.js +78 -5
- package/dist/queue-advanced-footer.e2e.test.js +60 -269
- package/dist/queue-advanced-model-switch.e2e.test.js +9 -11
- package/dist/queue-advanced-permissions-typing.e2e.test.js +15 -15
- package/dist/queue-advanced-question.e2e.test.js +32 -27
- package/dist/queue-advanced-typing-interrupt.e2e.test.js +10 -12
- package/dist/queue-advanced-typing.e2e.test.js +7 -9
- package/dist/queue-delete-message.e2e.test.js +3 -3
- package/dist/queue-drain-after-interactive-ui.e2e.test.js +5 -5
- package/dist/queue-interrupt-drain.e2e.test.js +122 -12
- package/dist/queue-question-select-drain.e2e.test.js +34 -132
- package/dist/runtime-lifecycle.e2e.test.js +21 -51
- package/dist/schema.js +35 -1
- package/dist/session-handler/event-stream-state.js +391 -50
- package/dist/session-handler/event-stream-state.test.js +736 -8
- package/dist/session-handler/model-utils.js +172 -5
- package/dist/session-handler/model-utils.test.js +445 -0
- package/dist/session-handler/opencode-session-event-log.js +30 -1
- package/dist/session-handler/thread-ingress.test.js +41 -0
- package/dist/session-handler/thread-runtime-state.js +2 -0
- package/dist/session-handler/thread-session-runtime-summary.test.js +121 -0
- package/dist/session-handler/thread-session-runtime.js +574 -299
- package/dist/session-search.js +123 -4
- package/dist/session-search.test.js +248 -1
- package/dist/session-sleep.test.js +224 -0
- package/dist/session-title-rename.test.js +37 -39
- package/dist/sleep.e2e.test.js +494 -0
- package/dist/startup-time.e2e.test.js +2 -1
- package/dist/store.js +3 -0
- package/dist/system-message.js +102 -16
- package/dist/system-message.test.js +192 -15
- package/dist/task-runner.js +96 -5
- package/dist/task-schedule.js +87 -1
- package/dist/task-schedule.test.js +149 -1
- package/dist/test-utils.js +17 -9
- package/dist/thinking-utils.js +10 -0
- package/dist/thinking-utils.test.js +35 -0
- package/dist/thread-message-queue.e2e.test.js +65 -213
- package/dist/undo-redo.e2e.test.js +17 -9
- package/dist/voice-attachment.js +23 -1
- package/dist/voice-handler.js +21 -4
- package/dist/voice-message.e2e.test.js +144 -30
- package/dist/voice-routing-input.e2e.test.js +195 -0
- package/dist/voice-routing-question.e2e.test.js +125 -0
- package/dist/voice.js +28 -8
- package/dist/voice.test.js +96 -30
- package/dist/worktree-clone-isolation.e2e.test.js +188 -0
- package/dist/worktree-lifecycle.e2e.test.js +55 -36
- package/dist/worktrees.js +226 -443
- package/dist/worktrees.test.js +435 -58
- package/dist/xai-auth-plugin.js +1 -0
- package/dist/xai-auth-state.js +1 -0
- package/package.json +8 -6
- package/skills/bump-holocron-consumers/SKILL.md +137 -0
- package/skills/critique/SKILL.md +4 -4
- package/skills/errore/SKILL.md +60 -218
- package/skills/goke/SKILL.md +150 -10
- package/skills/holocron/SKILL.md +154 -17
- package/skills/npm-package/SKILL.md +55 -35
- package/skills/profano/SKILL.md +17 -2
- package/skills/spiceflow/SKILL.md +39 -3
- package/skills/strada/SKILL.md +39 -0
- package/skills/zele/SKILL.md +19 -0
- package/src/agent-model.e2e.test.ts +287 -186
- package/src/anthropic-account-identity.ts +20 -1
- package/src/anthropic-auth-plugin.test.ts +19 -0
- package/src/anthropic-auth-plugin.ts +12 -16
- package/src/anthropic-auth-state.ts +1 -0
- package/src/bash-tool-schema-plugin.test.ts +55 -0
- package/src/bash-tool-schema-plugin.ts +46 -0
- package/src/bin.ts +78 -26
- package/src/cli-commands/maintenance.ts +59 -13
- package/src/cli-commands/project.ts +14 -35
- package/src/cli-commands/send.ts +31 -1
- package/src/cli-commands/session.ts +374 -114
- package/src/cli-commands/task.ts +34 -15
- package/src/cli-parsing.test.ts +103 -4
- package/src/cli-runner.test.ts +49 -0
- package/src/cli-runner.ts +44 -7
- package/src/cli-send-thread.e2e.test.ts +9 -11
- package/src/cli.ts +53 -0
- package/src/commands/abort.ts +6 -1
- package/src/commands/action-buttons.ts +2 -1
- package/src/commands/add-project.ts +4 -8
- package/src/commands/agent.ts +205 -29
- package/src/commands/ask-question.test.ts +48 -1
- package/src/commands/ask-question.ts +23 -33
- package/src/commands/btw.ts +40 -5
- package/src/commands/file-upload.ts +2 -1
- package/src/commands/fork-subagent.ts +2 -3
- package/src/commands/fork.ts +4 -3
- package/src/commands/last-sessions.ts +10 -4
- package/src/commands/login.ts +6 -0
- package/src/commands/merge-worktree.ts +54 -16
- package/src/commands/model-variant.ts +9 -1
- package/src/commands/model.ts +20 -9
- package/src/commands/multioauth.ts +1 -0
- package/src/commands/new-worktree.ts +138 -55
- package/src/commands/queue.ts +3 -2
- package/src/commands/resume.ts +4 -3
- package/src/commands/session.ts +94 -70
- package/src/commands/user-command.ts +6 -6
- package/src/commands/worktrees.ts +5 -12
- package/src/config.ts +18 -1
- package/src/cpu-profiler.test.ts +183 -0
- package/src/cpu-profiler.ts +293 -0
- package/src/database.ts +235 -3
- package/src/db.test.ts +293 -0
- package/src/db.ts +38 -0
- package/src/default-channel-provisioning.test.ts +155 -0
- package/src/discord-bot.ts +78 -36
- package/src/discord-command-registration.test.ts +20 -0
- package/src/discord-command-registration.ts +42 -12
- package/src/discord-utils.test.ts +176 -0
- package/src/discord-utils.ts +152 -7
- package/src/errors.ts +12 -0
- package/src/exec-async.ts +20 -11
- package/src/external-directory-permissions.e2e.test.ts +11 -7
- package/src/external-directory-project-deny.e2e.test.ts +5 -3
- package/src/external-opencode-sync.test.ts +248 -0
- package/src/external-opencode-sync.ts +122 -19
- package/src/extract-sdk-error-message.test.ts +18 -0
- package/src/file-edit-log.test.ts +287 -0
- package/src/file-edit-log.ts +283 -0
- package/src/format-tables.test.ts +101 -0
- package/src/format-tables.ts +75 -0
- package/src/gateway-proxy-reconnect.e2e.test.ts +38 -8
- package/src/gateway-proxy.e2e.test.ts +18 -17
- package/src/git-worktree-core.ts +247 -20
- package/src/interaction-handler.ts +36 -13
- package/src/ipc-tools-plugin.ts +82 -5
- package/src/kimaki-opencode-plugin-loading.e2e.test.ts +188 -97
- package/src/kimaki-opencode-plugin.ts +9 -3
- package/src/kimaki-workspace-adaptor.ts +40 -11
- package/src/logger.ts +14 -4
- package/src/markdown.test.ts +13 -3
- package/src/markdown.ts +4 -1
- package/src/message-formatting.test.ts +672 -25
- package/src/message-formatting.ts +242 -53
- package/src/message-preprocessing.ts +116 -1
- package/src/oauth-rotation-shared.ts +10 -0
- package/src/openai-auth-plugin.ts +1 -0
- package/src/openai-auth-state.ts +1 -0
- package/src/opencode-command.test.ts +140 -0
- package/src/opencode-command.ts +38 -0
- package/src/opencode-interrupt-plugin.test.ts +32 -0
- package/src/opencode-interrupt-plugin.ts +3 -0
- package/src/opencode.ts +115 -15
- package/src/project-add.test.ts +80 -0
- package/src/question-abort-clears-dropdown.e2e.test.ts +109 -0
- package/src/question-abort-retry-text-before-dropdown.e2e.test.ts +172 -0
- package/src/question-text-before-dropdown.e2e.test.ts +67 -0
- package/src/queue-advanced-abort.e2e.test.ts +8 -9
- package/src/queue-advanced-action-buttons.e2e.test.ts +8 -8
- package/src/queue-advanced-e2e-setup.ts +83 -4
- package/src/queue-advanced-footer.e2e.test.ts +67 -315
- package/src/queue-advanced-model-switch.e2e.test.ts +9 -10
- package/src/queue-advanced-permissions-typing.e2e.test.ts +15 -15
- package/src/queue-advanced-question.e2e.test.ts +36 -30
- package/src/queue-advanced-typing-interrupt.e2e.test.ts +11 -11
- package/src/queue-advanced-typing.e2e.test.ts +8 -8
- package/src/queue-delete-message.e2e.test.ts +3 -3
- package/src/queue-drain-after-interactive-ui.e2e.test.ts +5 -5
- package/src/queue-interrupt-drain.e2e.test.ts +133 -12
- package/src/queue-question-select-drain.e2e.test.ts +36 -161
- package/src/runtime-lifecycle.e2e.test.ts +21 -54
- package/src/schema.sql +15 -2
- package/src/schema.ts +37 -1
- package/src/session-handler/event-stream-state.test.ts +790 -7
- package/src/session-handler/event-stream-state.ts +568 -53
- package/src/session-handler/model-utils.test.ts +537 -0
- package/src/session-handler/model-utils.ts +281 -5
- package/src/session-handler/opencode-session-event-log.ts +31 -1
- package/src/session-handler/thread-ingress.test.ts +54 -0
- package/src/session-handler/thread-runtime-state.ts +3 -0
- package/src/session-handler/thread-session-runtime-summary.test.ts +169 -0
- package/src/session-handler/thread-session-runtime.ts +712 -358
- package/src/session-search.test.ts +281 -0
- package/src/session-search.ts +241 -7
- package/src/session-sleep.test.ts +294 -0
- package/src/session-title-rename.test.ts +54 -48
- package/src/sleep.e2e.test.ts +565 -0
- package/src/startup-time.e2e.test.ts +5 -1
- package/src/store.ts +22 -0
- package/src/system-message.test.ts +226 -15
- package/src/system-message.ts +130 -16
- package/src/task-runner.ts +138 -3
- package/src/task-schedule.test.ts +170 -0
- package/src/task-schedule.ts +141 -1
- package/src/test-utils.ts +21 -10
- package/src/thinking-utils.test.ts +46 -0
- package/src/thinking-utils.ts +21 -0
- package/src/thread-message-queue.e2e.test.ts +65 -245
- package/src/undo-redo.e2e.test.ts +17 -9
- package/src/voice-attachment.ts +29 -1
- package/src/voice-handler.ts +23 -4
- package/src/voice-message.e2e.test.ts +149 -29
- package/src/voice-routing-input.e2e.test.ts +203 -0
- package/src/voice-routing-question.e2e.test.ts +131 -0
- package/src/voice.test.ts +107 -29
- package/src/voice.ts +34 -6
- package/src/worktree-clone-isolation.e2e.test.ts +213 -0
- package/src/worktree-lifecycle.e2e.test.ts +55 -35
- package/src/worktrees.test.ts +505 -64
- package/src/worktrees.ts +257 -603
- package/src/xai-auth-plugin.ts +1 -0
- package/src/xai-auth-state.ts +1 -0
- package/dist/message-finish-field.e2e.test.js +0 -169
- package/dist/subagent-rate-limit-plugin.js +0 -184
- package/src/message-finish-field.e2e.test.ts +0 -196
- package/src/subagent-rate-limit-plugin.ts +0 -228
package/dist/cli-parsing.test.js
CHANGED
|
@@ -1,16 +1,19 @@
|
|
|
1
1
|
// Regression tests for CLI argument parsing around Discord ID string preservation.
|
|
2
2
|
import { describe, expect, test } from 'vitest';
|
|
3
3
|
import { execAsync } from './exec-async.js';
|
|
4
|
+
import maintenanceCommands from './cli-commands/maintenance.js';
|
|
4
5
|
async function parseWithGoke(argv) {
|
|
5
6
|
const script = [
|
|
6
7
|
"import { goke } from 'goke'",
|
|
7
8
|
'const cli = goke(\'kimaki\')',
|
|
8
9
|
"cli.command('send', 'Send a message').option('-c, --channel <channelId>', 'Discord channel ID').option('--thread <threadId>', 'Thread ID').option('--session <sessionId>', 'Session ID').option('--send-at <schedule>', 'Schedule')",
|
|
9
10
|
"cli.command('session archive <threadId>', 'Archive a thread')",
|
|
10
|
-
"cli.command('session
|
|
11
|
+
"cli.command('session title <title>', 'Update title').option('--session <sessionId>', 'Session ID').option('--thread <threadId>', 'Thread ID')",
|
|
12
|
+
"cli.command('session search <query>', 'Search sessions').option('--channel <channelId>', 'Discord channel ID').option('--project <path>', 'Project path').option('--all', 'Search all registered projects').option('--days <n>', 'Only search recent sessions')",
|
|
11
13
|
"cli.command('session export-events-jsonl', 'Export in-memory events to JSONL').option('--session <sessionId>', 'Session ID').option('--out <file>', 'Output path')",
|
|
12
14
|
"cli.command('add-project', 'Add a project').option('-g, --guild <guildId>', 'Discord guild/server ID')",
|
|
13
15
|
"cli.command('task delete <id>', 'Delete task')",
|
|
16
|
+
"cli.command('task edit <id>', 'Edit task').option('-u, --user <user>', 'Discord user')",
|
|
14
17
|
"cli.command('multioauth anthropic list', 'List stored Anthropic accounts')",
|
|
15
18
|
"cli.command('multioauth anthropic remove <indexOrEmail>', 'Remove stored Anthropic account')",
|
|
16
19
|
"cli.command('multioauth openai list', 'List stored OpenAI accounts')",
|
|
@@ -47,7 +50,7 @@ async function parseRootBotOptions(argv) {
|
|
|
47
50
|
const script = [
|
|
48
51
|
"import { goke } from 'goke'",
|
|
49
52
|
'const cli = goke(\'kimaki\')',
|
|
50
|
-
"cli.command('', 'bot').option('--no-analytics', 'Disable analytics')",
|
|
53
|
+
"cli.command('', 'bot').option('--no-analytics', 'Disable analytics').option('--skip-footer-mentions', 'Do not mention the thread creator in final footers')",
|
|
51
54
|
`const result = await cli.parse(${JSON.stringify(argv)}, { run: false })`,
|
|
52
55
|
'process.stdout.write(JSON.stringify({ args: result.args, options: result.options }))',
|
|
53
56
|
].join(';');
|
|
@@ -78,6 +81,23 @@ describe('goke CLI ID parsing', () => {
|
|
|
78
81
|
expect(result.options.guild).toBe(guildId);
|
|
79
82
|
expect(typeof result.options.guild).toBe('string');
|
|
80
83
|
});
|
|
84
|
+
test('keeps session title and session ID as strings', async () => {
|
|
85
|
+
const sessionId = '001111222233334444';
|
|
86
|
+
const title = 'Fix queue draining';
|
|
87
|
+
const result = await parseWithGoke([
|
|
88
|
+
'node',
|
|
89
|
+
'kimaki',
|
|
90
|
+
'session',
|
|
91
|
+
'title',
|
|
92
|
+
title,
|
|
93
|
+
'--session',
|
|
94
|
+
sessionId,
|
|
95
|
+
]);
|
|
96
|
+
expect(result.args[0]).toBe(title);
|
|
97
|
+
expect(typeof result.args[0]).toBe('string');
|
|
98
|
+
expect(result.options.session).toBe(sessionId);
|
|
99
|
+
expect(typeof result.options.session).toBe('string');
|
|
100
|
+
});
|
|
81
101
|
test('keeps session archive thread ID as string', async () => {
|
|
82
102
|
const threadId = '0098765432109876543';
|
|
83
103
|
const result = await parseWithGoke(['node', 'kimaki', 'session', 'archive', threadId]);
|
|
@@ -93,6 +113,15 @@ describe('goke CLI ID parsing', () => {
|
|
|
93
113
|
expect(result.options.channel).toBe(channelId);
|
|
94
114
|
expect(typeof result.options.channel).toBe('string');
|
|
95
115
|
});
|
|
116
|
+
test('parses session search --all as a boolean', async () => {
|
|
117
|
+
const result = await parseWithGoke(['node', 'kimaki', 'session', 'search', 'auth timeout', '--all']);
|
|
118
|
+
expect(result.args[0]).toBe('auth timeout');
|
|
119
|
+
expect(result.options.all).toBe(true);
|
|
120
|
+
});
|
|
121
|
+
test('parses session search --days as a string', async () => {
|
|
122
|
+
const result = await parseWithGoke(['node', 'kimaki', 'session', 'search', 'auth timeout', '--days', '0']);
|
|
123
|
+
expect(result.options.days).toBe('0');
|
|
124
|
+
});
|
|
96
125
|
test('keeps session export options as strings', async () => {
|
|
97
126
|
const sessionId = '001111222233334444';
|
|
98
127
|
const outPath = './tmp/session-events.jsonl';
|
|
@@ -123,6 +152,19 @@ describe('goke CLI ID parsing', () => {
|
|
|
123
152
|
expect(result.args[0]).toBe(taskId);
|
|
124
153
|
expect(typeof result.args[0]).toBe('string');
|
|
125
154
|
});
|
|
155
|
+
test('parses empty --user on task edit so the stored user can be cleared', async () => {
|
|
156
|
+
const result = await parseWithGoke([
|
|
157
|
+
'node',
|
|
158
|
+
'kimaki',
|
|
159
|
+
'task',
|
|
160
|
+
'edit',
|
|
161
|
+
'11',
|
|
162
|
+
'--user',
|
|
163
|
+
'',
|
|
164
|
+
]);
|
|
165
|
+
expect(result.args[0]).toBe('11');
|
|
166
|
+
expect(result.options.user).toBe('');
|
|
167
|
+
});
|
|
126
168
|
test('multioauth account remove parses index and email as strings', async () => {
|
|
127
169
|
const indexResult = await parseWithGoke(['node', 'kimaki', 'multioauth', 'anthropic', 'remove', '2']);
|
|
128
170
|
const emailResult = await parseWithGoke(['node', 'kimaki', 'multioauth', 'openai', 'remove', 'user@example.com']);
|
|
@@ -136,12 +178,49 @@ describe('goke CLI ID parsing', () => {
|
|
|
136
178
|
expect(stdout).toContain('send');
|
|
137
179
|
expect(stdout).toContain('multioauth');
|
|
138
180
|
});
|
|
139
|
-
test('
|
|
181
|
+
test('merge-worktree parses strategy and target branch', async () => {
|
|
182
|
+
const result = await maintenanceCommands.parse([
|
|
183
|
+
'node',
|
|
184
|
+
'kimaki',
|
|
185
|
+
'merge-worktree',
|
|
186
|
+
'--strategy',
|
|
187
|
+
'squash',
|
|
188
|
+
'--target-branch',
|
|
189
|
+
'release',
|
|
190
|
+
'--thread',
|
|
191
|
+
'123456789012345678',
|
|
192
|
+
'--thread-name',
|
|
193
|
+
'feature work',
|
|
194
|
+
], { run: false });
|
|
195
|
+
expect({
|
|
196
|
+
command: maintenanceCommands.matchedCommandName,
|
|
197
|
+
strategy: result.options.strategy,
|
|
198
|
+
targetBranch: result.options.targetBranch,
|
|
199
|
+
thread: result.options.thread,
|
|
200
|
+
threadName: result.options.threadName,
|
|
201
|
+
}).toMatchInlineSnapshot(`
|
|
202
|
+
{
|
|
203
|
+
"command": "merge-worktree",
|
|
204
|
+
"strategy": "squash",
|
|
205
|
+
"targetBranch": "release",
|
|
206
|
+
"thread": "123456789012345678",
|
|
207
|
+
"threadName": "feature work",
|
|
208
|
+
}
|
|
209
|
+
`);
|
|
210
|
+
});
|
|
211
|
+
test('parses root bot boolean flags', async () => {
|
|
140
212
|
const result = await parseRootBotOptions([
|
|
141
213
|
'node',
|
|
142
214
|
'kimaki',
|
|
143
215
|
'--no-analytics',
|
|
216
|
+
'--skip-footer-mentions',
|
|
144
217
|
]);
|
|
145
|
-
expect(result.options
|
|
218
|
+
expect(result.options).toMatchInlineSnapshot(`
|
|
219
|
+
{
|
|
220
|
+
"--": [],
|
|
221
|
+
"noAnalytics": true,
|
|
222
|
+
"skipFooterMentions": true,
|
|
223
|
+
}
|
|
224
|
+
`);
|
|
146
225
|
});
|
|
147
226
|
});
|
package/dist/cli-runner.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// Keeps cli.ts focused on command composition while preserving the bot onboarding flow.
|
|
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
|
-
import { getChannelsWithDescriptions, createDiscordClient, initDatabase, getChannelDirectory, startDiscordBot, initializeOpencodeForDirectory, createProjectChannels, createDefaultKimakiChannel, } from './discord-bot.js';
|
|
5
|
+
import { getChannelsWithDescriptions, createDiscordClient, initDatabase, getChannelDirectory, startDiscordBot, initializeOpencodeForDirectory, assertCompatibleOpencodeVersion, createProjectChannels, createDefaultKimakiChannel, } from './discord-bot.js';
|
|
6
6
|
import { getBotTokenWithMode, ensureServiceAuthToken, setBotToken, setBotMode, setChannelDirectory, findChannelsByDirectory, getDb, } from './database.js';
|
|
7
7
|
import * as orm from 'drizzle-orm';
|
|
8
8
|
import * as dbSchema from './schema.js';
|
|
@@ -312,6 +312,10 @@ export function formatTaskScheduleLine(schedule) {
|
|
|
312
312
|
return `cron "${schedule.cronExpr}" (${schedule.timezone}) next ${schedule.nextRunAt.toISOString()}`;
|
|
313
313
|
}
|
|
314
314
|
export const EXIT_NO_RESTART = 64;
|
|
315
|
+
// Temporary failure (sysexits EX_TEMPFAIL). Wrapper retries forever with
|
|
316
|
+
// backoff and does not count this toward the crash-loop detector. Used when
|
|
317
|
+
// Discord login fails because the network is down.
|
|
318
|
+
export const EXIT_TEMPFAIL = 75;
|
|
315
319
|
export function isGuildMemberSearchResult(value) {
|
|
316
320
|
if (!value) {
|
|
317
321
|
return false;
|
|
@@ -378,12 +382,28 @@ const TRANSIENT_ERROR_CODES = new Set([
|
|
|
378
382
|
'DEPTH_ZERO_SELF_SIGNED_CERT',
|
|
379
383
|
'SELF_SIGNED_CERT_IN_CHAIN',
|
|
380
384
|
'ERR_TLS_CERT_ALTNAME_INVALID',
|
|
385
|
+
// undici fetch/WebSocket connect failures when the network is down.
|
|
386
|
+
// Without these, Discord login exits EXIT_NO_RESTART and the wrapper never
|
|
387
|
+
// comes back after a connect timeout to discord-gateway.kimaki.dev.
|
|
388
|
+
'UND_ERR_CONNECT_TIMEOUT',
|
|
389
|
+
'UND_ERR_HEADERS_TIMEOUT',
|
|
390
|
+
'UND_ERR_BODY_TIMEOUT',
|
|
391
|
+
'UND_ERR_SOCKET',
|
|
392
|
+
]);
|
|
393
|
+
const TRANSIENT_ERROR_NAMES = new Set([
|
|
394
|
+
'ConnectTimeoutError',
|
|
395
|
+
'HeadersTimeoutError',
|
|
396
|
+
'BodyTimeoutError',
|
|
397
|
+
'SocketError',
|
|
381
398
|
]);
|
|
382
399
|
const TRANSIENT_ERROR_MESSAGE_PATTERNS = [
|
|
383
400
|
/unable to verify the first certificate/i,
|
|
384
401
|
/certificate has expired/i,
|
|
385
402
|
/self[- ]signed certificate/i,
|
|
386
403
|
/unable to get local issuer certificate/i,
|
|
404
|
+
/connect timeout error/i,
|
|
405
|
+
/headers timeout error/i,
|
|
406
|
+
/body timeout error/i,
|
|
387
407
|
];
|
|
388
408
|
export function isTransientNetworkError(error) {
|
|
389
409
|
if (!(error instanceof Error))
|
|
@@ -391,6 +411,8 @@ export function isTransientNetworkError(error) {
|
|
|
391
411
|
const code = error.code;
|
|
392
412
|
if (code && TRANSIENT_ERROR_CODES.has(code))
|
|
393
413
|
return true;
|
|
414
|
+
if (TRANSIENT_ERROR_NAMES.has(error.name))
|
|
415
|
+
return true;
|
|
394
416
|
// Fallback when code is stripped by wrappers (discord.js sometimes rethrows by message only).
|
|
395
417
|
if (TRANSIENT_ERROR_MESSAGE_PATTERNS.some((pattern) => pattern.test(error.message))) {
|
|
396
418
|
return true;
|
|
@@ -745,16 +767,23 @@ export function showReadyMessage({ kimakiChannels, createdChannels, }) {
|
|
|
745
767
|
note('Leave this process running to keep the bot active.\n\nIf you close this process or restart your machine, run `npx kimaki` again to start the bot.', '⚠️ Keep Running');
|
|
746
768
|
}
|
|
747
769
|
/**
|
|
748
|
-
* Create
|
|
770
|
+
* Create default channels in locally configured guilds, or proxy-authorized gateway guilds.
|
|
749
771
|
* Idempotent: skips guilds that already have the channel.
|
|
750
772
|
* Extracted so both the interactive and headless startup paths share the same logic.
|
|
751
773
|
*/
|
|
752
774
|
export async function ensureDefaultChannelsWithWelcome({ guilds, discordClient, appId, isGatewayMode, installerDiscordUserId, }) {
|
|
753
775
|
if (process.env['KIMAKI_NO_DEFAULT_CHANNEL'] === '1')
|
|
754
776
|
return [];
|
|
777
|
+
const localMappings = isGatewayMode ? [] : await findChannelsByDirectory({});
|
|
755
778
|
const created = [];
|
|
756
779
|
for (const guild of guilds) {
|
|
757
780
|
try {
|
|
781
|
+
if (!isGatewayMode) {
|
|
782
|
+
// Match live channel IDs: older local mappings have no guild_id.
|
|
783
|
+
const channels = await guild.channels.fetch();
|
|
784
|
+
if (!localMappings.some((row) => channels.has(row.channel_id)))
|
|
785
|
+
continue;
|
|
786
|
+
}
|
|
758
787
|
const result = await createDefaultKimakiChannel({
|
|
759
788
|
guild,
|
|
760
789
|
botName: discordClient.user?.username,
|
|
@@ -1126,6 +1155,11 @@ export async function run({ restartOnboarding, addChannels, useWorktrees, enable
|
|
|
1126
1155
|
possiblePathsWindows: ['~\\.bun\\bin\\bun.exe'],
|
|
1127
1156
|
}),
|
|
1128
1157
|
]);
|
|
1158
|
+
const opencodeVersionCheck = await assertCompatibleOpencodeVersion();
|
|
1159
|
+
if (opencodeVersionCheck instanceof Error) {
|
|
1160
|
+
cliLogger.error(opencodeVersionCheck.message);
|
|
1161
|
+
process.exit(EXIT_NO_RESTART);
|
|
1162
|
+
}
|
|
1129
1163
|
if (store.getState().autoUpgradeEnabled) {
|
|
1130
1164
|
void backgroundUpgradeKimaki();
|
|
1131
1165
|
}
|
|
@@ -1271,12 +1305,14 @@ export async function run({ restartOnboarding, addChannels, useWorktrees, enable
|
|
|
1271
1305
|
catch (error) {
|
|
1272
1306
|
cliLogger.log('Failed to connect to Discord', discordClient.ws.gateway);
|
|
1273
1307
|
cliLogger.error('Error: ' + (error instanceof Error ? error.stack : String(error)));
|
|
1274
|
-
// Transient network errors (DNS down, gateway unreachable
|
|
1275
|
-
// the bin.ts wrapper to restart us after a delay.
|
|
1276
|
-
//
|
|
1308
|
+
// Transient network errors (DNS down, gateway unreachable, connect
|
|
1309
|
+
// timeout) should allow the bin.ts wrapper to restart us after a delay.
|
|
1310
|
+
// EXIT_TEMPFAIL skips the crash-loop detector so a long outage cannot
|
|
1311
|
+
// permanently stop the bot. Only truly fatal errors (bad token, invalid
|
|
1312
|
+
// intent, etc.) should use EXIT_NO_RESTART.
|
|
1277
1313
|
if (isTransientNetworkError(error)) {
|
|
1278
1314
|
cliLogger.error('Transient network error, exiting for wrapper restart...');
|
|
1279
|
-
process.exit(
|
|
1315
|
+
process.exit(EXIT_TEMPFAIL);
|
|
1280
1316
|
}
|
|
1281
1317
|
process.exit(EXIT_NO_RESTART);
|
|
1282
1318
|
}
|
|
@@ -1327,7 +1363,7 @@ export async function run({ restartOnboarding, addChannels, useWorktrees, enable
|
|
|
1327
1363
|
catch (error) {
|
|
1328
1364
|
cliLogger.warn('Background channel sync failed:', error instanceof Error ? error.stack : String(error));
|
|
1329
1365
|
}
|
|
1330
|
-
// Create default
|
|
1366
|
+
// Create default channels only in locally configured or gateway-authorized guilds.
|
|
1331
1367
|
// Runs after channel sync so existing channels are detected correctly.
|
|
1332
1368
|
try {
|
|
1333
1369
|
await ensureDefaultChannelsWithWelcome({
|
|
@@ -1482,7 +1518,7 @@ export async function run({ restartOnboarding, addChannels, useWorktrees, enable
|
|
|
1482
1518
|
}
|
|
1483
1519
|
}
|
|
1484
1520
|
// Create default kimaki channel for general-purpose tasks.
|
|
1485
|
-
//
|
|
1521
|
+
// Only locally configured or gateway-authorized guilds are eligible.
|
|
1486
1522
|
const defaultChannelResults = await ensureDefaultChannelsWithWelcome({
|
|
1487
1523
|
guilds,
|
|
1488
1524
|
discordClient,
|
package/dist/cli-runner.test.js
CHANGED
|
@@ -56,4 +56,31 @@ describe('isTransientNetworkError', () => {
|
|
|
56
56
|
});
|
|
57
57
|
expect(isTransientNetworkError(error)).toBe(true);
|
|
58
58
|
});
|
|
59
|
+
test('treats undici connect timeouts as transient', () => {
|
|
60
|
+
const error = Object.assign(new Error('Connect Timeout Error (attempted address: discord-gateway.kimaki.dev:443, timeout: 10000ms)'), {
|
|
61
|
+
name: 'ConnectTimeoutError',
|
|
62
|
+
code: 'UND_ERR_CONNECT_TIMEOUT',
|
|
63
|
+
});
|
|
64
|
+
expect(isTransientNetworkError(error)).toBe(true);
|
|
65
|
+
});
|
|
66
|
+
test('matches undici connect timeouts by message when code is missing', () => {
|
|
67
|
+
expect(isTransientNetworkError(new Error('Connect Timeout Error (attempted address: discord-gateway.kimaki.dev:443, timeout: 10000ms)'))).toBe(true);
|
|
68
|
+
});
|
|
69
|
+
test('walks cause chains for nested undici connect timeouts', () => {
|
|
70
|
+
const cause = Object.assign(new Error('Connect Timeout Error (attempted address: discord-gateway.kimaki.dev:443, timeout: 10000ms)'), {
|
|
71
|
+
name: 'ConnectTimeoutError',
|
|
72
|
+
code: 'UND_ERR_CONNECT_TIMEOUT',
|
|
73
|
+
});
|
|
74
|
+
expect(isTransientNetworkError(new Error('Failed to connect to Discord', { cause }))).toBe(true);
|
|
75
|
+
});
|
|
76
|
+
test('treats other undici timeout and socket codes as transient', () => {
|
|
77
|
+
for (const [name, code] of [
|
|
78
|
+
['HeadersTimeoutError', 'UND_ERR_HEADERS_TIMEOUT'],
|
|
79
|
+
['BodyTimeoutError', 'UND_ERR_BODY_TIMEOUT'],
|
|
80
|
+
['SocketError', 'UND_ERR_SOCKET'],
|
|
81
|
+
]) {
|
|
82
|
+
const error = Object.assign(new Error(name), { name, code });
|
|
83
|
+
expect(isTransientNetworkError(error)).toBe(true);
|
|
84
|
+
}
|
|
85
|
+
});
|
|
59
86
|
});
|
|
@@ -319,9 +319,9 @@ describe('kimaki send --channel thread creation', () => {
|
|
|
319
319
|
"--- from: user (cli-send-tester)
|
|
320
320
|
thread empty content seed
|
|
321
321
|
--- from: assistant (TestBot)
|
|
322
|
-
*using deterministic-provider/deterministic-v2*
|
|
323
|
-
|
|
324
|
-
*project ⋅ main ⋅
|
|
322
|
+
> *using deterministic-provider/deterministic-v2*
|
|
323
|
+
> caught-by-model
|
|
324
|
+
> *project ⋅ main ⋅ <1s ⋅ 0% ⋅ deterministic-v2* <@200000000000000830>
|
|
325
325
|
--- from: user (cli-send-tester)
|
|
326
326
|
--- from: assistant (TestBot)
|
|
327
327
|
I can see you sent a message, but Discord did not include its text.
|
|
@@ -436,13 +436,13 @@ describe('kimaki send --channel thread creation', () => {
|
|
|
436
436
|
}));
|
|
437
437
|
// Add test user to thread
|
|
438
438
|
await botClient.rest.put(Routes.threadMembers(threadData.id, TEST_USER_ID));
|
|
439
|
-
// Wait for the bot to reply
|
|
440
|
-
//
|
|
439
|
+
// Wait for the bot to reply (proves ThreadCreate handler picked up
|
|
440
|
+
// the starter message and started a session)
|
|
441
441
|
await waitForBotMessageContaining({
|
|
442
442
|
discord,
|
|
443
443
|
threadId: threadData.id,
|
|
444
444
|
userId: discord.botUserId,
|
|
445
|
-
text: '
|
|
445
|
+
text: 'ok',
|
|
446
446
|
timeout: 4_000,
|
|
447
447
|
});
|
|
448
448
|
// Wait for footer message (proves session completed successfully)
|
|
@@ -450,7 +450,7 @@ describe('kimaki send --channel thread creation', () => {
|
|
|
450
450
|
discord,
|
|
451
451
|
threadId: threadData.id,
|
|
452
452
|
timeout: 4_000,
|
|
453
|
-
afterMessageIncludes: '
|
|
453
|
+
afterMessageIncludes: 'ok',
|
|
454
454
|
afterAuthorId: discord.botUserId,
|
|
455
455
|
});
|
|
456
456
|
// Verify no DiscordAPIError[160004] or other errors in the thread.
|
|
@@ -461,9 +461,9 @@ describe('kimaki send --channel thread creation', () => {
|
|
|
461
461
|
return m.content.includes('Error:') || m.content.includes('160004');
|
|
462
462
|
});
|
|
463
463
|
expect(errorMessages).toHaveLength(0);
|
|
464
|
-
// Verify at least one
|
|
464
|
+
// Verify at least one reply exists (session produced output)
|
|
465
465
|
const botReplies = messages.filter((m) => {
|
|
466
|
-
return
|
|
466
|
+
return m.author.id === discord.botUserId;
|
|
467
467
|
});
|
|
468
468
|
expect(botReplies.length).toBeGreaterThanOrEqual(1);
|
|
469
469
|
}, 15_000);
|
package/dist/cli.js
CHANGED
|
@@ -11,6 +11,7 @@ import { createLogger, formatErrorWithStack, initLogFile, LogPrefix } from './lo
|
|
|
11
11
|
import { setDataDir, setProjectsDir, getDataDir, getProjectsDir, } from './config.js';
|
|
12
12
|
import { getCurrentVersion } from './upgrade.js';
|
|
13
13
|
import { store } from './store.js';
|
|
14
|
+
import { publicOpencodeBindRequiresPassword } from './opencode.js';
|
|
14
15
|
import multioauthCommands from './commands/multioauth.js';
|
|
15
16
|
import botCommands from './cli-commands/bot.js';
|
|
16
17
|
import maintenanceCommands from './cli-commands/maintenance.js';
|
|
@@ -37,6 +38,7 @@ cli
|
|
|
37
38
|
.option('--verbosity <level>', 'Default verbosity for all channels (tools_and_text, text_and_essential_tools, or text_only)')
|
|
38
39
|
.option('--mention-mode', 'Bot only responds when @mentioned (default for all channels)')
|
|
39
40
|
.option('--no-critique', 'Disable automatic diff upload to critique.work in system prompts')
|
|
41
|
+
.option('--skip-footer-mentions', 'Do not mention the thread creator in final session footers')
|
|
40
42
|
.option('--auto-restart', 'Automatically restart the bot on crash or OOM kill')
|
|
41
43
|
.option('--allow-all-users', 'Allow all Discord users to start sessions without needing Kimaki role or admin permissions (no-kimaki role still blocks)')
|
|
42
44
|
.option('--restrict-directories', 'Only allow the agent to access the session working directory and a few known-safe paths. Any other folder asks for permission. By default every directory is allowed and you protect folders with deny/ask rules in opencode.json')
|
|
@@ -44,6 +46,8 @@ cli
|
|
|
44
46
|
.option('--disable-sync', 'Disable background sync of external OpenCode sessions into Discord')
|
|
45
47
|
.option('--no-analytics', 'Disable anonymous product analytics (Strada). Same as KIMAKI_STRADA_ENABLED=0')
|
|
46
48
|
.option('--no-auto-upgrade', 'Disable background auto-upgrade on startup')
|
|
49
|
+
.option('--opencode-hostname <host>', 'Hostname the OpenCode server listens on (default: 127.0.0.1). Use 0.0.0.0 to expose it on a VPS')
|
|
50
|
+
.option('--opencode-port <port>', 'Port the OpenCode server listens on (default: a random free port)')
|
|
47
51
|
.option('--gateway', 'Force gateway mode (use the gateway Kimaki bot instead of a self-hosted bot)')
|
|
48
52
|
.option('--gateway-callback-url <url>', 'After gateway OAuth install, redirect to this URL instead of the default success page (appends ?guild_id=<id>)')
|
|
49
53
|
.option('--allow-mention <type>', z
|
|
@@ -149,12 +153,29 @@ cli
|
|
|
149
153
|
}
|
|
150
154
|
return parsed * 60_000;
|
|
151
155
|
})();
|
|
156
|
+
const opencodeHostname = options.opencodeHostname?.trim() || undefined;
|
|
157
|
+
const opencodePort = (() => {
|
|
158
|
+
if (!options.opencodePort)
|
|
159
|
+
return undefined;
|
|
160
|
+
const parsed = Number(options.opencodePort);
|
|
161
|
+
if (!Number.isInteger(parsed) || parsed < 1 || parsed > 65535) {
|
|
162
|
+
cliLogger.error(`Invalid --opencode-port: ${options.opencodePort}. Must be an integer between 1 and 65535.`);
|
|
163
|
+
process.exit(EXIT_NO_RESTART);
|
|
164
|
+
}
|
|
165
|
+
return parsed;
|
|
166
|
+
})();
|
|
167
|
+
if (publicOpencodeBindRequiresPassword({ hostname: opencodeHostname }) &&
|
|
168
|
+
!process.env.OPENCODE_SERVER_PASSWORD) {
|
|
169
|
+
cliLogger.error(`OPENCODE_SERVER_PASSWORD is required when --opencode-hostname is ${opencodeHostname}. Binding the OpenCode server on a public address without a password lets anyone control the agent.`);
|
|
170
|
+
process.exit(EXIT_NO_RESTART);
|
|
171
|
+
}
|
|
152
172
|
store.setState({
|
|
153
173
|
...(defaultVerbosity && {
|
|
154
174
|
defaultVerbosity,
|
|
155
175
|
}),
|
|
156
176
|
...(options.mentionMode && { defaultMentionMode: true }),
|
|
157
177
|
...(options.noCritique && { critiqueEnabled: false }),
|
|
178
|
+
...(options.skipFooterMentions && { footerMentionsEnabled: false }),
|
|
158
179
|
...(options.allowAllUsers && { allowAllUsers: true }),
|
|
159
180
|
...(options.restrictDirectories && { restrictExternalDirectories: true }),
|
|
160
181
|
...(permissionTimeoutMs !== undefined && { permissionTimeoutMs }),
|
|
@@ -163,6 +184,8 @@ cli
|
|
|
163
184
|
...(enabledSkills.length > 0 && { enabledSkills }),
|
|
164
185
|
...(disabledSkills.length > 0 && { disabledSkills }),
|
|
165
186
|
...(options.allowMention && { allowedMentions: options.allowMention }),
|
|
187
|
+
...(opencodeHostname && { opencodeHostname }),
|
|
188
|
+
...(opencodePort !== undefined && { opencodePort }),
|
|
166
189
|
});
|
|
167
190
|
if (enabledSkills.length > 0) {
|
|
168
191
|
cliLogger.log(`Skill whitelist enabled: only [${enabledSkills.join(', ')}] will be injected`);
|
|
@@ -188,6 +211,9 @@ cli
|
|
|
188
211
|
if (options.noCritique) {
|
|
189
212
|
cliLogger.log('Critique disabled: diffs will not be auto-uploaded to critique.work');
|
|
190
213
|
}
|
|
214
|
+
if (options.skipFooterMentions) {
|
|
215
|
+
cliLogger.log('Footer mentions disabled: final session footers will not mention thread creators');
|
|
216
|
+
}
|
|
191
217
|
if (options.noAutoUpgrade) {
|
|
192
218
|
cliLogger.log('Auto-upgrade disabled: kimaki will not check for updates on startup');
|
|
193
219
|
}
|
|
@@ -198,6 +224,12 @@ cli
|
|
|
198
224
|
process.env.KIMAKI_STRADA_ENABLED = '0';
|
|
199
225
|
cliLogger.log('Anonymous product analytics disabled (--no-analytics)');
|
|
200
226
|
}
|
|
227
|
+
if (opencodeHostname) {
|
|
228
|
+
cliLogger.log(`OpenCode server hostname: ${opencodeHostname}`);
|
|
229
|
+
}
|
|
230
|
+
if (opencodePort !== undefined) {
|
|
231
|
+
cliLogger.log(`OpenCode server port: ${opencodePort}`);
|
|
232
|
+
}
|
|
201
233
|
if (options.installUrl) {
|
|
202
234
|
await printDiscordInstallUrlAndExit({
|
|
203
235
|
gateway: options.gateway,
|
package/dist/commands/abort.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// /abort command - Abort the current OpenCode request in this thread.
|
|
2
2
|
import { ChannelType, MessageFlags, } from 'discord.js';
|
|
3
|
-
import { getThreadSession } from '../database.js';
|
|
3
|
+
import { cancelSessionSleepForThread, getThreadSession } from '../database.js';
|
|
4
4
|
import { getOpencodeClient, initializeOpencodeForDirectory } from '../opencode.js';
|
|
5
5
|
import { resolveWorkingDirectory, SILENT_MESSAGE_FLAGS, } from '../discord-utils.js';
|
|
6
6
|
import { getRuntime } from '../session-handler/thread-session-runtime.js';
|
|
@@ -41,6 +41,10 @@ export async function handleAbortCommand({ command, }) {
|
|
|
41
41
|
await command.editReply('No active session in this thread');
|
|
42
42
|
return;
|
|
43
43
|
}
|
|
44
|
+
// Aborting is an explicit "stop waiting" from the user, and it never goes
|
|
45
|
+
// through enqueueIncoming, so cancel any pending sleep here too. Otherwise the
|
|
46
|
+
// wake would still fire later and restart a session the user just stopped.
|
|
47
|
+
await cancelSessionSleepForThread({ threadId: channel.id });
|
|
44
48
|
// abortActiveRun delegates to session.abort(), run settlement stays event-driven.
|
|
45
49
|
const runtime = getRuntime(channel.id);
|
|
46
50
|
if (runtime) {
|
|
@@ -6,6 +6,7 @@ import crypto from 'node:crypto';
|
|
|
6
6
|
import { getThreadSession } from '../database.js';
|
|
7
7
|
import { NOTIFY_MESSAGE_FLAGS, SILENT_MESSAGE_FLAGS, resolveWorkingDirectory, sendThreadMessage, } from '../discord-utils.js';
|
|
8
8
|
import { createLogger } from '../logger.js';
|
|
9
|
+
import { QUEUE_PREFIX } from '../message-formatting.js';
|
|
9
10
|
import { notifyError } from '../sentry.js';
|
|
10
11
|
import { getOrCreateRuntime, } from '../session-handler/thread-session-runtime.js';
|
|
11
12
|
const logger = createLogger('ACT_BTN');
|
|
@@ -214,7 +215,7 @@ export async function handleActionButton(interaction) {
|
|
|
214
215
|
});
|
|
215
216
|
const username = interaction.user.globalName || interaction.user.username;
|
|
216
217
|
const prompt = `User clicked: ${button.label}`;
|
|
217
|
-
await sendThreadMessage(thread,
|
|
218
|
+
await sendThreadMessage(thread, `${QUEUE_PREFIX}**${username}:** ${button.label}`);
|
|
218
219
|
try {
|
|
219
220
|
await sendClickedActionToModel({
|
|
220
221
|
interaction,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// /add-project command - Create Discord channels for an existing OpenCode project.
|
|
2
2
|
import fs from 'node:fs';
|
|
3
3
|
import path from 'node:path';
|
|
4
|
-
import {
|
|
4
|
+
import { findRegisteredTextChannelForDirectory, getAllTextChannelDirectories, } from '../database.js';
|
|
5
5
|
import { initializeOpencodeForDirectory } from '../opencode.js';
|
|
6
6
|
import { createProjectChannels } from '../channel-management.js';
|
|
7
7
|
import { createLogger, LogPrefix } from '../logger.js';
|
|
@@ -38,12 +38,9 @@ export async function handleAddProjectCommand({ command, }) {
|
|
|
38
38
|
await command.editReply(`Directory does not exist: ${directory}`);
|
|
39
39
|
return;
|
|
40
40
|
}
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
});
|
|
45
|
-
if (existingChannels.length > 0) {
|
|
46
|
-
await command.editReply(`A channel already exists for this directory: <#${existingChannels[0].channel_id}>`);
|
|
41
|
+
const existingChannel = await findRegisteredTextChannelForDirectory(directory);
|
|
42
|
+
if (existingChannel) {
|
|
43
|
+
await command.editReply(`A channel already exists for this directory: <#${existingChannel.channel_id}>`);
|
|
47
44
|
return;
|
|
48
45
|
}
|
|
49
46
|
const { textChannelId, voiceChannelId, channelName } = await createProjectChannels({
|