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/src/cli-commands/task.ts
CHANGED
|
@@ -24,7 +24,7 @@ import { archiveThread, uploadFilesToDiscord, stripMentions } from '../discord-u
|
|
|
24
24
|
import { setDataDir, setProjectsDir, getDataDir, getProjectsDir } from '../config.js'
|
|
25
25
|
import { execAsync, validateWorktreeDirectory } from '../worktrees.js'
|
|
26
26
|
import { upgrade, getCurrentVersion } from '../upgrade.js'
|
|
27
|
-
import { getPromptPreview, parseSendAtValue, parseScheduledTaskPayload, serializeScheduledTaskPayload, type ScheduledTaskPayload } from '../task-schedule.js'
|
|
27
|
+
import { applyScheduledTaskUserEdit, getPromptPreview, parseSendAtValue, parseScheduledTaskPayload, serializeScheduledTaskPayload, type ScheduledTaskPayload } from '../task-schedule.js'
|
|
28
28
|
import {
|
|
29
29
|
EXIT_NO_RESTART,
|
|
30
30
|
formatMemberLookupUnavailableMessage,
|
|
@@ -39,6 +39,7 @@ import {
|
|
|
39
39
|
resolveDiscordUserOption,
|
|
40
40
|
sendDiscordMessageWithOptionalAttachment,
|
|
41
41
|
} from '../cli-runner.js'
|
|
42
|
+
import { validateCliModelOption } from '../session-handler/model-utils.js'
|
|
42
43
|
|
|
43
44
|
const cliLogger = createLogger(LogPrefix.CLI)
|
|
44
45
|
const cli = goke()
|
|
@@ -192,7 +193,7 @@ cli
|
|
|
192
193
|
)
|
|
193
194
|
.option(
|
|
194
195
|
'-u, --user <user>',
|
|
195
|
-
'Discord user ID, mention, or username added to the task thread',
|
|
196
|
+
'Discord user ID, mention, or username added to the task thread (empty string clears)',
|
|
196
197
|
)
|
|
197
198
|
.action(async (id, options) => {
|
|
198
199
|
try {
|
|
@@ -202,11 +203,12 @@ cli
|
|
|
202
203
|
const hasModel = options.model !== undefined
|
|
203
204
|
const hasPreRun = options.preRun !== undefined
|
|
204
205
|
const hasAllowConcurrency = options.allowConcurrency !== undefined
|
|
206
|
+
const hasUser = options.user !== undefined
|
|
205
207
|
|
|
206
208
|
if (
|
|
207
209
|
!trimmedPrompt &&
|
|
208
210
|
!options.sendAt &&
|
|
209
|
-
!
|
|
211
|
+
!hasUser &&
|
|
210
212
|
!hasAgent &&
|
|
211
213
|
!hasModel &&
|
|
212
214
|
!hasPreRun &&
|
|
@@ -251,7 +253,7 @@ cli
|
|
|
251
253
|
process.exit(EXIT_NO_RESTART)
|
|
252
254
|
}
|
|
253
255
|
|
|
254
|
-
const resolvedUser = options.user
|
|
256
|
+
const resolvedUser = hasUser && options.user.trim()
|
|
255
257
|
? await resolveTaskUser({
|
|
256
258
|
user: options.user,
|
|
257
259
|
channelId: task.channel_id,
|
|
@@ -263,24 +265,39 @@ cli
|
|
|
263
265
|
}
|
|
264
266
|
|
|
265
267
|
const newPrompt = trimmedPrompt ?? existingPayload.prompt
|
|
268
|
+
const nextModel = hasModel ? options.model!.trim() || null : existingPayload.model
|
|
269
|
+
if (nextModel) {
|
|
270
|
+
const modelCheck = await validateCliModelOption({
|
|
271
|
+
model: nextModel,
|
|
272
|
+
directory: task.project_directory ?? undefined,
|
|
273
|
+
})
|
|
274
|
+
if (modelCheck instanceof Error) {
|
|
275
|
+
cliLogger.error(modelCheck.message)
|
|
276
|
+
process.exit(EXIT_NO_RESTART)
|
|
277
|
+
}
|
|
278
|
+
}
|
|
266
279
|
// Match send --model/--agent: empty string clears the override (null).
|
|
267
280
|
const updatedPayload: ScheduledTaskPayload = {
|
|
268
281
|
...existingPayload,
|
|
269
282
|
prompt: newPrompt,
|
|
270
|
-
...(resolvedUser
|
|
271
|
-
? { userId: resolvedUser.id, username: resolvedUser.username || null }
|
|
272
|
-
: {}),
|
|
273
283
|
...(hasAgent ? { agent: options.agent!.trim() || null } : {}),
|
|
274
|
-
...(hasModel ? { model:
|
|
284
|
+
...(hasModel ? { model: nextModel } : {}),
|
|
275
285
|
...(hasPreRun ? { preRunCommand: options.preRun!.trim() || null } : {}),
|
|
276
286
|
...(hasAllowConcurrency
|
|
277
287
|
? { allowConcurrency: options.allowConcurrency === 'true' }
|
|
278
288
|
: {}),
|
|
279
289
|
}
|
|
290
|
+
const payloadWithUser = hasUser
|
|
291
|
+
? applyScheduledTaskUserEdit({
|
|
292
|
+
payload: updatedPayload,
|
|
293
|
+
userOption: options.user,
|
|
294
|
+
resolvedUser,
|
|
295
|
+
})
|
|
296
|
+
: updatedPayload
|
|
280
297
|
|
|
281
298
|
const updateData: Parameters<typeof updateScheduledTask>[0] = {
|
|
282
299
|
taskId,
|
|
283
|
-
payloadJson: serializeScheduledTaskPayload(
|
|
300
|
+
payloadJson: serializeScheduledTaskPayload(payloadWithUser),
|
|
284
301
|
promptPreview: getPromptPreview(newPrompt),
|
|
285
302
|
}
|
|
286
303
|
|
|
@@ -309,20 +326,22 @@ cli
|
|
|
309
326
|
}
|
|
310
327
|
|
|
311
328
|
const parts: string[] = [`Updated task ${taskId}`]
|
|
312
|
-
if (
|
|
329
|
+
if (hasUser) {
|
|
313
330
|
parts.push(
|
|
314
|
-
|
|
331
|
+
payloadWithUser.userId
|
|
332
|
+
? `user ${payloadWithUser.username || payloadWithUser.userId} will be added to the thread`
|
|
333
|
+
: 'user cleared; nobody will be added to the thread',
|
|
315
334
|
)
|
|
316
335
|
}
|
|
317
336
|
if (hasAgent) {
|
|
318
|
-
parts.push(`agent=${
|
|
337
|
+
parts.push(`agent=${payloadWithUser.agent || '-'}`)
|
|
319
338
|
}
|
|
320
339
|
if (hasModel) {
|
|
321
|
-
parts.push(`model=${
|
|
340
|
+
parts.push(`model=${payloadWithUser.model || '-'}`)
|
|
322
341
|
}
|
|
323
|
-
if (hasPreRun) parts.push(`preRun=${
|
|
342
|
+
if (hasPreRun) parts.push(`preRun=${payloadWithUser.preRunCommand || '-'}`)
|
|
324
343
|
if (hasAllowConcurrency) {
|
|
325
|
-
parts.push(`allowConcurrency=${
|
|
344
|
+
parts.push(`allowConcurrency=${payloadWithUser.allowConcurrency}`)
|
|
326
345
|
}
|
|
327
346
|
cliLogger.log(parts.join(' | '))
|
|
328
347
|
process.exit(0)
|
package/src/cli-parsing.test.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
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
|
|
|
5
6
|
async function parseWithGoke(argv: string[]) {
|
|
6
7
|
const script = [
|
|
@@ -8,10 +9,12 @@ async function parseWithGoke(argv: string[]) {
|
|
|
8
9
|
'const cli = goke(\'kimaki\')',
|
|
9
10
|
"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')",
|
|
10
11
|
"cli.command('session archive <threadId>', 'Archive a thread')",
|
|
11
|
-
"cli.command('session
|
|
12
|
+
"cli.command('session title <title>', 'Update title').option('--session <sessionId>', 'Session ID').option('--thread <threadId>', 'Thread ID')",
|
|
13
|
+
"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')",
|
|
12
14
|
"cli.command('session export-events-jsonl', 'Export in-memory events to JSONL').option('--session <sessionId>', 'Session ID').option('--out <file>', 'Output path')",
|
|
13
15
|
"cli.command('add-project', 'Add a project').option('-g, --guild <guildId>', 'Discord guild/server ID')",
|
|
14
16
|
"cli.command('task delete <id>', 'Delete task')",
|
|
17
|
+
"cli.command('task edit <id>', 'Edit task').option('-u, --user <user>', 'Discord user')",
|
|
15
18
|
"cli.command('multioauth anthropic list', 'List stored Anthropic accounts')",
|
|
16
19
|
"cli.command('multioauth anthropic remove <indexOrEmail>', 'Remove stored Anthropic account')",
|
|
17
20
|
"cli.command('multioauth openai list', 'List stored OpenAI accounts')",
|
|
@@ -55,7 +58,7 @@ async function parseRootBotOptions(argv: string[]) {
|
|
|
55
58
|
const script = [
|
|
56
59
|
"import { goke } from 'goke'",
|
|
57
60
|
'const cli = goke(\'kimaki\')',
|
|
58
|
-
"cli.command('', 'bot').option('--no-analytics', 'Disable analytics')",
|
|
61
|
+
"cli.command('', 'bot').option('--no-analytics', 'Disable analytics').option('--skip-footer-mentions', 'Do not mention the thread creator in final footers')",
|
|
59
62
|
`const result = await cli.parse(${JSON.stringify(argv)}, { run: false })`,
|
|
60
63
|
'process.stdout.write(JSON.stringify({ args: result.args, options: result.options }))',
|
|
61
64
|
].join(';')
|
|
@@ -106,6 +109,28 @@ describe('goke CLI ID parsing', () => {
|
|
|
106
109
|
expect(typeof result.options.guild).toBe('string')
|
|
107
110
|
})
|
|
108
111
|
|
|
112
|
+
test('keeps session title and session ID as strings', async () => {
|
|
113
|
+
const sessionId = '001111222233334444'
|
|
114
|
+
const title = 'Fix queue draining'
|
|
115
|
+
|
|
116
|
+
const result = await parseWithGoke(
|
|
117
|
+
[
|
|
118
|
+
'node',
|
|
119
|
+
'kimaki',
|
|
120
|
+
'session',
|
|
121
|
+
'title',
|
|
122
|
+
title,
|
|
123
|
+
'--session',
|
|
124
|
+
sessionId,
|
|
125
|
+
],
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
expect(result.args[0]).toBe(title)
|
|
129
|
+
expect(typeof result.args[0]).toBe('string')
|
|
130
|
+
expect(result.options.session).toBe(sessionId)
|
|
131
|
+
expect(typeof result.options.session).toBe('string')
|
|
132
|
+
})
|
|
133
|
+
|
|
109
134
|
test('keeps session archive thread ID as string', async () => {
|
|
110
135
|
const threadId = '0098765432109876543'
|
|
111
136
|
|
|
@@ -131,6 +156,23 @@ describe('goke CLI ID parsing', () => {
|
|
|
131
156
|
expect(typeof result.options.channel).toBe('string')
|
|
132
157
|
})
|
|
133
158
|
|
|
159
|
+
test('parses session search --all as a boolean', async () => {
|
|
160
|
+
const result = await parseWithGoke(
|
|
161
|
+
['node', 'kimaki', 'session', 'search', 'auth timeout', '--all'],
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
expect(result.args[0]).toBe('auth timeout')
|
|
165
|
+
expect(result.options.all).toBe(true)
|
|
166
|
+
})
|
|
167
|
+
|
|
168
|
+
test('parses session search --days as a string', async () => {
|
|
169
|
+
const result = await parseWithGoke(
|
|
170
|
+
['node', 'kimaki', 'session', 'search', 'auth timeout', '--days', '0'],
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
expect(result.options.days).toBe('0')
|
|
174
|
+
})
|
|
175
|
+
|
|
134
176
|
test('keeps session export options as strings', async () => {
|
|
135
177
|
const sessionId = '001111222233334444'
|
|
136
178
|
const outPath = './tmp/session-events.jsonl'
|
|
@@ -172,6 +214,21 @@ describe('goke CLI ID parsing', () => {
|
|
|
172
214
|
expect(typeof result.args[0]).toBe('string')
|
|
173
215
|
})
|
|
174
216
|
|
|
217
|
+
test('parses empty --user on task edit so the stored user can be cleared', async () => {
|
|
218
|
+
const result = await parseWithGoke([
|
|
219
|
+
'node',
|
|
220
|
+
'kimaki',
|
|
221
|
+
'task',
|
|
222
|
+
'edit',
|
|
223
|
+
'11',
|
|
224
|
+
'--user',
|
|
225
|
+
'',
|
|
226
|
+
])
|
|
227
|
+
|
|
228
|
+
expect(result.args[0]).toBe('11')
|
|
229
|
+
expect(result.options.user).toBe('')
|
|
230
|
+
})
|
|
231
|
+
|
|
175
232
|
test('multioauth account remove parses index and email as strings', async () => {
|
|
176
233
|
const indexResult = await parseWithGoke(
|
|
177
234
|
['node', 'kimaki', 'multioauth', 'anthropic', 'remove', '2'],
|
|
@@ -194,13 +251,55 @@ describe('goke CLI ID parsing', () => {
|
|
|
194
251
|
expect(stdout).toContain('multioauth')
|
|
195
252
|
})
|
|
196
253
|
|
|
197
|
-
test('
|
|
254
|
+
test('merge-worktree parses strategy and target branch', async () => {
|
|
255
|
+
const result = await maintenanceCommands.parse(
|
|
256
|
+
[
|
|
257
|
+
'node',
|
|
258
|
+
'kimaki',
|
|
259
|
+
'merge-worktree',
|
|
260
|
+
'--strategy',
|
|
261
|
+
'squash',
|
|
262
|
+
'--target-branch',
|
|
263
|
+
'release',
|
|
264
|
+
'--thread',
|
|
265
|
+
'123456789012345678',
|
|
266
|
+
'--thread-name',
|
|
267
|
+
'feature work',
|
|
268
|
+
],
|
|
269
|
+
{ run: false },
|
|
270
|
+
)
|
|
271
|
+
|
|
272
|
+
expect({
|
|
273
|
+
command: maintenanceCommands.matchedCommandName,
|
|
274
|
+
strategy: result.options.strategy,
|
|
275
|
+
targetBranch: result.options.targetBranch,
|
|
276
|
+
thread: result.options.thread,
|
|
277
|
+
threadName: result.options.threadName,
|
|
278
|
+
}).toMatchInlineSnapshot(`
|
|
279
|
+
{
|
|
280
|
+
"command": "merge-worktree",
|
|
281
|
+
"strategy": "squash",
|
|
282
|
+
"targetBranch": "release",
|
|
283
|
+
"thread": "123456789012345678",
|
|
284
|
+
"threadName": "feature work",
|
|
285
|
+
}
|
|
286
|
+
`)
|
|
287
|
+
})
|
|
288
|
+
|
|
289
|
+
test('parses root bot boolean flags', async () => {
|
|
198
290
|
const result = await parseRootBotOptions([
|
|
199
291
|
'node',
|
|
200
292
|
'kimaki',
|
|
201
293
|
'--no-analytics',
|
|
294
|
+
'--skip-footer-mentions',
|
|
202
295
|
])
|
|
203
296
|
|
|
204
|
-
expect(result.options
|
|
297
|
+
expect(result.options).toMatchInlineSnapshot(`
|
|
298
|
+
{
|
|
299
|
+
"--": [],
|
|
300
|
+
"noAnalytics": true,
|
|
301
|
+
"skipFooterMentions": true,
|
|
302
|
+
}
|
|
303
|
+
`)
|
|
205
304
|
})
|
|
206
305
|
})
|
package/src/cli-runner.test.ts
CHANGED
|
@@ -77,4 +77,53 @@ describe('isTransientNetworkError', () => {
|
|
|
77
77
|
})
|
|
78
78
|
expect(isTransientNetworkError(error)).toBe(true)
|
|
79
79
|
})
|
|
80
|
+
|
|
81
|
+
test('treats undici connect timeouts as transient', () => {
|
|
82
|
+
const error = Object.assign(
|
|
83
|
+
new Error(
|
|
84
|
+
'Connect Timeout Error (attempted address: discord-gateway.kimaki.dev:443, timeout: 10000ms)',
|
|
85
|
+
),
|
|
86
|
+
{
|
|
87
|
+
name: 'ConnectTimeoutError',
|
|
88
|
+
code: 'UND_ERR_CONNECT_TIMEOUT',
|
|
89
|
+
},
|
|
90
|
+
)
|
|
91
|
+
expect(isTransientNetworkError(error)).toBe(true)
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
test('matches undici connect timeouts by message when code is missing', () => {
|
|
95
|
+
expect(
|
|
96
|
+
isTransientNetworkError(
|
|
97
|
+
new Error(
|
|
98
|
+
'Connect Timeout Error (attempted address: discord-gateway.kimaki.dev:443, timeout: 10000ms)',
|
|
99
|
+
),
|
|
100
|
+
),
|
|
101
|
+
).toBe(true)
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
test('walks cause chains for nested undici connect timeouts', () => {
|
|
105
|
+
const cause = Object.assign(
|
|
106
|
+
new Error(
|
|
107
|
+
'Connect Timeout Error (attempted address: discord-gateway.kimaki.dev:443, timeout: 10000ms)',
|
|
108
|
+
),
|
|
109
|
+
{
|
|
110
|
+
name: 'ConnectTimeoutError',
|
|
111
|
+
code: 'UND_ERR_CONNECT_TIMEOUT',
|
|
112
|
+
},
|
|
113
|
+
)
|
|
114
|
+
expect(
|
|
115
|
+
isTransientNetworkError(new Error('Failed to connect to Discord', { cause })),
|
|
116
|
+
).toBe(true)
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
test('treats other undici timeout and socket codes as transient', () => {
|
|
120
|
+
for (const [name, code] of [
|
|
121
|
+
['HeadersTimeoutError', 'UND_ERR_HEADERS_TIMEOUT'],
|
|
122
|
+
['BodyTimeoutError', 'UND_ERR_BODY_TIMEOUT'],
|
|
123
|
+
['SocketError', 'UND_ERR_SOCKET'],
|
|
124
|
+
] as const) {
|
|
125
|
+
const error = Object.assign(new Error(name), { name, code })
|
|
126
|
+
expect(isTransientNetworkError(error)).toBe(true)
|
|
127
|
+
}
|
|
128
|
+
})
|
|
80
129
|
})
|
package/src/cli-runner.ts
CHANGED
|
@@ -29,6 +29,7 @@ import {
|
|
|
29
29
|
getChannelDirectory,
|
|
30
30
|
startDiscordBot,
|
|
31
31
|
initializeOpencodeForDirectory,
|
|
32
|
+
assertCompatibleOpencodeVersion,
|
|
32
33
|
createProjectChannels,
|
|
33
34
|
createDefaultKimakiChannel,
|
|
34
35
|
type ChannelWithTags,
|
|
@@ -456,6 +457,10 @@ export function formatTaskScheduleLine(schedule: ParsedSendAt): string {
|
|
|
456
457
|
}
|
|
457
458
|
|
|
458
459
|
export const EXIT_NO_RESTART = 64
|
|
460
|
+
// Temporary failure (sysexits EX_TEMPFAIL). Wrapper retries forever with
|
|
461
|
+
// backoff and does not count this toward the crash-loop detector. Used when
|
|
462
|
+
// Discord login fails because the network is down.
|
|
463
|
+
export const EXIT_TEMPFAIL = 75
|
|
459
464
|
|
|
460
465
|
export type GuildMemberSearchResult = {
|
|
461
466
|
user: { id: string; username: string; global_name?: string }
|
|
@@ -542,6 +547,20 @@ const TRANSIENT_ERROR_CODES = new Set([
|
|
|
542
547
|
'DEPTH_ZERO_SELF_SIGNED_CERT',
|
|
543
548
|
'SELF_SIGNED_CERT_IN_CHAIN',
|
|
544
549
|
'ERR_TLS_CERT_ALTNAME_INVALID',
|
|
550
|
+
// undici fetch/WebSocket connect failures when the network is down.
|
|
551
|
+
// Without these, Discord login exits EXIT_NO_RESTART and the wrapper never
|
|
552
|
+
// comes back after a connect timeout to discord-gateway.kimaki.dev.
|
|
553
|
+
'UND_ERR_CONNECT_TIMEOUT',
|
|
554
|
+
'UND_ERR_HEADERS_TIMEOUT',
|
|
555
|
+
'UND_ERR_BODY_TIMEOUT',
|
|
556
|
+
'UND_ERR_SOCKET',
|
|
557
|
+
])
|
|
558
|
+
|
|
559
|
+
const TRANSIENT_ERROR_NAMES = new Set([
|
|
560
|
+
'ConnectTimeoutError',
|
|
561
|
+
'HeadersTimeoutError',
|
|
562
|
+
'BodyTimeoutError',
|
|
563
|
+
'SocketError',
|
|
545
564
|
])
|
|
546
565
|
|
|
547
566
|
const TRANSIENT_ERROR_MESSAGE_PATTERNS = [
|
|
@@ -549,12 +568,16 @@ const TRANSIENT_ERROR_MESSAGE_PATTERNS = [
|
|
|
549
568
|
/certificate has expired/i,
|
|
550
569
|
/self[- ]signed certificate/i,
|
|
551
570
|
/unable to get local issuer certificate/i,
|
|
571
|
+
/connect timeout error/i,
|
|
572
|
+
/headers timeout error/i,
|
|
573
|
+
/body timeout error/i,
|
|
552
574
|
]
|
|
553
575
|
|
|
554
576
|
export function isTransientNetworkError(error: unknown): boolean {
|
|
555
577
|
if (!(error instanceof Error)) return false
|
|
556
578
|
const code = (error as NodeJS.ErrnoException).code
|
|
557
579
|
if (code && TRANSIENT_ERROR_CODES.has(code)) return true
|
|
580
|
+
if (TRANSIENT_ERROR_NAMES.has(error.name)) return true
|
|
558
581
|
// Fallback when code is stripped by wrappers (discord.js sometimes rethrows by message only).
|
|
559
582
|
if (
|
|
560
583
|
TRANSIENT_ERROR_MESSAGE_PATTERNS.some((pattern) =>
|
|
@@ -1082,7 +1105,7 @@ export function showReadyMessage({
|
|
|
1082
1105
|
}
|
|
1083
1106
|
|
|
1084
1107
|
/**
|
|
1085
|
-
* Create
|
|
1108
|
+
* Create default channels in locally configured guilds, or proxy-authorized gateway guilds.
|
|
1086
1109
|
* Idempotent: skips guilds that already have the channel.
|
|
1087
1110
|
* Extracted so both the interactive and headless startup paths share the same logic.
|
|
1088
1111
|
*/
|
|
@@ -1101,9 +1124,15 @@ export async function ensureDefaultChannelsWithWelcome({
|
|
|
1101
1124
|
}): Promise<{ name: string; id: string; guildId: string }[]> {
|
|
1102
1125
|
if (process.env['KIMAKI_NO_DEFAULT_CHANNEL'] === '1') return []
|
|
1103
1126
|
|
|
1127
|
+
const localMappings = isGatewayMode ? [] : await findChannelsByDirectory({})
|
|
1104
1128
|
const created: { name: string; id: string; guildId: string }[] = []
|
|
1105
1129
|
for (const guild of guilds) {
|
|
1106
1130
|
try {
|
|
1131
|
+
if (!isGatewayMode) {
|
|
1132
|
+
// Match live channel IDs: older local mappings have no guild_id.
|
|
1133
|
+
const channels = await guild.channels.fetch()
|
|
1134
|
+
if (!localMappings.some((row) => channels.has(row.channel_id))) continue
|
|
1135
|
+
}
|
|
1107
1136
|
const result = await createDefaultKimakiChannel({
|
|
1108
1137
|
guild,
|
|
1109
1138
|
botName: discordClient.user?.username,
|
|
@@ -1586,6 +1615,12 @@ export async function run({
|
|
|
1586
1615
|
}),
|
|
1587
1616
|
])
|
|
1588
1617
|
|
|
1618
|
+
const opencodeVersionCheck = await assertCompatibleOpencodeVersion()
|
|
1619
|
+
if (opencodeVersionCheck instanceof Error) {
|
|
1620
|
+
cliLogger.error(opencodeVersionCheck.message)
|
|
1621
|
+
process.exit(EXIT_NO_RESTART)
|
|
1622
|
+
}
|
|
1623
|
+
|
|
1589
1624
|
|
|
1590
1625
|
if (store.getState().autoUpgradeEnabled) {
|
|
1591
1626
|
void backgroundUpgradeKimaki()
|
|
@@ -1757,12 +1792,14 @@ export async function run({
|
|
|
1757
1792
|
cliLogger.error(
|
|
1758
1793
|
'Error: ' + (error instanceof Error ? error.stack : String(error)),
|
|
1759
1794
|
)
|
|
1760
|
-
// Transient network errors (DNS down, gateway unreachable
|
|
1761
|
-
// the bin.ts wrapper to restart us after a delay.
|
|
1762
|
-
//
|
|
1795
|
+
// Transient network errors (DNS down, gateway unreachable, connect
|
|
1796
|
+
// timeout) should allow the bin.ts wrapper to restart us after a delay.
|
|
1797
|
+
// EXIT_TEMPFAIL skips the crash-loop detector so a long outage cannot
|
|
1798
|
+
// permanently stop the bot. Only truly fatal errors (bad token, invalid
|
|
1799
|
+
// intent, etc.) should use EXIT_NO_RESTART.
|
|
1763
1800
|
if (isTransientNetworkError(error)) {
|
|
1764
1801
|
cliLogger.error('Transient network error, exiting for wrapper restart...')
|
|
1765
|
-
process.exit(
|
|
1802
|
+
process.exit(EXIT_TEMPFAIL)
|
|
1766
1803
|
}
|
|
1767
1804
|
process.exit(EXIT_NO_RESTART)
|
|
1768
1805
|
}
|
|
@@ -1823,7 +1860,7 @@ export async function run({
|
|
|
1823
1860
|
)
|
|
1824
1861
|
}
|
|
1825
1862
|
|
|
1826
|
-
// Create default
|
|
1863
|
+
// Create default channels only in locally configured or gateway-authorized guilds.
|
|
1827
1864
|
// Runs after channel sync so existing channels are detected correctly.
|
|
1828
1865
|
try {
|
|
1829
1866
|
await ensureDefaultChannelsWithWelcome({
|
|
@@ -2030,7 +2067,7 @@ export async function run({
|
|
|
2030
2067
|
}
|
|
2031
2068
|
|
|
2032
2069
|
// Create default kimaki channel for general-purpose tasks.
|
|
2033
|
-
//
|
|
2070
|
+
// Only locally configured or gateway-authorized guilds are eligible.
|
|
2034
2071
|
const defaultChannelResults = await ensureDefaultChannelsWithWelcome({
|
|
2035
2072
|
guilds,
|
|
2036
2073
|
discordClient,
|
|
@@ -410,9 +410,9 @@ describe('kimaki send --channel thread creation', () => {
|
|
|
410
410
|
"--- from: user (cli-send-tester)
|
|
411
411
|
thread empty content seed
|
|
412
412
|
--- from: assistant (TestBot)
|
|
413
|
-
*using deterministic-provider/deterministic-v2*
|
|
414
|
-
|
|
415
|
-
*project ⋅ main ⋅
|
|
413
|
+
> *using deterministic-provider/deterministic-v2*
|
|
414
|
+
> caught-by-model
|
|
415
|
+
> *project ⋅ main ⋅ <1s ⋅ 0% ⋅ deterministic-v2* <@200000000000000830>
|
|
416
416
|
--- from: user (cli-send-tester)
|
|
417
417
|
--- from: assistant (TestBot)
|
|
418
418
|
I can see you sent a message, but Discord did not include its text.
|
|
@@ -570,13 +570,13 @@ describe('kimaki send --channel thread creation', () => {
|
|
|
570
570
|
Routes.threadMembers(threadData.id, TEST_USER_ID),
|
|
571
571
|
)
|
|
572
572
|
|
|
573
|
-
// Wait for the bot to reply
|
|
574
|
-
//
|
|
573
|
+
// Wait for the bot to reply (proves ThreadCreate handler picked up
|
|
574
|
+
// the starter message and started a session)
|
|
575
575
|
await waitForBotMessageContaining({
|
|
576
576
|
discord,
|
|
577
577
|
threadId: threadData.id,
|
|
578
578
|
userId: discord.botUserId,
|
|
579
|
-
text: '
|
|
579
|
+
text: 'ok',
|
|
580
580
|
timeout: 4_000,
|
|
581
581
|
})
|
|
582
582
|
|
|
@@ -585,7 +585,7 @@ describe('kimaki send --channel thread creation', () => {
|
|
|
585
585
|
discord,
|
|
586
586
|
threadId: threadData.id,
|
|
587
587
|
timeout: 4_000,
|
|
588
|
-
afterMessageIncludes: '
|
|
588
|
+
afterMessageIncludes: 'ok',
|
|
589
589
|
afterAuthorId: discord.botUserId,
|
|
590
590
|
})
|
|
591
591
|
|
|
@@ -598,11 +598,9 @@ describe('kimaki send --channel thread creation', () => {
|
|
|
598
598
|
})
|
|
599
599
|
expect(errorMessages).toHaveLength(0)
|
|
600
600
|
|
|
601
|
-
// Verify at least one
|
|
601
|
+
// Verify at least one reply exists (session produced output)
|
|
602
602
|
const botReplies = messages.filter((m) => {
|
|
603
|
-
return
|
|
604
|
-
m.author.id === discord.botUserId && m.content.startsWith('⬥')
|
|
605
|
-
)
|
|
603
|
+
return m.author.id === discord.botUserId
|
|
606
604
|
})
|
|
607
605
|
expect(botReplies.length).toBeGreaterThanOrEqual(1)
|
|
608
606
|
},
|
package/src/cli.ts
CHANGED
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
} from './config.js'
|
|
17
17
|
import { getCurrentVersion } from './upgrade.js'
|
|
18
18
|
import { store } from './store.js'
|
|
19
|
+
import { publicOpencodeBindRequiresPassword } from './opencode.js'
|
|
19
20
|
import multioauthCommands from './commands/multioauth.js'
|
|
20
21
|
import botCommands from './cli-commands/bot.js'
|
|
21
22
|
import maintenanceCommands from './cli-commands/maintenance.js'
|
|
@@ -73,6 +74,10 @@ cli
|
|
|
73
74
|
'--no-critique',
|
|
74
75
|
'Disable automatic diff upload to critique.work in system prompts',
|
|
75
76
|
)
|
|
77
|
+
.option(
|
|
78
|
+
'--skip-footer-mentions',
|
|
79
|
+
'Do not mention the thread creator in final session footers',
|
|
80
|
+
)
|
|
76
81
|
.option(
|
|
77
82
|
'--auto-restart',
|
|
78
83
|
'Automatically restart the bot on crash or OOM kill',
|
|
@@ -98,6 +103,14 @@ cli
|
|
|
98
103
|
'Disable anonymous product analytics (Strada). Same as KIMAKI_STRADA_ENABLED=0',
|
|
99
104
|
)
|
|
100
105
|
.option('--no-auto-upgrade', 'Disable background auto-upgrade on startup')
|
|
106
|
+
.option(
|
|
107
|
+
'--opencode-hostname <host>',
|
|
108
|
+
'Hostname the OpenCode server listens on (default: 127.0.0.1). Use 0.0.0.0 to expose it on a VPS',
|
|
109
|
+
)
|
|
110
|
+
.option(
|
|
111
|
+
'--opencode-port <port>',
|
|
112
|
+
'Port the OpenCode server listens on (default: a random free port)',
|
|
113
|
+
)
|
|
101
114
|
.option(
|
|
102
115
|
'--gateway',
|
|
103
116
|
'Force gateway mode (use the gateway Kimaki bot instead of a self-hosted bot)',
|
|
@@ -145,6 +158,7 @@ cli
|
|
|
145
158
|
verbosity?: string
|
|
146
159
|
mentionMode?: boolean
|
|
147
160
|
noCritique?: boolean
|
|
161
|
+
skipFooterMentions?: boolean
|
|
148
162
|
allowAllUsers?: boolean
|
|
149
163
|
restrictDirectories?: boolean
|
|
150
164
|
permissionTimeoutMinutes?: string
|
|
@@ -157,6 +171,8 @@ cli
|
|
|
157
171
|
allowMention?: Array<'users' | 'roles' | 'everyone'>
|
|
158
172
|
enableSkill?: string[]
|
|
159
173
|
disableSkill?: string[]
|
|
174
|
+
opencodeHostname?: string
|
|
175
|
+
opencodePort?: string
|
|
160
176
|
}) => {
|
|
161
177
|
// Guard: only one kimaki bot process can run per lock port. Agents may run
|
|
162
178
|
// a second dev bot only when they explicitly choose a different lock port.
|
|
@@ -269,12 +285,36 @@ cli
|
|
|
269
285
|
return parsed * 60_000
|
|
270
286
|
})()
|
|
271
287
|
|
|
288
|
+
const opencodeHostname = options.opencodeHostname?.trim() || undefined
|
|
289
|
+
const opencodePort = (() => {
|
|
290
|
+
if (!options.opencodePort) return undefined
|
|
291
|
+
const parsed = Number(options.opencodePort)
|
|
292
|
+
if (!Number.isInteger(parsed) || parsed < 1 || parsed > 65535) {
|
|
293
|
+
cliLogger.error(
|
|
294
|
+
`Invalid --opencode-port: ${options.opencodePort}. Must be an integer between 1 and 65535.`,
|
|
295
|
+
)
|
|
296
|
+
process.exit(EXIT_NO_RESTART)
|
|
297
|
+
}
|
|
298
|
+
return parsed
|
|
299
|
+
})()
|
|
300
|
+
|
|
301
|
+
if (
|
|
302
|
+
publicOpencodeBindRequiresPassword({ hostname: opencodeHostname }) &&
|
|
303
|
+
!process.env.OPENCODE_SERVER_PASSWORD
|
|
304
|
+
) {
|
|
305
|
+
cliLogger.error(
|
|
306
|
+
`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.`,
|
|
307
|
+
)
|
|
308
|
+
process.exit(EXIT_NO_RESTART)
|
|
309
|
+
}
|
|
310
|
+
|
|
272
311
|
store.setState({
|
|
273
312
|
...(defaultVerbosity && {
|
|
274
313
|
defaultVerbosity,
|
|
275
314
|
}),
|
|
276
315
|
...(options.mentionMode && { defaultMentionMode: true }),
|
|
277
316
|
...(options.noCritique && { critiqueEnabled: false }),
|
|
317
|
+
...(options.skipFooterMentions && { footerMentionsEnabled: false }),
|
|
278
318
|
...(options.allowAllUsers && { allowAllUsers: true }),
|
|
279
319
|
...(options.restrictDirectories && { restrictExternalDirectories: true }),
|
|
280
320
|
...(permissionTimeoutMs !== undefined && { permissionTimeoutMs }),
|
|
@@ -283,6 +323,8 @@ cli
|
|
|
283
323
|
...(enabledSkills.length > 0 && { enabledSkills }),
|
|
284
324
|
...(disabledSkills.length > 0 && { disabledSkills }),
|
|
285
325
|
...(options.allowMention && { allowedMentions: options.allowMention }),
|
|
326
|
+
...(opencodeHostname && { opencodeHostname }),
|
|
327
|
+
...(opencodePort !== undefined && { opencodePort }),
|
|
286
328
|
})
|
|
287
329
|
|
|
288
330
|
if (enabledSkills.length > 0) {
|
|
@@ -323,6 +365,11 @@ cli
|
|
|
323
365
|
'Critique disabled: diffs will not be auto-uploaded to critique.work',
|
|
324
366
|
)
|
|
325
367
|
}
|
|
368
|
+
if (options.skipFooterMentions) {
|
|
369
|
+
cliLogger.log(
|
|
370
|
+
'Footer mentions disabled: final session footers will not mention thread creators',
|
|
371
|
+
)
|
|
372
|
+
}
|
|
326
373
|
if (options.noAutoUpgrade) {
|
|
327
374
|
cliLogger.log(
|
|
328
375
|
'Auto-upgrade disabled: kimaki will not check for updates on startup',
|
|
@@ -339,6 +386,12 @@ cli
|
|
|
339
386
|
'Anonymous product analytics disabled (--no-analytics)',
|
|
340
387
|
)
|
|
341
388
|
}
|
|
389
|
+
if (opencodeHostname) {
|
|
390
|
+
cliLogger.log(`OpenCode server hostname: ${opencodeHostname}`)
|
|
391
|
+
}
|
|
392
|
+
if (opencodePort !== undefined) {
|
|
393
|
+
cliLogger.log(`OpenCode server port: ${opencodePort}`)
|
|
394
|
+
}
|
|
342
395
|
|
|
343
396
|
if (options.installUrl) {
|
|
344
397
|
await printDiscordInstallUrlAndExit({
|
package/src/commands/abort.ts
CHANGED
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
type ThreadChannel,
|
|
8
8
|
} from 'discord.js'
|
|
9
9
|
import type { CommandContext } from './types.js'
|
|
10
|
-
import { getThreadSession } from '../database.js'
|
|
10
|
+
import { cancelSessionSleepForThread, getThreadSession } from '../database.js'
|
|
11
11
|
import { getOpencodeClient, initializeOpencodeForDirectory } from '../opencode.js'
|
|
12
12
|
import {
|
|
13
13
|
resolveWorkingDirectory,
|
|
@@ -66,6 +66,11 @@ export async function handleAbortCommand({
|
|
|
66
66
|
return
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
+
// Aborting is an explicit "stop waiting" from the user, and it never goes
|
|
70
|
+
// through enqueueIncoming, so cancel any pending sleep here too. Otherwise the
|
|
71
|
+
// wake would still fire later and restart a session the user just stopped.
|
|
72
|
+
await cancelSessionSleepForThread({ threadId: channel.id })
|
|
73
|
+
|
|
69
74
|
// abortActiveRun delegates to session.abort(), run settlement stays event-driven.
|
|
70
75
|
const runtime = getRuntime(channel.id)
|
|
71
76
|
if (runtime) {
|
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
sendThreadMessage,
|
|
20
20
|
} from '../discord-utils.js'
|
|
21
21
|
import { createLogger } from '../logger.js'
|
|
22
|
+
import { QUEUE_PREFIX } from '../message-formatting.js'
|
|
22
23
|
import { notifyError } from '../sentry.js'
|
|
23
24
|
import {
|
|
24
25
|
getOrCreateRuntime,
|
|
@@ -331,7 +332,7 @@ export async function handleActionButton(
|
|
|
331
332
|
|
|
332
333
|
await sendThreadMessage(
|
|
333
334
|
thread,
|
|
334
|
-
|
|
335
|
+
`${QUEUE_PREFIX}**${username}:** ${button.label}`,
|
|
335
336
|
)
|
|
336
337
|
|
|
337
338
|
try {
|