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
|
@@ -1,4 +1,23 @@
|
|
|
1
|
-
//
|
|
1
|
+
// LEGACY rotation, superseded by @subrouter/opencode. See oauth-rotation-shared.ts.
|
|
2
|
+
// Helpers for Anthropic OAuth account and Claude Code client identity.
|
|
3
|
+
|
|
4
|
+
const CLAUDE_CODE_USER_AGENT = 'claude-cli/2.1.257 (external, cli)'
|
|
5
|
+
|
|
6
|
+
export function applyClaudeCodeRequestIdentity({
|
|
7
|
+
headers,
|
|
8
|
+
accessToken,
|
|
9
|
+
}: {
|
|
10
|
+
headers: Headers
|
|
11
|
+
accessToken: string
|
|
12
|
+
}) {
|
|
13
|
+
headers.set('authorization', `Bearer ${accessToken}`)
|
|
14
|
+
headers.set(
|
|
15
|
+
'user-agent',
|
|
16
|
+
process.env.OPENCODE_ANTHROPIC_USER_AGENT || CLAUDE_CODE_USER_AGENT,
|
|
17
|
+
)
|
|
18
|
+
headers.set('x-app', 'cli')
|
|
19
|
+
return headers
|
|
20
|
+
}
|
|
2
21
|
|
|
3
22
|
export type AnthropicAccountIdentity = {
|
|
4
23
|
email?: string
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// Tests Anthropic system prompt rewriting so project instructions survive OpenCode prompt layout changes.
|
|
2
2
|
|
|
3
3
|
import { describe, expect, test } from 'vitest'
|
|
4
|
+
import { applyClaudeCodeRequestIdentity } from './anthropic-account-identity.js'
|
|
4
5
|
import { replacer } from './anthropic-auth-plugin.js'
|
|
5
6
|
|
|
6
7
|
async function transformSystem(systemText: string) {
|
|
@@ -18,6 +19,24 @@ async function transformSystem(systemText: string) {
|
|
|
18
19
|
return output.system.join('\n')
|
|
19
20
|
}
|
|
20
21
|
|
|
22
|
+
describe('Anthropic OAuth request identity', () => {
|
|
23
|
+
test('applies the current external Claude Code client headers', () => {
|
|
24
|
+
const headers = applyClaudeCodeRequestIdentity({
|
|
25
|
+
headers: new Headers({ Accept: 'application/json' }),
|
|
26
|
+
accessToken: 'access-token',
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
expect(Object.fromEntries(headers.entries())).toMatchInlineSnapshot(`
|
|
30
|
+
{
|
|
31
|
+
"accept": "application/json",
|
|
32
|
+
"authorization": "Bearer access-token",
|
|
33
|
+
"user-agent": "claude-cli/2.1.257 (external, cli)",
|
|
34
|
+
"x-app": "cli",
|
|
35
|
+
}
|
|
36
|
+
`)
|
|
37
|
+
})
|
|
38
|
+
})
|
|
39
|
+
|
|
21
40
|
describe('Anthropic system prompt rewriting', () => {
|
|
22
41
|
test('preserves instructions when OpenCode places them before skills', async () => {
|
|
23
42
|
const transformed = await transformSystem(`You are OpenCode, the best coding agent on the planet.
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
// LEGACY rotation, superseded by @subrouter/opencode. See oauth-rotation-shared.ts.
|
|
1
2
|
/**
|
|
2
3
|
* Anthropic OAuth authentication plugin for OpenCode.
|
|
3
4
|
*
|
|
@@ -41,6 +42,7 @@ import {
|
|
|
41
42
|
withAuthStateLock,
|
|
42
43
|
} from "./anthropic-auth-state.js";
|
|
43
44
|
import {
|
|
45
|
+
applyClaudeCodeRequestIdentity,
|
|
44
46
|
extractAnthropicAccountIdentity,
|
|
45
47
|
type AnthropicAccountIdentity,
|
|
46
48
|
} from "./anthropic-account-identity.js";
|
|
@@ -90,7 +92,6 @@ const REDIRECT_URI = `http://localhost:${CALLBACK_PORT}${CALLBACK_PATH}`;
|
|
|
90
92
|
const SCOPES =
|
|
91
93
|
"org:create_api_key user:profile user:inference user:sessions:claude_code user:mcp_servers user:file_upload";
|
|
92
94
|
const OAUTH_TIMEOUT_MS = 5 * 60 * 1000;
|
|
93
|
-
const CLAUDE_CODE_VERSION = "2.1.75";
|
|
94
95
|
const CLAUDE_CODE_IDENTITY =
|
|
95
96
|
"You are Claude Code, Anthropic's official CLI for Claude.";
|
|
96
97
|
|
|
@@ -344,14 +345,12 @@ async function fetchAnthropicAccountIdentity(accessToken: string) {
|
|
|
344
345
|
for (const url of urls) {
|
|
345
346
|
const responseText = await requestText(url, {
|
|
346
347
|
method: "GET",
|
|
347
|
-
headers:
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
"x-app": "cli",
|
|
354
|
-
},
|
|
348
|
+
headers: Object.fromEntries(
|
|
349
|
+
applyClaudeCodeRequestIdentity({
|
|
350
|
+
headers: new Headers({ Accept: "application/json" }),
|
|
351
|
+
accessToken,
|
|
352
|
+
}).entries(),
|
|
353
|
+
),
|
|
355
354
|
}).catch(() => {
|
|
356
355
|
return undefined;
|
|
357
356
|
});
|
|
@@ -1087,13 +1086,10 @@ const AnthropicAuthPlugin: Plugin = async ({ serverUrl, directory }) => {
|
|
|
1087
1086
|
"anthropic-dangerous-direct-browser-access",
|
|
1088
1087
|
"true",
|
|
1089
1088
|
);
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
`claude-cli/${CLAUDE_CODE_VERSION}`,
|
|
1095
|
-
);
|
|
1096
|
-
requestHeaders.set("x-app", "cli");
|
|
1089
|
+
applyClaudeCodeRequestIdentity({
|
|
1090
|
+
headers: requestHeaders,
|
|
1091
|
+
accessToken: auth.access,
|
|
1092
|
+
});
|
|
1097
1093
|
requestHeaders.delete("x-api-key");
|
|
1098
1094
|
|
|
1099
1095
|
return fetch(input, {
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { describe, expect, test } from 'vitest'
|
|
2
|
+
import {
|
|
3
|
+
extendBashToolDefinition,
|
|
4
|
+
type ToolDefinitionOutput,
|
|
5
|
+
} from './bash-tool-schema-plugin.js'
|
|
6
|
+
|
|
7
|
+
describe('extendBashToolDefinition', () => {
|
|
8
|
+
test('keeps parameters and assigns jsonSchema from bashParameters', () => {
|
|
9
|
+
const parameters = { keep: true }
|
|
10
|
+
const output: ToolDefinitionOutput = {
|
|
11
|
+
description: 'Execute a shell command',
|
|
12
|
+
parameters,
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
extendBashToolDefinition(output)
|
|
16
|
+
|
|
17
|
+
expect(output.parameters).toBe(parameters)
|
|
18
|
+
expect(output.jsonSchema).toMatchInlineSnapshot(`
|
|
19
|
+
{
|
|
20
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
21
|
+
"additionalProperties": false,
|
|
22
|
+
"properties": {
|
|
23
|
+
"command": {
|
|
24
|
+
"description": "The command to execute",
|
|
25
|
+
"type": "string",
|
|
26
|
+
},
|
|
27
|
+
"description": {
|
|
28
|
+
"description": "Short 5-10 word summary shown in Discord when the command is longer than 50 characters",
|
|
29
|
+
"type": "string",
|
|
30
|
+
},
|
|
31
|
+
"hasSideEffect": {
|
|
32
|
+
"description": "True if the command writes files, modifies state, installs packages, or triggers external effects",
|
|
33
|
+
"type": "boolean",
|
|
34
|
+
},
|
|
35
|
+
"timeout": {
|
|
36
|
+
"description": "Optional timeout in milliseconds",
|
|
37
|
+
"maximum": 9007199254740991,
|
|
38
|
+
"minimum": -9007199254740991,
|
|
39
|
+
"type": "integer",
|
|
40
|
+
},
|
|
41
|
+
"workdir": {
|
|
42
|
+
"description": "Working directory. Defaults to the current directory.",
|
|
43
|
+
"type": "string",
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
"required": [
|
|
47
|
+
"command",
|
|
48
|
+
"description",
|
|
49
|
+
"hasSideEffect",
|
|
50
|
+
],
|
|
51
|
+
"type": "object",
|
|
52
|
+
}
|
|
53
|
+
`)
|
|
54
|
+
})
|
|
55
|
+
})
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
// Adds description and hasSideEffect to the built-in bash tool schema.
|
|
2
|
+
// Uses tool.definition so OpenCode keeps the real bash execute.
|
|
3
|
+
// Assign jsonSchema only. Leave parameters as the Effect Schema used to decode
|
|
4
|
+
// args before execute. Extra fields stay in the model-facing schema and in
|
|
5
|
+
// Discord input; they never reach the shell.
|
|
6
|
+
|
|
7
|
+
import type { Plugin } from '@opencode-ai/plugin'
|
|
8
|
+
import { z } from 'zod'
|
|
9
|
+
|
|
10
|
+
export const bashParameters = z.object({
|
|
11
|
+
command: z.string().describe('The command to execute'),
|
|
12
|
+
description: z
|
|
13
|
+
.string()
|
|
14
|
+
.describe(
|
|
15
|
+
'Short 5-10 word summary shown in Discord when the command is longer than 50 characters',
|
|
16
|
+
),
|
|
17
|
+
hasSideEffect: z
|
|
18
|
+
.boolean()
|
|
19
|
+
.describe(
|
|
20
|
+
'True if the command writes files, modifies state, installs packages, or triggers external effects',
|
|
21
|
+
),
|
|
22
|
+
workdir: z
|
|
23
|
+
.string()
|
|
24
|
+
.optional()
|
|
25
|
+
.describe('Working directory. Defaults to the current directory.'),
|
|
26
|
+
timeout: z.number().int().optional().describe('Optional timeout in milliseconds'),
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
export type ToolDefinitionOutput = {
|
|
30
|
+
description: string
|
|
31
|
+
parameters: unknown
|
|
32
|
+
jsonSchema?: unknown
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function extendBashToolDefinition(output: ToolDefinitionOutput) {
|
|
36
|
+
output.jsonSchema = z.toJSONSchema(bashParameters)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export const bashToolSchemaPlugin: Plugin = async () => {
|
|
40
|
+
return {
|
|
41
|
+
'tool.definition': async (input, output) => {
|
|
42
|
+
if (input.toolID !== 'bash') return
|
|
43
|
+
extendBashToolDefinition(output)
|
|
44
|
+
},
|
|
45
|
+
}
|
|
46
|
+
}
|
package/src/bin.ts
CHANGED
|
@@ -35,16 +35,61 @@ if (process.env.__KIMAKI_CHILD || isSubcommand || isHelpFlag) {
|
|
|
35
35
|
console.error('no subcommand detected. kimaki will automatically restart on crash')
|
|
36
36
|
console.error()
|
|
37
37
|
const EXIT_NO_RESTART = 64
|
|
38
|
+
// Keep in sync with EXIT_TEMPFAIL in cli-runner.ts. Network-down login
|
|
39
|
+
// failures use this so a long outage cannot trip the crash-loop detector.
|
|
40
|
+
const EXIT_TEMPFAIL = 75
|
|
38
41
|
const MAX_RAPID_RESTARTS = 5
|
|
39
42
|
const RAPID_RESTART_WINDOW_MS = 60_000
|
|
40
43
|
const RESTART_DELAY_MS = 2_000
|
|
41
44
|
|
|
45
|
+
const CHILD_EXIT_DEADLINE_MS = 15_000
|
|
42
46
|
const restartTimestamps: number[] = []
|
|
47
|
+
let tempFailAttempts = 0
|
|
43
48
|
let child: ReturnType<typeof spawn> | null = null
|
|
44
49
|
// Track when we forwarded a termination signal so we don't restart after graceful shutdown
|
|
45
50
|
let shutdownRequested = false
|
|
51
|
+
let forceKillTimer: ReturnType<typeof setTimeout> | null = null
|
|
52
|
+
let scheduledRestart: ReturnType<typeof setTimeout> | null = null
|
|
53
|
+
|
|
54
|
+
function clearForceKillTimer() {
|
|
55
|
+
if (!forceKillTimer) return
|
|
56
|
+
clearTimeout(forceKillTimer)
|
|
57
|
+
forceKillTimer = null
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function clearScheduledRestart() {
|
|
61
|
+
if (!scheduledRestart) return
|
|
62
|
+
clearTimeout(scheduledRestart)
|
|
63
|
+
scheduledRestart = null
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function killChild(signal: NodeJS.Signals) {
|
|
67
|
+
const target = child
|
|
68
|
+
if (!target) {
|
|
69
|
+
if (shutdownRequested) {
|
|
70
|
+
clearScheduledRestart()
|
|
71
|
+
process.exit(0)
|
|
72
|
+
}
|
|
73
|
+
return
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const sent = target.kill(signal)
|
|
77
|
+
if (!sent || forceKillTimer) return
|
|
78
|
+
|
|
79
|
+
forceKillTimer = setTimeout(() => {
|
|
80
|
+
forceKillTimer = null
|
|
81
|
+
if (child !== target) return
|
|
82
|
+
console.error(
|
|
83
|
+
`[kimaki] Child did not exit within ${CHILD_EXIT_DEADLINE_MS / 1000}s, force-killing it`,
|
|
84
|
+
)
|
|
85
|
+
target.kill('SIGKILL')
|
|
86
|
+
}, CHILD_EXIT_DEADLINE_MS)
|
|
87
|
+
forceKillTimer.unref()
|
|
88
|
+
}
|
|
46
89
|
|
|
47
90
|
function start() {
|
|
91
|
+
if (shutdownRequested) return
|
|
92
|
+
scheduledRestart = null
|
|
48
93
|
if (!fs.existsSync(HEAP_SNAPSHOT_DIR)) {
|
|
49
94
|
fs.mkdirSync(HEAP_SNAPSHOT_DIR, { recursive: true })
|
|
50
95
|
}
|
|
@@ -53,7 +98,7 @@ if (process.env.__KIMAKI_CHILD || isSubcommand || isHelpFlag) {
|
|
|
53
98
|
`--diagnostic-dir=${HEAP_SNAPSHOT_DIR}`,
|
|
54
99
|
]
|
|
55
100
|
const args = [...heapArgs, ...process.execArgv, ...process.argv.slice(1)]
|
|
56
|
-
|
|
101
|
+
const currentChild = spawn(
|
|
57
102
|
process.argv[0]!,
|
|
58
103
|
args,
|
|
59
104
|
{
|
|
@@ -61,41 +106,47 @@ if (process.env.__KIMAKI_CHILD || isSubcommand || isHelpFlag) {
|
|
|
61
106
|
env: { ...process.env, __KIMAKI_CHILD: '1' },
|
|
62
107
|
},
|
|
63
108
|
)
|
|
109
|
+
child = currentChild
|
|
64
110
|
|
|
65
|
-
|
|
111
|
+
currentChild.on('exit', (code, signal) => {
|
|
112
|
+
if (child === currentChild) child = null
|
|
113
|
+
clearForceKillTimer()
|
|
66
114
|
if (code === 0 || code === EXIT_NO_RESTART || shutdownRequested) {
|
|
67
115
|
process.exit(code ?? 0)
|
|
68
116
|
return
|
|
69
117
|
}
|
|
70
118
|
|
|
71
119
|
const now = Date.now()
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
restartTimestamps.
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
120
|
+
const isTempFail = code === EXIT_TEMPFAIL
|
|
121
|
+
if (!isTempFail) {
|
|
122
|
+
restartTimestamps.push(now)
|
|
123
|
+
while (
|
|
124
|
+
restartTimestamps.length > 0 &&
|
|
125
|
+
restartTimestamps[0]! < now - RAPID_RESTART_WINDOW_MS
|
|
126
|
+
) {
|
|
127
|
+
restartTimestamps.shift()
|
|
128
|
+
}
|
|
79
129
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
130
|
+
if (restartTimestamps.length > MAX_RAPID_RESTARTS) {
|
|
131
|
+
console.error(
|
|
132
|
+
`[kimaki] Crash loop detected (${MAX_RAPID_RESTARTS} crashes in ${RAPID_RESTART_WINDOW_MS / 1000}s), exiting`,
|
|
133
|
+
)
|
|
134
|
+
process.exit(1)
|
|
135
|
+
return
|
|
136
|
+
}
|
|
86
137
|
}
|
|
87
138
|
|
|
88
139
|
const reason = signal ? `signal ${signal}` : `code ${code}`
|
|
89
140
|
// Progressive backoff: 2s, 4s, 8s, 16s, capped at 30s.
|
|
90
141
|
// Prevents hammering DNS/gateway during sustained network outages.
|
|
91
|
-
const
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
)
|
|
142
|
+
const backoffStep = isTempFail
|
|
143
|
+
? Math.min(tempFailAttempts++, 4)
|
|
144
|
+
: restartTimestamps.length - 1
|
|
145
|
+
const delay = Math.min(RESTART_DELAY_MS * 2 ** backoffStep, 30_000)
|
|
95
146
|
console.error(
|
|
96
147
|
`[kimaki] Process exited with ${reason}, restarting in ${(delay / 1000).toFixed(0)}s...`,
|
|
97
148
|
)
|
|
98
|
-
setTimeout(start, delay)
|
|
149
|
+
scheduledRestart = setTimeout(start, delay)
|
|
99
150
|
})
|
|
100
151
|
}
|
|
101
152
|
|
|
@@ -104,14 +155,15 @@ if (process.env.__KIMAKI_CHILD || isSubcommand || isHelpFlag) {
|
|
|
104
155
|
for (const sig of ['SIGTERM', 'SIGINT'] as const) {
|
|
105
156
|
process.on(sig, () => {
|
|
106
157
|
shutdownRequested = true
|
|
107
|
-
|
|
108
|
-
})
|
|
109
|
-
}
|
|
110
|
-
for (const sig of ['SIGUSR1', 'SIGUSR2'] as const) {
|
|
111
|
-
process.on(sig, () => {
|
|
112
|
-
child?.kill(sig)
|
|
158
|
+
killChild(sig)
|
|
113
159
|
})
|
|
114
160
|
}
|
|
161
|
+
process.on('SIGUSR1', () => {
|
|
162
|
+
child?.kill('SIGUSR1')
|
|
163
|
+
})
|
|
164
|
+
process.on('SIGUSR2', () => {
|
|
165
|
+
killChild('SIGUSR2')
|
|
166
|
+
})
|
|
115
167
|
|
|
116
168
|
start()
|
|
117
169
|
}
|
|
@@ -87,8 +87,8 @@ cli
|
|
|
87
87
|
|
|
88
88
|
cli
|
|
89
89
|
.command(
|
|
90
|
-
'worktree
|
|
91
|
-
'Merge worktree
|
|
90
|
+
'merge-worktree',
|
|
91
|
+
'Merge the current worktree into a target branch',
|
|
92
92
|
)
|
|
93
93
|
.option('-d, --directory <path>', 'Worktree directory (defaults to cwd)')
|
|
94
94
|
.option(
|
|
@@ -99,14 +99,30 @@ cli
|
|
|
99
99
|
'-n, --name <name>',
|
|
100
100
|
'Worktree/branch name (auto-detected from branch)',
|
|
101
101
|
)
|
|
102
|
+
.option(
|
|
103
|
+
'--strategy [strategy]',
|
|
104
|
+
z.enum(['rebase', 'squash'])
|
|
105
|
+
.default('rebase')
|
|
106
|
+
.describe('Keep commits or squash them into one'),
|
|
107
|
+
)
|
|
108
|
+
.option(
|
|
109
|
+
'--target-branch [branch]',
|
|
110
|
+
z.string()
|
|
111
|
+
.optional()
|
|
112
|
+
.describe('Branch to merge into (defaults to origin/HEAD or main)'),
|
|
113
|
+
)
|
|
114
|
+
.option(
|
|
115
|
+
'--thread [threadId]',
|
|
116
|
+
z.string().optional().describe('Discord thread to mark as merged'),
|
|
117
|
+
)
|
|
118
|
+
.option(
|
|
119
|
+
'--thread-name [name]',
|
|
120
|
+
z.string().optional().describe('Discord thread name after merging'),
|
|
121
|
+
)
|
|
102
122
|
.action(
|
|
103
|
-
async (options
|
|
104
|
-
directory?: string
|
|
105
|
-
mainRepo?: string
|
|
106
|
-
name?: string
|
|
107
|
-
}) => {
|
|
123
|
+
async (options) => {
|
|
108
124
|
try {
|
|
109
|
-
const { mergeWorktree } = await import('../worktrees.js')
|
|
125
|
+
const { formatMergeWorktreeError, mergeWorktree } = await import('../worktrees.js')
|
|
110
126
|
const worktreeDir = path.resolve(options.directory || '.')
|
|
111
127
|
|
|
112
128
|
// Auto-detect main repo: find the main worktree's toplevel.
|
|
@@ -119,7 +135,10 @@ cli
|
|
|
119
135
|
try {
|
|
120
136
|
// `git worktree list --porcelain` first line is always the main worktree
|
|
121
137
|
const { stdout } = await execAsync(
|
|
122
|
-
|
|
138
|
+
{
|
|
139
|
+
command: 'git',
|
|
140
|
+
args: ['-C', worktreeDir, 'worktree', 'list', '--porcelain'],
|
|
141
|
+
},
|
|
123
142
|
)
|
|
124
143
|
const firstLine = stdout.split('\n')[0] || ''
|
|
125
144
|
// Format: "worktree /path/to/main"
|
|
@@ -127,7 +146,10 @@ cli
|
|
|
127
146
|
} catch {
|
|
128
147
|
// Fallback: derive from git common dir
|
|
129
148
|
const { stdout: commonDir } = await execAsync(
|
|
130
|
-
|
|
149
|
+
{
|
|
150
|
+
command: 'git',
|
|
151
|
+
args: ['-C', worktreeDir, 'rev-parse', '--git-common-dir'],
|
|
152
|
+
},
|
|
131
153
|
)
|
|
132
154
|
const resolved = path.isAbsolute(commonDir.trim())
|
|
133
155
|
? commonDir.trim()
|
|
@@ -141,7 +163,10 @@ cli
|
|
|
141
163
|
if (!worktreeName) {
|
|
142
164
|
try {
|
|
143
165
|
const { stdout } = await execAsync(
|
|
144
|
-
|
|
166
|
+
{
|
|
167
|
+
command: 'git',
|
|
168
|
+
args: ['-C', worktreeDir, 'symbolic-ref', '--short', 'HEAD'],
|
|
169
|
+
},
|
|
145
170
|
)
|
|
146
171
|
worktreeName = stdout.trim()
|
|
147
172
|
} catch {
|
|
@@ -159,13 +184,15 @@ cli
|
|
|
159
184
|
worktreeDir,
|
|
160
185
|
mainRepoDir,
|
|
161
186
|
worktreeName,
|
|
187
|
+
strategy: options.strategy,
|
|
188
|
+
targetBranch: options.targetBranch,
|
|
162
189
|
onProgress: (msg) => {
|
|
163
190
|
cliLogger.log(msg)
|
|
164
191
|
},
|
|
165
192
|
})
|
|
166
193
|
|
|
167
194
|
if (result instanceof Error) {
|
|
168
|
-
cliLogger.error(
|
|
195
|
+
cliLogger.error(formatMergeWorktreeError(result, { maxLength: 8000 }))
|
|
169
196
|
if (result instanceof RebaseConflictError) {
|
|
170
197
|
cliLogger.log(
|
|
171
198
|
'Resolve the rebase conflicts, then run this command again.',
|
|
@@ -174,9 +201,28 @@ cli
|
|
|
174
201
|
process.exit(1)
|
|
175
202
|
}
|
|
176
203
|
|
|
204
|
+
const action = options.strategy === 'squash' ? 'Squashed' : 'Merged'
|
|
177
205
|
cliLogger.log(
|
|
178
|
-
|
|
206
|
+
`${action} ${result.branchName} into ${result.defaultBranch} @ ${result.shortSha} (${result.commitCount} source commit${result.commitCount === 1 ? '' : 's'})`,
|
|
179
207
|
)
|
|
208
|
+
if (options.thread && options.threadName) {
|
|
209
|
+
const credentials = await resolveBotCredentials().catch((e) => {
|
|
210
|
+
return new Error('Failed to resolve Discord credentials', { cause: e })
|
|
211
|
+
})
|
|
212
|
+
if (credentials instanceof Error) {
|
|
213
|
+
cliLogger.warn(`Could not update the thread title: ${credentials.message}`)
|
|
214
|
+
} else {
|
|
215
|
+
const rest = createDiscordRest(credentials.token)
|
|
216
|
+
const renameResult = await rest
|
|
217
|
+
.patch(Routes.channel(options.thread), {
|
|
218
|
+
body: { name: options.threadName },
|
|
219
|
+
})
|
|
220
|
+
.catch((e) => new Error('Discord rejected the thread rename', { cause: e }))
|
|
221
|
+
if (renameResult instanceof Error) {
|
|
222
|
+
cliLogger.warn(`Could not update the thread title: ${renameResult.message}`)
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
180
226
|
process.exit(0)
|
|
181
227
|
} catch (error) {
|
|
182
228
|
cliLogger.error(
|
|
@@ -13,7 +13,7 @@ import { spawn, execSync } from 'node:child_process'
|
|
|
13
13
|
import { createLogger, LogPrefix, initLogFile } from '../logger.js'
|
|
14
14
|
import { createDiscordClient, initDatabase, getChannelDirectory, initializeOpencodeForDirectory, createProjectChannels } from '../discord-bot.js'
|
|
15
15
|
import { getDefaultKimakiDirectory } from '../channel-management.js'
|
|
16
|
-
import { getBotTokenWithMode, getThreadSession, getThreadIdBySessionId, getSessionEventSnapshot, getDb, createScheduledTask, listScheduledTasks, cancelScheduledTask, getScheduledTask, updateScheduledTask, getSessionStartSourcesBySessionIds, deleteChannelDirectoryById, findChannelsByDirectory } from '../database.js'
|
|
16
|
+
import { getBotTokenWithMode, getThreadSession, getThreadIdBySessionId, getSessionEventSnapshot, getDb, createScheduledTask, listScheduledTasks, cancelScheduledTask, getScheduledTask, updateScheduledTask, getSessionStartSourcesBySessionIds, deleteChannelDirectoryById, findChannelsByDirectory, findRegisteredTextChannelForDirectory, formatProjectAlreadyRegisteredError } from '../database.js'
|
|
17
17
|
import { ShareMarkdown } from '../markdown.js'
|
|
18
18
|
import { parseSessionSearchPattern, findFirstSessionSearchHit, buildSessionSearchSnippet, getPartSearchTexts } from '../session-search.js'
|
|
19
19
|
import { formatWorktreeName, formatAutoWorktreeName } from '../commands/new-worktree.js'
|
|
@@ -77,9 +77,19 @@ cli
|
|
|
77
77
|
process.exit(EXIT_NO_RESTART)
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
-
// Initialize database
|
|
81
80
|
await initDatabase()
|
|
82
81
|
|
|
82
|
+
const existingChannel = await findRegisteredTextChannelForDirectory(absolutePath)
|
|
83
|
+
if (existingChannel) {
|
|
84
|
+
cliLogger.error(
|
|
85
|
+
formatProjectAlreadyRegisteredError({
|
|
86
|
+
channelId: existingChannel.channel_id,
|
|
87
|
+
directory: absolutePath,
|
|
88
|
+
}),
|
|
89
|
+
)
|
|
90
|
+
process.exit(EXIT_NO_RESTART)
|
|
91
|
+
}
|
|
92
|
+
|
|
83
93
|
const { token: botToken, appId } = await resolveBotCredentials({
|
|
84
94
|
appIdOverride: options.appId,
|
|
85
95
|
})
|
|
@@ -109,38 +119,6 @@ cli
|
|
|
109
119
|
|
|
110
120
|
const guild = await resolveGuildForProjectCommand({ client, guildIdOverride: options.guild })
|
|
111
121
|
|
|
112
|
-
// Check if channel already exists in this guild
|
|
113
|
-
cliLogger.log('Checking for existing channel...')
|
|
114
|
-
try {
|
|
115
|
-
const existingChannels = await findChannelsByDirectory({
|
|
116
|
-
directory: absolutePath,
|
|
117
|
-
channelType: 'text',
|
|
118
|
-
})
|
|
119
|
-
|
|
120
|
-
for (const existingChannel of existingChannels) {
|
|
121
|
-
try {
|
|
122
|
-
const ch = await client.channels.fetch(existingChannel.channel_id)
|
|
123
|
-
if (ch && !ch.isDMBased() && ch.guild.id === guild.id) {
|
|
124
|
-
void client.destroy()
|
|
125
|
-
cliLogger.error(
|
|
126
|
-
`Channel already exists for this directory in ${guild.name}. Channel ID: ${existingChannel.channel_id}`,
|
|
127
|
-
)
|
|
128
|
-
process.exit(EXIT_NO_RESTART)
|
|
129
|
-
}
|
|
130
|
-
} catch (error) {
|
|
131
|
-
cliLogger.debug(
|
|
132
|
-
`Failed to fetch channel ${existingChannel.channel_id} while checking existing channels:`,
|
|
133
|
-
error instanceof Error ? error.stack : String(error),
|
|
134
|
-
)
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
} catch (error) {
|
|
138
|
-
cliLogger.debug(
|
|
139
|
-
'Database lookup failed while checking existing channels:',
|
|
140
|
-
error instanceof Error ? error.stack : String(error),
|
|
141
|
-
)
|
|
142
|
-
}
|
|
143
|
-
|
|
144
122
|
const { textChannelId, voiceChannelId, channelName } =
|
|
145
123
|
await createProjectChannels({
|
|
146
124
|
guild,
|
|
@@ -207,7 +185,8 @@ cli
|
|
|
207
185
|
guildId = data.guild_id || ''
|
|
208
186
|
} catch (error) {
|
|
209
187
|
// Only mark as deleted for Unknown Channel (10003) or 404,
|
|
210
|
-
// not transient errors like rate limits or 5xx
|
|
188
|
+
// not transient errors like rate limits or 5xx.
|
|
189
|
+
// Gateway-proxy must return Discord 404 for unknown channels, not 403.
|
|
211
190
|
const code = error instanceof Error ? Reflect.get(error, 'code') : undefined
|
|
212
191
|
const status = error instanceof Error ? Reflect.get(error, 'status') : undefined
|
|
213
192
|
const isUnknownChannel = code === 10003 || status === 404
|
package/src/cli-commands/send.ts
CHANGED
|
@@ -26,6 +26,7 @@ import { ShareMarkdown } from '../markdown.js'
|
|
|
26
26
|
import { parseSessionSearchPattern, findFirstSessionSearchHit, buildSessionSearchSnippet, getPartSearchTexts } from '../session-search.js'
|
|
27
27
|
import { formatWorktreeName, formatAutoWorktreeName } from '../commands/new-worktree.js'
|
|
28
28
|
import { WORKTREE_PREFIX } from '../commands/merge-worktree.js'
|
|
29
|
+
import { QUEUE_PREFIX } from '../message-formatting.js'
|
|
29
30
|
import type { ThreadStartMarker } from '../system-message.js'
|
|
30
31
|
import { buildOpencodeEventLogLine } from '../session-handler/opencode-session-event-log.js'
|
|
31
32
|
import { createDiscordRest } from '../discord-urls.js'
|
|
@@ -47,6 +48,7 @@ import {
|
|
|
47
48
|
resolveDiscordUserOption,
|
|
48
49
|
sendDiscordMessageWithOptionalAttachment,
|
|
49
50
|
} from '../cli-runner.js'
|
|
51
|
+
import { validateCliModelOption } from '../session-handler/model-utils.js'
|
|
50
52
|
|
|
51
53
|
const cliLogger = createLogger(LogPrefix.CLI)
|
|
52
54
|
const cli = goke()
|
|
@@ -178,6 +180,14 @@ cli
|
|
|
178
180
|
process.exit(EXIT_NO_RESTART)
|
|
179
181
|
}
|
|
180
182
|
|
|
183
|
+
const earlyModelCheck = await validateCliModelOption({
|
|
184
|
+
model: options.model,
|
|
185
|
+
})
|
|
186
|
+
if (earlyModelCheck instanceof Error) {
|
|
187
|
+
cliLogger.error(earlyModelCheck.message)
|
|
188
|
+
process.exit(EXIT_NO_RESTART)
|
|
189
|
+
}
|
|
190
|
+
|
|
181
191
|
const filePaths = options.file?.length
|
|
182
192
|
? options.file.map((f: string) => path.resolve(f))
|
|
183
193
|
: undefined
|
|
@@ -485,6 +495,14 @@ cli
|
|
|
485
495
|
// We only require it for features that genuinely need a local directory
|
|
486
496
|
// (scheduled tasks and --wait).
|
|
487
497
|
const channelConfig = await getChannelDirectory(threadData.parent_id)
|
|
498
|
+
const threadModelCheck = await validateCliModelOption({
|
|
499
|
+
model: options.model,
|
|
500
|
+
directory: channelConfig?.directory,
|
|
501
|
+
})
|
|
502
|
+
if (threadModelCheck instanceof Error) {
|
|
503
|
+
cliLogger.error(threadModelCheck.message)
|
|
504
|
+
process.exit(EXIT_NO_RESTART)
|
|
505
|
+
}
|
|
488
506
|
|
|
489
507
|
// Guard early: fail before sending the message if a feature that
|
|
490
508
|
// needs local project directory mapping is requested.
|
|
@@ -537,6 +555,10 @@ cli
|
|
|
537
555
|
|
|
538
556
|
const threadPromptMarker: ThreadStartMarker = {
|
|
539
557
|
start: true,
|
|
558
|
+
...(threadTargetUser && {
|
|
559
|
+
userId: threadTargetUser.id,
|
|
560
|
+
...(threadTargetUser.username && { username: threadTargetUser.username }),
|
|
561
|
+
}),
|
|
540
562
|
...(options.agent && { agent: options.agent }),
|
|
541
563
|
...(options.model && { model: options.model }),
|
|
542
564
|
...(options.permission?.length ? { permissions: options.permission } : {}),
|
|
@@ -553,7 +575,7 @@ cli
|
|
|
553
575
|
// Prefix the prompt so it's clear who sent it (matches /queue format).
|
|
554
576
|
// Use a newline between prefix and prompt so leading /command
|
|
555
577
|
// detection can find the command on its own line.
|
|
556
|
-
const prefixedPrompt =
|
|
578
|
+
const prefixedPrompt = `${QUEUE_PREFIX}**kimaki-cli:**\n${prompt}`
|
|
557
579
|
|
|
558
580
|
if (threadTargetUser) {
|
|
559
581
|
cliLogger.log(
|
|
@@ -624,6 +646,14 @@ cli
|
|
|
624
646
|
// (--send-at, --wait, --cwd).
|
|
625
647
|
const channelConfig = await getChannelDirectory(channelData.id)
|
|
626
648
|
const projectDirectory = channelConfig?.directory
|
|
649
|
+
const channelModelCheck = await validateCliModelOption({
|
|
650
|
+
model: options.model,
|
|
651
|
+
directory: projectDirectory,
|
|
652
|
+
})
|
|
653
|
+
if (channelModelCheck instanceof Error) {
|
|
654
|
+
cliLogger.error(channelModelCheck.message)
|
|
655
|
+
process.exit(EXIT_NO_RESTART)
|
|
656
|
+
}
|
|
627
657
|
|
|
628
658
|
// Features that require a local project directory mapping
|
|
629
659
|
const needsProjectDirectory = Boolean(parsedSchedule || options.wait || options.cwd)
|