kimaki 0.26.0 → 0.27.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 +25 -122
- package/dist/anthropic-account-identity.js +1 -0
- package/dist/anthropic-auth-plugin.js +1 -0
- package/dist/anthropic-auth-state.js +1 -0
- package/dist/bin.js +73 -19
- package/dist/cli-commands/maintenance.js +46 -7
- package/dist/cli-commands/send.js +24 -0
- package/dist/cli-commands/session.js +183 -8
- package/dist/cli-commands/task.js +13 -1
- package/dist/cli-parsing.test.js +59 -3
- package/dist/cli-runner.js +28 -4
- package/dist/cli-runner.test.js +27 -0
- package/dist/cli-send-thread.e2e.test.js +1 -1
- package/dist/cli.js +32 -0
- package/dist/commands/abort.js +5 -1
- package/dist/commands/agent.js +6 -8
- package/dist/commands/ask-question.js +21 -32
- package/dist/commands/ask-question.test.js +43 -1
- package/dist/commands/login.js +6 -0
- package/dist/commands/merge-worktree.js +36 -10
- package/dist/commands/model-variant.js +7 -1
- package/dist/commands/model.js +14 -8
- package/dist/commands/multioauth.js +1 -0
- package/dist/commands/new-worktree.js +170 -102
- package/dist/commands/user-command.js +6 -8
- package/dist/commands/worktrees.js +5 -13
- package/dist/config.js +16 -1
- package/dist/database.js +124 -3
- package/dist/db.js +22 -0
- package/dist/db.test.js +167 -1
- package/dist/discord-bot.js +34 -32
- package/dist/discord-command-registration.js +9 -1
- package/dist/discord-utils.js +13 -5
- package/dist/discord-utils.test.js +17 -1
- package/dist/errors.js +5 -0
- package/dist/exec-async.js +17 -5
- package/dist/external-directory-permissions.e2e.test.js +2 -2
- 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 +5 -5
- package/dist/git-worktree-core.js +182 -17
- package/dist/ipc-tools-plugin.js +63 -3
- package/dist/kimaki-opencode-plugin-loading.e2e.test.js +122 -58
- package/dist/kimaki-opencode-plugin.js +7 -3
- package/dist/kimaki-workspace-adaptor.js +33 -10
- package/dist/logger.js +10 -1
- package/dist/markdown.js +3 -1
- package/dist/markdown.test.js +11 -3
- package/dist/message-formatting.js +18 -7
- package/dist/message-formatting.test.js +56 -9
- 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.test.js +60 -0
- package/dist/opencode.js +53 -12
- package/dist/question-abort-clears-dropdown.e2e.test.js +89 -0
- package/dist/question-text-before-dropdown.e2e.test.js +52 -0
- package/dist/queue-advanced-abort.e2e.test.js +1 -1
- package/dist/queue-advanced-action-buttons.e2e.test.js +3 -3
- package/dist/queue-advanced-e2e-setup.js +78 -5
- package/dist/queue-advanced-footer.e2e.test.js +40 -246
- package/dist/queue-advanced-model-switch.e2e.test.js +2 -2
- package/dist/queue-advanced-permissions-typing.e2e.test.js +7 -9
- package/dist/queue-advanced-question.e2e.test.js +31 -26
- package/dist/queue-advanced-typing-interrupt.e2e.test.js +2 -2
- package/dist/queue-delete-message.e2e.test.js +1 -1
- package/dist/queue-drain-after-interactive-ui.e2e.test.js +2 -2
- package/dist/queue-interrupt-drain.e2e.test.js +112 -2
- package/dist/queue-question-select-drain.e2e.test.js +30 -128
- package/dist/runtime-lifecycle.e2e.test.js +10 -37
- package/dist/schema.js +34 -0
- package/dist/session-handler/event-stream-state.js +267 -50
- package/dist/session-handler/event-stream-state.test.js +415 -8
- package/dist/session-handler/model-utils.js +159 -5
- package/dist/session-handler/model-utils.test.js +345 -0
- package/dist/session-handler/thread-session-runtime.js +252 -182
- package/dist/session-sleep.test.js +224 -0
- package/dist/session-title-rename.test.js +7 -39
- package/dist/sleep.e2e.test.js +491 -0
- package/dist/startup-time.e2e.test.js +2 -1
- package/dist/store.js +3 -0
- package/dist/system-message.js +59 -6
- package/dist/system-message.test.js +76 -6
- package/dist/task-runner.js +94 -4
- package/dist/task-schedule.js +56 -0
- package/dist/task-schedule.test.js +71 -1
- package/dist/thread-message-queue.e2e.test.js +10 -169
- package/dist/undo-redo.e2e.test.js +2 -2
- package/dist/voice-attachment.js +23 -1
- package/dist/voice-message.e2e.test.js +10 -10
- package/dist/voice.test.js +55 -29
- package/dist/worktree-clone-isolation.e2e.test.js +188 -0
- package/dist/worktree-lifecycle.e2e.test.js +29 -10
- 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/errore/SKILL.md +18 -0
- package/skills/goke/SKILL.md +150 -10
- package/skills/holocron/SKILL.md +37 -0
- 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 +25 -151
- package/src/anthropic-account-identity.ts +1 -0
- package/src/anthropic-auth-plugin.ts +1 -0
- package/src/anthropic-auth-state.ts +1 -0
- package/src/bin.ts +78 -26
- package/src/cli-commands/maintenance.ts +59 -13
- package/src/cli-commands/send.ts +25 -0
- package/src/cli-commands/session.ts +203 -9
- package/src/cli-commands/task.ts +13 -1
- package/src/cli-parsing.test.ts +69 -3
- package/src/cli-runner.test.ts +49 -0
- package/src/cli-runner.ts +28 -4
- package/src/cli-send-thread.e2e.test.ts +1 -1
- package/src/cli.ts +53 -0
- package/src/commands/abort.ts +6 -1
- package/src/commands/agent.ts +6 -6
- package/src/commands/ask-question.test.ts +48 -1
- package/src/commands/ask-question.ts +21 -32
- package/src/commands/login.ts +6 -0
- package/src/commands/merge-worktree.ts +43 -12
- package/src/commands/model-variant.ts +8 -1
- package/src/commands/model.ts +18 -8
- package/src/commands/multioauth.ts +1 -0
- package/src/commands/new-worktree.ts +138 -55
- package/src/commands/user-command.ts +6 -6
- package/src/commands/worktrees.ts +5 -12
- package/src/config.ts +18 -1
- package/src/database.ts +195 -3
- package/src/db.test.ts +188 -0
- package/src/db.ts +26 -0
- package/src/discord-bot.ts +35 -29
- package/src/discord-command-registration.ts +15 -1
- package/src/discord-utils.test.ts +19 -0
- package/src/discord-utils.ts +21 -6
- package/src/errors.ts +5 -0
- package/src/exec-async.ts +20 -11
- package/src/external-directory-permissions.e2e.test.ts +2 -2
- 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 +5 -5
- package/src/git-worktree-core.ts +247 -20
- package/src/ipc-tools-plugin.ts +75 -5
- package/src/kimaki-opencode-plugin-loading.e2e.test.ts +188 -97
- package/src/kimaki-opencode-plugin.ts +7 -3
- package/src/kimaki-workspace-adaptor.ts +40 -11
- package/src/logger.ts +13 -4
- package/src/markdown.test.ts +13 -3
- package/src/markdown.ts +4 -1
- package/src/message-formatting.test.ts +86 -12
- package/src/message-formatting.ts +19 -7
- 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 +81 -0
- package/src/opencode.ts +71 -11
- package/src/question-abort-clears-dropdown.e2e.test.ts +109 -0
- package/src/question-text-before-dropdown.e2e.test.ts +67 -0
- package/src/queue-advanced-abort.e2e.test.ts +1 -1
- package/src/queue-advanced-action-buttons.e2e.test.ts +3 -3
- package/src/queue-advanced-e2e-setup.ts +83 -4
- package/src/queue-advanced-footer.e2e.test.ts +47 -294
- package/src/queue-advanced-model-switch.e2e.test.ts +2 -2
- package/src/queue-advanced-permissions-typing.e2e.test.ts +7 -9
- package/src/queue-advanced-question.e2e.test.ts +35 -29
- package/src/queue-advanced-typing-interrupt.e2e.test.ts +2 -2
- package/src/queue-delete-message.e2e.test.ts +1 -1
- package/src/queue-drain-after-interactive-ui.e2e.test.ts +2 -2
- package/src/queue-interrupt-drain.e2e.test.ts +123 -2
- package/src/queue-question-select-drain.e2e.test.ts +32 -157
- package/src/runtime-lifecycle.e2e.test.ts +10 -41
- package/src/schema.sql +14 -1
- package/src/schema.ts +36 -0
- package/src/session-handler/event-stream-state.test.ts +437 -7
- package/src/session-handler/event-stream-state.ts +389 -53
- package/src/session-handler/model-utils.test.ts +415 -0
- package/src/session-handler/model-utils.ts +249 -5
- package/src/session-handler/thread-session-runtime.ts +300 -215
- package/src/session-sleep.test.ts +294 -0
- package/src/session-title-rename.test.ts +9 -48
- package/src/sleep.e2e.test.ts +562 -0
- package/src/startup-time.e2e.test.ts +5 -1
- package/src/store.ts +21 -0
- package/src/system-message.test.ts +86 -6
- package/src/system-message.ts +66 -7
- package/src/task-runner.ts +136 -2
- package/src/task-schedule.test.ts +76 -0
- package/src/task-schedule.ts +95 -0
- package/src/thread-message-queue.e2e.test.ts +10 -202
- package/src/undo-redo.e2e.test.ts +2 -2
- package/src/voice-attachment.ts +29 -1
- package/src/voice-message.e2e.test.ts +10 -10
- package/src/voice.test.ts +63 -29
- package/src/worktree-clone-isolation.e2e.test.ts +213 -0
- package/src/worktree-lifecycle.e2e.test.ts +29 -9
- 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
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// Session inspection and archival terminal commands.
|
|
2
2
|
import { goke } from 'goke';
|
|
3
3
|
import { z } from 'zod';
|
|
4
|
+
import dedent from 'string-dedent';
|
|
4
5
|
import { note } from '@clack/prompts';
|
|
5
6
|
import YAML from 'yaml';
|
|
6
7
|
import * as errore from 'errore';
|
|
@@ -15,10 +16,13 @@ import { getBotTokenWithMode, getThreadSession, getThreadIdBySessionId, getSessi
|
|
|
15
16
|
import { ShareMarkdown } from '../markdown.js';
|
|
16
17
|
import { parseSessionSearchPattern, findFirstSessionSearchHit, buildSessionSearchSnippet, getPartSearchTexts } from '../session-search.js';
|
|
17
18
|
import { formatWorktreeName, formatAutoWorktreeName } from '../commands/new-worktree.js';
|
|
19
|
+
import { formatTimeAgo } from '../commands/worktrees.js';
|
|
20
|
+
import { editorsForFile, loadFileEditEvents } from '../file-edit-log.js';
|
|
18
21
|
import { WORKTREE_PREFIX } from '../commands/merge-worktree.js';
|
|
19
22
|
import { buildOpencodeEventLogLine } from '../session-handler/opencode-session-event-log.js';
|
|
20
23
|
import { createDiscordRest } from '../discord-urls.js';
|
|
21
24
|
import { archiveThread, uploadFilesToDiscord, stripMentions } from '../discord-utils.js';
|
|
25
|
+
import { OpenCodeSdkError } from '../errors.js';
|
|
22
26
|
import { setDataDir, setProjectsDir, getDataDir, getProjectsDir } from '../config.js';
|
|
23
27
|
import { execAsync, validateWorktreeDirectory } from '../worktrees.js';
|
|
24
28
|
import { upgrade, getCurrentVersion } from '../upgrade.js';
|
|
@@ -53,6 +57,8 @@ async function resolveSessionDirectoryFromDatabase({ sessionId, }) {
|
|
|
53
57
|
cli
|
|
54
58
|
.command('session list', 'List all OpenCode sessions, marking which were started via Kimaki')
|
|
55
59
|
.option('--project <path>', 'Project directory to list sessions for (defaults to cwd)')
|
|
60
|
+
.option('--active', 'Only list active sessions; exits 1 when none remain')
|
|
61
|
+
.option('--exclude <sessionId>', 'Exclude one session ID from the results')
|
|
56
62
|
.option('--json', 'Output as JSON')
|
|
57
63
|
.action(async (options) => {
|
|
58
64
|
try {
|
|
@@ -66,9 +72,32 @@ cli
|
|
|
66
72
|
}
|
|
67
73
|
const sessionsResponse = await getClient().session.list();
|
|
68
74
|
const sessions = sessionsResponse.data || [];
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
75
|
+
const statuses = await (async () => {
|
|
76
|
+
if (!options.active)
|
|
77
|
+
return null;
|
|
78
|
+
const response = await getClient().session.status({
|
|
79
|
+
directory: projectDirectory,
|
|
80
|
+
});
|
|
81
|
+
if (response.error) {
|
|
82
|
+
cliLogger.error('Failed to list active sessions');
|
|
83
|
+
process.exit(EXIT_NO_RESTART);
|
|
84
|
+
}
|
|
85
|
+
return response.data || {};
|
|
86
|
+
})();
|
|
87
|
+
const selectedSessions = sessions.filter((session) => {
|
|
88
|
+
if (session.id === options.exclude)
|
|
89
|
+
return false;
|
|
90
|
+
if (!options.active)
|
|
91
|
+
return true;
|
|
92
|
+
const status = statuses?.[session.id];
|
|
93
|
+
return Boolean(status && status.type !== 'idle');
|
|
94
|
+
});
|
|
95
|
+
if (selectedSessions.length === 0) {
|
|
96
|
+
if (options.json)
|
|
97
|
+
console.log('[]');
|
|
98
|
+
else
|
|
99
|
+
cliLogger.log(options.active ? 'No active sessions found' : 'No sessions found');
|
|
100
|
+
process.exit(options.active ? 1 : 0);
|
|
72
101
|
}
|
|
73
102
|
// Look up which sessions were started via kimaki (have a thread mapping)
|
|
74
103
|
const db = await getDb();
|
|
@@ -78,7 +107,7 @@ cli
|
|
|
78
107
|
const sessionToThread = new Map(threadSessions
|
|
79
108
|
.filter((row) => row.session_id !== '')
|
|
80
109
|
.map((row) => [row.session_id, row.thread_id]));
|
|
81
|
-
const sessionStartSources = await getSessionStartSourcesBySessionIds(
|
|
110
|
+
const sessionStartSources = await getSessionStartSourcesBySessionIds(selectedSessions.map((session) => session.id));
|
|
82
111
|
const scheduleModeLabel = ({ scheduleKind, }) => {
|
|
83
112
|
if (scheduleKind === 'at') {
|
|
84
113
|
return 'delay';
|
|
@@ -86,7 +115,7 @@ cli
|
|
|
86
115
|
return 'cron';
|
|
87
116
|
};
|
|
88
117
|
if (options.json) {
|
|
89
|
-
const output =
|
|
118
|
+
const output = selectedSessions.map((session) => {
|
|
90
119
|
const startSource = sessionStartSources.get(session.id);
|
|
91
120
|
const startedBy = startSource
|
|
92
121
|
? `scheduled-${scheduleModeLabel({ scheduleKind: startSource.schedule_kind })}`
|
|
@@ -100,12 +129,13 @@ cli
|
|
|
100
129
|
threadId: sessionToThread.get(session.id) || null,
|
|
101
130
|
startedBy,
|
|
102
131
|
scheduledTaskId: startSource?.scheduled_task_id || null,
|
|
132
|
+
status: options.active ? statuses?.[session.id]?.type || 'busy' : undefined,
|
|
103
133
|
};
|
|
104
134
|
});
|
|
105
135
|
console.log(JSON.stringify(output, null, 2));
|
|
106
136
|
process.exit(0);
|
|
107
137
|
}
|
|
108
|
-
for (const session of
|
|
138
|
+
for (const session of selectedSessions) {
|
|
109
139
|
const threadId = sessionToThread.get(session.id);
|
|
110
140
|
const startSource = sessionStartSources.get(session.id);
|
|
111
141
|
const source = threadId ? '(kimaki)' : '(opencode)';
|
|
@@ -114,7 +144,10 @@ cli
|
|
|
114
144
|
: '';
|
|
115
145
|
const updatedAt = new Date(session.time.updated).toISOString();
|
|
116
146
|
const threadInfo = threadId ? ` | thread: ${threadId}` : '';
|
|
117
|
-
|
|
147
|
+
const statusInfo = options.active
|
|
148
|
+
? ` | status: ${statuses?.[session.id]?.type || 'busy'}`
|
|
149
|
+
: '';
|
|
150
|
+
console.log(`${session.id} | ${session.title || 'Untitled Session'} | ${session.directory} | ${updatedAt} | ${source}${threadInfo}${startedBy}${statusInfo}`);
|
|
118
151
|
}
|
|
119
152
|
process.exit(0);
|
|
120
153
|
}
|
|
@@ -123,6 +156,79 @@ cli
|
|
|
123
156
|
process.exit(EXIT_NO_RESTART);
|
|
124
157
|
}
|
|
125
158
|
});
|
|
159
|
+
cli
|
|
160
|
+
.command('session editors <file>', dedent `
|
|
161
|
+
List sessions that last edited a file, newest first.
|
|
162
|
+
|
|
163
|
+
Use this before a commit in another session so the \`Session:\` line
|
|
164
|
+
uses the session that actually edited the file.
|
|
165
|
+
`)
|
|
166
|
+
.option('--json', 'Output as JSON')
|
|
167
|
+
.option('--limit <n>', z.number().default(20).describe('Max sessions to show'))
|
|
168
|
+
.example('kimaki session editors src/cli.ts')
|
|
169
|
+
.example('kimaki session editors src/cli.ts --json')
|
|
170
|
+
.action(async (file, options, { console, process }) => {
|
|
171
|
+
try {
|
|
172
|
+
const cwd = process.cwd;
|
|
173
|
+
const loaded = loadFileEditEvents({ dataDir: getDataDir() });
|
|
174
|
+
if (loaded instanceof Error) {
|
|
175
|
+
console.error(loaded.message);
|
|
176
|
+
process.exit(EXIT_NO_RESTART);
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
const editors = editorsForFile({
|
|
180
|
+
events: loaded,
|
|
181
|
+
filePath: file,
|
|
182
|
+
cwd,
|
|
183
|
+
}).slice(0, options.limit);
|
|
184
|
+
if (editors.length === 0) {
|
|
185
|
+
console.error(`No recorded editors for ${path.resolve(cwd, file)}`);
|
|
186
|
+
process.exit(1);
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
const titles = new Map();
|
|
190
|
+
try {
|
|
191
|
+
await initDatabase();
|
|
192
|
+
const db = await getDb();
|
|
193
|
+
const sessionRows = await db.query.thread_sessions.findMany({
|
|
194
|
+
columns: { session_id: true, last_synced_name: true },
|
|
195
|
+
where: { session_id: { in: editors.map((editor) => editor.sessionId) } },
|
|
196
|
+
orderBy: { updated_at: 'desc' },
|
|
197
|
+
});
|
|
198
|
+
for (const row of sessionRows) {
|
|
199
|
+
if (!titles.has(row.session_id) && row.last_synced_name) {
|
|
200
|
+
titles.set(row.session_id, row.last_synced_name);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
catch (error) {
|
|
205
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
206
|
+
}
|
|
207
|
+
const rows = editors.map((editor) => {
|
|
208
|
+
const title = titles.get(editor.sessionId) || '-';
|
|
209
|
+
const editedAt = new Date(editor.at);
|
|
210
|
+
return {
|
|
211
|
+
sessionId: editor.sessionId,
|
|
212
|
+
title,
|
|
213
|
+
editedAt: editedAt.toISOString(),
|
|
214
|
+
ago: formatTimeAgo(editedAt),
|
|
215
|
+
};
|
|
216
|
+
});
|
|
217
|
+
if (options.json) {
|
|
218
|
+
console.log(JSON.stringify(rows, null, 2));
|
|
219
|
+
process.exit(0);
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
for (const row of rows) {
|
|
223
|
+
console.log(`${row.sessionId} | ${row.title} | ${row.ago}`);
|
|
224
|
+
}
|
|
225
|
+
process.exit(0);
|
|
226
|
+
}
|
|
227
|
+
catch (error) {
|
|
228
|
+
console.error(error instanceof Error ? error.stack : String(error));
|
|
229
|
+
process.exit(EXIT_NO_RESTART);
|
|
230
|
+
}
|
|
231
|
+
});
|
|
126
232
|
cli
|
|
127
233
|
.command('session read <sessionId>', 'Read a session conversation as markdown (pipe to file to grep)')
|
|
128
234
|
.option('--project <path>', 'Project directory (defaults to cwd)')
|
|
@@ -430,7 +536,7 @@ cli
|
|
|
430
536
|
process.exit(0);
|
|
431
537
|
});
|
|
432
538
|
cli
|
|
433
|
-
.command('session archive [threadId]', 'Archive a Discord thread
|
|
539
|
+
.command('session archive [threadId]', 'Archive a Discord thread without stopping its mapped OpenCode session')
|
|
434
540
|
.option('--session <sessionId>', 'Resolve thread from an OpenCode session ID')
|
|
435
541
|
.action(async (threadIdArg, options) => {
|
|
436
542
|
try {
|
|
@@ -544,6 +650,75 @@ cli
|
|
|
544
650
|
process.exit(EXIT_NO_RESTART);
|
|
545
651
|
}
|
|
546
652
|
});
|
|
653
|
+
cli
|
|
654
|
+
.command('session title <title>', 'Update the OpenCode session title. Discord thread name follows automatically.')
|
|
655
|
+
.option('--session <sessionId>', 'OpenCode session ID')
|
|
656
|
+
.option('--thread <threadId>', 'Discord thread ID')
|
|
657
|
+
.example("kimaki session title 'Fix queue draining' --session ses_xxx")
|
|
658
|
+
.action(async (title, options) => {
|
|
659
|
+
try {
|
|
660
|
+
await initDatabase();
|
|
661
|
+
const trimmedTitle = title.trim();
|
|
662
|
+
if (!trimmedTitle) {
|
|
663
|
+
cliLogger.error('Title must not be empty');
|
|
664
|
+
process.exit(EXIT_NO_RESTART);
|
|
665
|
+
}
|
|
666
|
+
if (options.session && options.thread) {
|
|
667
|
+
cliLogger.error('Use either --session or --thread, not both');
|
|
668
|
+
process.exit(EXIT_NO_RESTART);
|
|
669
|
+
}
|
|
670
|
+
if (!options.session && !options.thread) {
|
|
671
|
+
cliLogger.error('Provide --session <sessionId> or --thread <threadId>');
|
|
672
|
+
process.exit(EXIT_NO_RESTART);
|
|
673
|
+
}
|
|
674
|
+
const sessionId = await (async () => {
|
|
675
|
+
if (options.session)
|
|
676
|
+
return options.session;
|
|
677
|
+
const threadId = options.thread;
|
|
678
|
+
if (!threadId)
|
|
679
|
+
return null;
|
|
680
|
+
return getThreadSession(threadId);
|
|
681
|
+
})();
|
|
682
|
+
if (!sessionId) {
|
|
683
|
+
cliLogger.error(options.thread
|
|
684
|
+
? `No OpenCode session found for thread: ${options.thread}`
|
|
685
|
+
: 'Provide --session <sessionId> or --thread <threadId>');
|
|
686
|
+
process.exit(EXIT_NO_RESTART);
|
|
687
|
+
}
|
|
688
|
+
const directory = await resolveSessionDirectoryFromDatabase({
|
|
689
|
+
sessionId,
|
|
690
|
+
});
|
|
691
|
+
if (directory instanceof Error) {
|
|
692
|
+
cliLogger.error(directory.message);
|
|
693
|
+
process.exit(EXIT_NO_RESTART);
|
|
694
|
+
}
|
|
695
|
+
const serverResult = await initializeOpencodeForDirectory(directory);
|
|
696
|
+
if (serverResult instanceof Error) {
|
|
697
|
+
cliLogger.error(`Failed to initialize OpenCode: ${serverResult.message}`);
|
|
698
|
+
process.exit(EXIT_NO_RESTART);
|
|
699
|
+
}
|
|
700
|
+
const updateResult = await serverResult()
|
|
701
|
+
.session.update({
|
|
702
|
+
sessionID: sessionId,
|
|
703
|
+
title: trimmedTitle,
|
|
704
|
+
})
|
|
705
|
+
.catch((e) => new OpenCodeSdkError({ operation: 'session.update', cause: e }));
|
|
706
|
+
if (updateResult instanceof Error) {
|
|
707
|
+
cliLogger.error(updateResult.message);
|
|
708
|
+
process.exit(EXIT_NO_RESTART);
|
|
709
|
+
}
|
|
710
|
+
if (updateResult.error) {
|
|
711
|
+
cliLogger.error('OpenCode rejected the session title update');
|
|
712
|
+
process.exit(EXIT_NO_RESTART);
|
|
713
|
+
}
|
|
714
|
+
note(`Updated OpenCode title: ${trimmedTitle}`, 'Title updated');
|
|
715
|
+
process.exit(0);
|
|
716
|
+
}
|
|
717
|
+
catch (error) {
|
|
718
|
+
cliLogger.error('Error:', error instanceof Error ? error.stack : String(error));
|
|
719
|
+
process.exit(EXIT_NO_RESTART);
|
|
720
|
+
}
|
|
721
|
+
});
|
|
547
722
|
cli
|
|
548
723
|
.command('session discord-url <sessionId>', 'Print the Discord thread URL for a session')
|
|
549
724
|
.option('--json', 'Output as JSON')
|
|
@@ -24,6 +24,7 @@ import { execAsync, validateWorktreeDirectory } 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, getDiscordUserIdFromUserOption, isDiscordMemberLookupUnavailable, isGuildMemberSearchResult, isThreadChannelType, printDiscordInstallUrlAndExit, resolveBotCredentials, resolveDiscordUserOption, sendDiscordMessageWithOptionalAttachment, } from '../cli-runner.js';
|
|
27
|
+
import { validateCliModelOption } from '../session-handler/model-utils.js';
|
|
27
28
|
const cliLogger = createLogger(LogPrefix.CLI);
|
|
28
29
|
const cli = goke();
|
|
29
30
|
cli
|
|
@@ -191,6 +192,17 @@ cli
|
|
|
191
192
|
process.exit(EXIT_NO_RESTART);
|
|
192
193
|
}
|
|
193
194
|
const newPrompt = trimmedPrompt ?? existingPayload.prompt;
|
|
195
|
+
const nextModel = hasModel ? options.model.trim() || null : existingPayload.model;
|
|
196
|
+
if (nextModel) {
|
|
197
|
+
const modelCheck = await validateCliModelOption({
|
|
198
|
+
model: nextModel,
|
|
199
|
+
directory: task.project_directory ?? undefined,
|
|
200
|
+
});
|
|
201
|
+
if (modelCheck instanceof Error) {
|
|
202
|
+
cliLogger.error(modelCheck.message);
|
|
203
|
+
process.exit(EXIT_NO_RESTART);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
194
206
|
// Match send --model/--agent: empty string clears the override (null).
|
|
195
207
|
const updatedPayload = {
|
|
196
208
|
...existingPayload,
|
|
@@ -199,7 +211,7 @@ cli
|
|
|
199
211
|
? { userId: resolvedUser.id, username: resolvedUser.username || null }
|
|
200
212
|
: {}),
|
|
201
213
|
...(hasAgent ? { agent: options.agent.trim() || null } : {}),
|
|
202
|
-
...(hasModel ? { model:
|
|
214
|
+
...(hasModel ? { model: nextModel } : {}),
|
|
203
215
|
...(hasPreRun ? { preRunCommand: options.preRun.trim() || null } : {}),
|
|
204
216
|
...(hasAllowConcurrency
|
|
205
217
|
? { allowConcurrency: options.allowConcurrency === 'true' }
|
package/dist/cli-parsing.test.js
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
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')",
|
|
11
|
+
"cli.command('session title <title>', 'Update title').option('--session <sessionId>', 'Session ID').option('--thread <threadId>', 'Thread ID')",
|
|
10
12
|
"cli.command('session search <query>', 'Search sessions').option('--channel <channelId>', 'Discord channel ID').option('--project <path>', 'Project path')",
|
|
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')",
|
|
@@ -47,7 +49,7 @@ async function parseRootBotOptions(argv) {
|
|
|
47
49
|
const script = [
|
|
48
50
|
"import { goke } from 'goke'",
|
|
49
51
|
'const cli = goke(\'kimaki\')',
|
|
50
|
-
"cli.command('', 'bot').option('--no-analytics', 'Disable analytics')",
|
|
52
|
+
"cli.command('', 'bot').option('--no-analytics', 'Disable analytics').option('--skip-footer-mentions', 'Do not mention the thread creator in final footers')",
|
|
51
53
|
`const result = await cli.parse(${JSON.stringify(argv)}, { run: false })`,
|
|
52
54
|
'process.stdout.write(JSON.stringify({ args: result.args, options: result.options }))',
|
|
53
55
|
].join(';');
|
|
@@ -78,6 +80,23 @@ describe('goke CLI ID parsing', () => {
|
|
|
78
80
|
expect(result.options.guild).toBe(guildId);
|
|
79
81
|
expect(typeof result.options.guild).toBe('string');
|
|
80
82
|
});
|
|
83
|
+
test('keeps session title and session ID as strings', async () => {
|
|
84
|
+
const sessionId = '001111222233334444';
|
|
85
|
+
const title = 'Fix queue draining';
|
|
86
|
+
const result = await parseWithGoke([
|
|
87
|
+
'node',
|
|
88
|
+
'kimaki',
|
|
89
|
+
'session',
|
|
90
|
+
'title',
|
|
91
|
+
title,
|
|
92
|
+
'--session',
|
|
93
|
+
sessionId,
|
|
94
|
+
]);
|
|
95
|
+
expect(result.args[0]).toBe(title);
|
|
96
|
+
expect(typeof result.args[0]).toBe('string');
|
|
97
|
+
expect(result.options.session).toBe(sessionId);
|
|
98
|
+
expect(typeof result.options.session).toBe('string');
|
|
99
|
+
});
|
|
81
100
|
test('keeps session archive thread ID as string', async () => {
|
|
82
101
|
const threadId = '0098765432109876543';
|
|
83
102
|
const result = await parseWithGoke(['node', 'kimaki', 'session', 'archive', threadId]);
|
|
@@ -136,12 +155,49 @@ describe('goke CLI ID parsing', () => {
|
|
|
136
155
|
expect(stdout).toContain('send');
|
|
137
156
|
expect(stdout).toContain('multioauth');
|
|
138
157
|
});
|
|
139
|
-
test('
|
|
158
|
+
test('merge-worktree parses strategy and target branch', async () => {
|
|
159
|
+
const result = await maintenanceCommands.parse([
|
|
160
|
+
'node',
|
|
161
|
+
'kimaki',
|
|
162
|
+
'merge-worktree',
|
|
163
|
+
'--strategy',
|
|
164
|
+
'squash',
|
|
165
|
+
'--target-branch',
|
|
166
|
+
'release',
|
|
167
|
+
'--thread',
|
|
168
|
+
'123456789012345678',
|
|
169
|
+
'--thread-name',
|
|
170
|
+
'feature work',
|
|
171
|
+
], { run: false });
|
|
172
|
+
expect({
|
|
173
|
+
command: maintenanceCommands.matchedCommandName,
|
|
174
|
+
strategy: result.options.strategy,
|
|
175
|
+
targetBranch: result.options.targetBranch,
|
|
176
|
+
thread: result.options.thread,
|
|
177
|
+
threadName: result.options.threadName,
|
|
178
|
+
}).toMatchInlineSnapshot(`
|
|
179
|
+
{
|
|
180
|
+
"command": "merge-worktree",
|
|
181
|
+
"strategy": "squash",
|
|
182
|
+
"targetBranch": "release",
|
|
183
|
+
"thread": "123456789012345678",
|
|
184
|
+
"threadName": "feature work",
|
|
185
|
+
}
|
|
186
|
+
`);
|
|
187
|
+
});
|
|
188
|
+
test('parses root bot boolean flags', async () => {
|
|
140
189
|
const result = await parseRootBotOptions([
|
|
141
190
|
'node',
|
|
142
191
|
'kimaki',
|
|
143
192
|
'--no-analytics',
|
|
193
|
+
'--skip-footer-mentions',
|
|
144
194
|
]);
|
|
145
|
-
expect(result.options
|
|
195
|
+
expect(result.options).toMatchInlineSnapshot(`
|
|
196
|
+
{
|
|
197
|
+
"--": [],
|
|
198
|
+
"noAnalytics": true,
|
|
199
|
+
"skipFooterMentions": true,
|
|
200
|
+
}
|
|
201
|
+
`);
|
|
146
202
|
});
|
|
147
203
|
});
|
package/dist/cli-runner.js
CHANGED
|
@@ -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;
|
|
@@ -1271,12 +1293,14 @@ export async function run({ restartOnboarding, addChannels, useWorktrees, enable
|
|
|
1271
1293
|
catch (error) {
|
|
1272
1294
|
cliLogger.log('Failed to connect to Discord', discordClient.ws.gateway);
|
|
1273
1295
|
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
|
-
//
|
|
1296
|
+
// Transient network errors (DNS down, gateway unreachable, connect
|
|
1297
|
+
// timeout) should allow the bin.ts wrapper to restart us after a delay.
|
|
1298
|
+
// EXIT_TEMPFAIL skips the crash-loop detector so a long outage cannot
|
|
1299
|
+
// permanently stop the bot. Only truly fatal errors (bad token, invalid
|
|
1300
|
+
// intent, etc.) should use EXIT_NO_RESTART.
|
|
1277
1301
|
if (isTransientNetworkError(error)) {
|
|
1278
1302
|
cliLogger.error('Transient network error, exiting for wrapper restart...');
|
|
1279
|
-
process.exit(
|
|
1303
|
+
process.exit(EXIT_TEMPFAIL);
|
|
1280
1304
|
}
|
|
1281
1305
|
process.exit(EXIT_NO_RESTART);
|
|
1282
1306
|
}
|
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
|
});
|
|
@@ -321,7 +321,7 @@ describe('kimaki send --channel thread creation', () => {
|
|
|
321
321
|
--- from: assistant (TestBot)
|
|
322
322
|
*using deterministic-provider/deterministic-v2*
|
|
323
323
|
⬥ caught-by-model
|
|
324
|
-
*project ⋅ main ⋅ Ns ⋅ N% ⋅ deterministic-v2*
|
|
324
|
+
*project ⋅ main ⋅ Ns ⋅ N% ⋅ 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.
|
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) {
|
package/dist/commands/agent.js
CHANGED
|
@@ -487,14 +487,12 @@ async function handleQuickAgentWithPrompt({ command, appId, fallbackAgentName, p
|
|
|
487
487
|
rest: command.client.rest,
|
|
488
488
|
});
|
|
489
489
|
}
|
|
490
|
-
const
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
return result;
|
|
497
|
-
})();
|
|
490
|
+
const worktreeResult = worktreePromise ? await worktreePromise : projectDirectory;
|
|
491
|
+
if (worktreeResult instanceof Error) {
|
|
492
|
+
await command.editReply(`Worktree creation failed: ${worktreeResult.message}`);
|
|
493
|
+
return;
|
|
494
|
+
}
|
|
495
|
+
const sessionDirectory = worktreeResult;
|
|
498
496
|
await command
|
|
499
497
|
.editReply(`Sent with **${resolvedAgentName}** agent in ${thread.toString()}`)
|
|
500
498
|
.catch(() => {
|